1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.core; 18 19 import android.util.Rational; 20 import android.view.Surface; 21 import android.view.SurfaceView; 22 import android.view.View; 23 24 import androidx.annotation.IntDef; 25 import androidx.annotation.RestrictTo; 26 import androidx.camera.core.impl.ImageOutputConfig; 27 import androidx.camera.core.resolutionselector.AspectRatioStrategy; 28 import androidx.camera.core.resolutionselector.ResolutionSelector; 29 import androidx.core.util.Preconditions; 30 31 import org.jspecify.annotations.NonNull; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.concurrent.Executor; 36 37 /** 38 * The field of view of one or many {@link UseCase}s. 39 * 40 * <p> The {@link ViewPort} defines a FOV which is used by CameraX to calculate output crop rects. 41 * For use cases associated with the same {@link ViewPort} in a {@link UseCaseGroup}, the output 42 * crop rect will be mapped to the same camera sensor area. Usually {@link ViewPort} is 43 * configured to optimize for {@link Preview} so that {@link ImageAnalysis} and 44 * {@link ImageCapture} produce the same crop rect in a WYSIWYG way. 45 * 46 * <p> If the {@link ViewPort} is used with a {@link ImageCapture} and 47 * {@link ImageCapture#takePicture( 48 *ImageCapture.OutputFileOptions, Executor, ImageCapture.OnImageSavedCallback)} is called, 49 * the image may be cropped before saving to disk which introduces an additional 50 * latency. To avoid the latency and get the uncropped image, please use the in-memory method 51 * {@link ImageCapture#takePicture(Executor, ImageCapture.OnImageCapturedCallback)}. 52 * 53 * <p> For {@link ImageAnalysis} and in-memory {@link ImageCapture}, the output crop rect is 54 * {@link ImageProxy#getCropRect()}; for on-disk {@link ImageCapture}, the image is cropped before 55 * saving; for {@link Preview}, the crop rect is 56 * {@link SurfaceRequest.TransformationInfo#getCropRect()}. Caller should transform the output in 57 * a way that only the area defined by the crop rect is visible to end users. Once the crop rect 58 * is applied, all the use cases will produce the same image with possibly different resolutions. 59 */ 60 public final class ViewPort { 61 62 /** 63 * LayoutDirection that defines the start and end of the {@link ScaleType}. 64 * 65 * @see android.util.LayoutDirection 66 */ 67 @IntDef({android.util.LayoutDirection.LTR, android.util.LayoutDirection.RTL}) 68 @Retention(RetentionPolicy.SOURCE) 69 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 70 public @interface LayoutDirection { 71 } 72 73 /** 74 * Scale types used to calculate the crop rect for a {@link UseCase}. 75 * 76 */ 77 @IntDef({FILL_START, FILL_CENTER, FILL_END, FIT}) 78 @Retention(RetentionPolicy.SOURCE) 79 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 80 public @interface ScaleType { 81 } 82 83 /** 84 * Generate a crop rect that once applied, it scales the output while maintaining its aspect 85 * ratio, so it fills the entire {@link ViewPort}, and align it to the start of the 86 * {@link ViewPort}, which is the top left corner in a left-to-right (LTR) layout, or the top 87 * right corner in a right-to-left (RTL) layout. 88 * <p> 89 * This may cause the output to be cropped if the output aspect ratio does not match that of 90 * the {@link ViewPort}. 91 */ 92 public static final int FILL_START = 0; 93 94 /** 95 * Generate a crop rect that once applied, it scales the output while maintaining its aspect 96 * ratio, so it fills the entire {@link ViewPort} and center it. 97 * <p> 98 * This may cause the output to be cropped if the output aspect ratio does not match that of 99 * the {@link ViewPort}. 100 */ 101 public static final int FILL_CENTER = 1; 102 103 /** 104 * Generate a crop rect that once applied, it scales the output while maintaining its aspect 105 * ratio, so it fills the entire {@link ViewPort}, and align it to the end of the 106 * {@link ViewPort}, which is the bottom right corner in a left-to-right (LTR) layout, or the 107 * bottom left corner in a right-to-left (RTL) layout. 108 * <p> 109 * This may cause the output to be cropped if the output aspect ratio does not match that of 110 * the {@link ViewPort}. 111 */ 112 public static final int FILL_END = 2; 113 114 /** 115 * Generate the max possible crop rect ignoring the aspect ratio. For {@link ImageAnalysis} 116 * and {@link ImageCapture}, the output will be an image defined by the crop rect. 117 * 118 * <p> For {@link Preview}, further calculation is needed to to fit the crop rect into the 119 * viewfinder. Code sample below is a simplified version assuming {@link Surface} 120 * orientation is the same as the camera sensor orientation, the viewfinder is a 121 * {@link SurfaceView} and the viewfinder's pixel width/height is the same as the size 122 * request by CameraX in {@link SurfaceRequest#getResolution()}. For more complicated 123 * scenarios, please check out the source code of PreviewView in androidx.camera.view artifact. 124 * 125 * <p> First, calculate the transformation to fit the crop rect in the center of the viewfinder: 126 * 127 * <pre>{@code 128 * val transformation = Matrix() 129 * transformation.setRectToRect( 130 * cropRect, new RectF(0, 0, viewFinder.width, viewFinder.height, ScaleToFit.CENTER)) 131 * }</pre> 132 * 133 * <p> Then apply the transformation to the viewfinder: 134 * 135 * <pre>{@code 136 * val transformedRect = RectF(0, 0, viewFinder.width, viewFinder.height) 137 * transformation.mapRect(surfaceRect) 138 * viewFinder.pivotX = 0 139 * viewFinder.pivotY = 0 140 * viewFinder.translationX = transformedRect.left 141 * viewFinder.translationY = transformedRect.top 142 * viewFinder.scaleX = surfaceRect.width/transformedRect.width 143 * viewFinder.scaleY = surfaceRect.height/transformedRect.height 144 * }</pre> 145 */ 146 public static final int FIT = 3; 147 148 @ScaleType 149 private int mScaleType; 150 151 private @NonNull Rational mAspectRatio; 152 153 @ImageOutputConfig.RotationValue 154 private int mRotation; 155 156 @LayoutDirection 157 private int mLayoutDirection; 158 ViewPort(@caleType int scaleType, @NonNull Rational aspectRatio, @ImageOutputConfig.RotationValue int rotation, @LayoutDirection int layoutDirection)159 ViewPort(@ScaleType int scaleType, @NonNull Rational aspectRatio, 160 @ImageOutputConfig.RotationValue int rotation, @LayoutDirection int layoutDirection) { 161 mScaleType = scaleType; 162 mAspectRatio = aspectRatio; 163 mRotation = rotation; 164 mLayoutDirection = layoutDirection; 165 } 166 167 /** 168 * Gets the aspect ratio of the {@link ViewPort}. 169 */ getAspectRatio()170 public @NonNull Rational getAspectRatio() { 171 return mAspectRatio; 172 } 173 174 /** 175 * Gets the rotation of the {@link ViewPort}. 176 */ 177 @ImageOutputConfig.RotationValue getRotation()178 public int getRotation() { 179 return mRotation; 180 } 181 182 /** 183 * Gets the scale type of the {@link ViewPort}. 184 */ 185 @ScaleType getScaleType()186 public int getScaleType() { 187 return mScaleType; 188 } 189 190 /** 191 * Gets the layout direction of the {@link ViewPort}. 192 */ 193 @LayoutDirection getLayoutDirection()194 public int getLayoutDirection() { 195 return mLayoutDirection; 196 } 197 198 /** 199 * Builder for {@link ViewPort}. 200 */ 201 public static final class Builder { 202 203 private static final int DEFAULT_LAYOUT_DIRECTION = android.util.LayoutDirection.LTR; 204 @ScaleType 205 private static final int DEFAULT_SCALE_TYPE = FILL_CENTER; 206 207 @ScaleType 208 private int mScaleType = DEFAULT_SCALE_TYPE; 209 210 private final Rational mAspectRatio; 211 212 @ImageOutputConfig.RotationValue 213 private final int mRotation; 214 215 @LayoutDirection 216 private int mLayoutDirection = DEFAULT_LAYOUT_DIRECTION; 217 218 /** 219 * Creates {@link ViewPort.Builder} with aspect ratio and rotation. 220 * 221 * <p> To create a {@link ViewPort} that is based on the {@link Preview} use 222 * case, the aspect ratio should be the dimension of the {@link View} and 223 * the rotation should be the value of {@link Preview#getTargetRotation()}: 224 * 225 * <pre>{@code 226 * val aspectRatio = Rational(viewFinder.width, viewFinder.height) 227 * val viewport = ViewPort.Builder(aspectRatio, preview.getTargetRotation()).build() 228 * }</pre> 229 * 230 * <p> In a scenario where {@link Preview} is not used, for example, face detection in 231 * {@link ImageAnalysis} and taking pictures with {@link ImageCapture} when faces are 232 * found, the {@link ViewPort} should be created with the aspect ratio and rotation of the 233 * {@link ImageCapture} use case. 234 * 235 * <p>All {@link UseCase}s have a configurable aspect ratio setting that determines the 236 * supported sizes for creating the capture session to receive images from the camera. 237 * See {@link ResolutionSelector.Builder#setAspectRatioStrategy(AspectRatioStrategy)} for 238 * {@link Preview}, {@link ImageCapture}, and {@link ImageAnalysis}, and 239 * {@link androidx.camera.video.Recorder.Builder#setAspectRatio(int)} for 240 * {@link androidx.camera.video.VideoCapture}. This is distinct from the {@link ViewPort} 241 * 's aspect ratio setting, which is used to calculate output crop rectangles among bound 242 * {@link UseCase}s to ensure that they have the same content. 243 * 244 * <p>To obtain the maximum field of view (FOV) of the full camera sensor, it is 245 * recommended that the {@link ViewPort} and the bound {@link UseCase}s have matching 246 * aspect ratio settings. Otherwise, the output crop rectangles may be a double-cropped 247 * result from the full camera sensor FOV. 248 * 249 * <p>For example, if all {@link UseCase}s have a 16:9 aspect ratio preference setting 250 * and are bound with a {@link ViewPort} with a 4:3 aspect ratio, the images obtained 251 * from the camera will be 16:9 images cropped from the 4:3 camera sensor data. The 252 * content inside the output crop rectangles will be 4:3 cropped images from the 16:9 253 * images cropped from the 4:3 camera sensor data. In other words, the images will be 254 * cropped twice: first to fit the 16:9 aspect ratio, and then to fit the 4:3 aspect 255 * ratio. This will result in a loss of FOV. To avoid this, it is important to ensure 256 * that the {@link ViewPort} and the bound {@link UseCase}s have matching aspect ratio 257 * settings. 258 * 259 * @param aspectRatio aspect ratio of the output crop rect if the scale type 260 * is FILL_START, FILL_CENTER or FILL_END. This is usually the 261 * width/height of the preview viewfinder that displays the camera 262 * feed. The value is ignored if the scale type is FIT. 263 * @param rotation The rotation value is one of four valid values: 264 * {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, 265 * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}. 266 */ Builder(@onNull Rational aspectRatio, @ImageOutputConfig.RotationValue int rotation)267 public Builder(@NonNull Rational aspectRatio, 268 @ImageOutputConfig.RotationValue int rotation) { 269 mAspectRatio = aspectRatio; 270 mRotation = rotation; 271 } 272 273 /** 274 * Sets the scale type of the {@link ViewPort}. 275 * 276 * <p> The value is used by {@link UseCase} to calculate the crop rect. 277 * 278 * <p> The default value is {@link #FILL_CENTER} if not set. 279 */ setScaleType(@caleType int scaleType)280 public @NonNull Builder setScaleType(@ScaleType int scaleType) { 281 mScaleType = scaleType; 282 return this; 283 } 284 285 /** 286 * Sets the layout direction of the {@link ViewPort}. 287 * 288 * <p> The layout direction decides the start and the end of the crop rect if 289 * the scale type is {@link #FILL_END} or {@link #FILL_START}. 290 * 291 * <p> The default value is {@link android.util.LayoutDirection#LTR} if not set. 292 */ setLayoutDirection(@ayoutDirection int layoutDirection)293 public @NonNull Builder setLayoutDirection(@LayoutDirection int layoutDirection) { 294 mLayoutDirection = layoutDirection; 295 return this; 296 } 297 298 /** 299 * Builds the {@link ViewPort}. 300 */ build()301 public @NonNull ViewPort build() { 302 Preconditions.checkNotNull(mAspectRatio, "The crop aspect ratio must be set."); 303 return new ViewPort(mScaleType, mAspectRatio, mRotation, mLayoutDirection); 304 } 305 } 306 } 307