ParameterUtils.java revision df6242e374b81e802a38cb891477f05d3e4b3cbc
1/* 2 * Copyright (C) 2014 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 17package android.hardware.camera2.legacy; 18 19import android.graphics.Matrix; 20import android.graphics.Rect; 21import android.graphics.RectF; 22import android.hardware.Camera; 23import android.hardware.camera2.utils.ListUtils; 24import android.hardware.camera2.utils.ParamsUtils; 25import android.hardware.camera2.utils.SizeAreaComparator; 26import android.util.Size; 27import android.util.SizeF; 28 29import android.util.Log; 30 31import java.util.ArrayList; 32import java.util.Arrays; 33import java.util.List; 34 35import static com.android.internal.util.Preconditions.*; 36 37/** 38 * Various utilities for dealing with camera API1 parameters. 39 */ 40public class ParameterUtils { 41 private static final String TAG = "ParameterUtils"; 42 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 43 44 /** getZoomRatios stores zoom ratios in 1/100 increments, e.x. a zoom of 3.2 is 320 */ 45 private static final int ZOOM_RATIO_MULTIPLIER = 100; 46 47 /** 48 * Convert a camera API1 size into a util size 49 */ 50 public static Size convertSize(Camera.Size size) { 51 checkNotNull(size, "size must not be null"); 52 53 return new Size(size.width, size.height); 54 } 55 56 /** 57 * Convert a camera API1 list of sizes into a util list of sizes 58 */ 59 public static List<Size> convertSizeList(List<Camera.Size> sizeList) { 60 checkNotNull(sizeList, "sizeList must not be null"); 61 62 List<Size> sizes = new ArrayList<>(sizeList.size()); 63 for (Camera.Size s : sizeList) { 64 sizes.add(new Size(s.width, s.height)); 65 } 66 return sizes; 67 } 68 69 /** 70 * Returns the largest supported picture size, as compared by its area. 71 */ 72 public static Size getLargestSupportedJpegSizeByArea(Camera.Parameters params) { 73 checkNotNull(params, "params must not be null"); 74 75 List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes()); 76 return SizeAreaComparator.findLargestByArea(supportedJpegSizes); 77 } 78 79 /** 80 * Convert a camera area into a human-readable string. 81 */ 82 public static String stringFromArea(Camera.Area area) { 83 if (area == null) { 84 return null; 85 } else { 86 StringBuilder sb = new StringBuilder(); 87 Rect r = area.rect; 88 89 sb.setLength(0); 90 sb.append("(["); sb.append(r.left); sb.append(','); 91 sb.append(r.top); sb.append("]["); sb.append(r.right); 92 sb.append(','); sb.append(r.bottom); sb.append(']'); 93 94 sb.append(','); 95 sb.append(area.weight); 96 sb.append(')'); 97 98 return sb.toString(); 99 } 100 } 101 102 /** 103 * Calculate the closest zoom index for the user-requested crop region by rounding 104 * up to the closest (largest or equal) possible zoom crop. 105 * 106 * <p>If the requested crop region exceeds the size of the active array, it is 107 * shrunk to fit inside of the active array first.</p> 108 * 109 * <p>Since all api1 camera devices only support a discrete set of zooms, we have 110 * to translate the per-pixel-granularity requested crop region into a per-zoom-index 111 * granularity.</p> 112 * 113 * <p>Furthermore, since the zoom index and zoom levels also depends on the field-of-view 114 * of the preview, the current preview {@code streamSize} is also used.</p> 115 * 116 * <p>The calculated crop regions are then written to in-place to {@code reportedCropRegion} 117 * and {@code previewCropRegion}, in coordinates relative to the active array.</p> 118 * 119 * @param params non-{@code null} camera api1 parameters 120 * @param activeArray active array dimensions, in sensor space 121 * @param streamSize stream size dimensions, in pixels 122 * @param cropRegion user-specified crop region, in active array coordinates 123 * @param reportedCropRegion (out parameter) what the result for {@code cropRegion} looks like 124 * @param previewCropRegion (out parameter) what the visual preview crop is 125 * @return 126 * the zoom index inclusively between 0 and {@code Parameters#getMaxZoom}, 127 * where 0 means the camera is not zoomed 128 * 129 * @throws NullPointerException if any of the args were {@code null} 130 */ 131 public static int getClosestAvailableZoomCrop( 132 Camera.Parameters params, Rect activeArray, Size streamSize, Rect cropRegion, 133 /*out*/ 134 Rect reportedCropRegion, 135 Rect previewCropRegion) { 136 checkNotNull(params, "params must not be null"); 137 checkNotNull(activeArray, "activeArray must not be null"); 138 checkNotNull(streamSize, "streamSize must not be null"); 139 checkNotNull(reportedCropRegion, "reportedCropRegion must not be null"); 140 checkNotNull(previewCropRegion, "previewCropRegion must not be null"); 141 142 Rect actualCrop = new Rect(cropRegion); 143 144 /* 145 * Shrink requested crop region to fit inside of the active array size 146 */ 147 if (!actualCrop.intersect(activeArray)) { 148 Log.w(TAG, "getClosestAvailableZoomCrop - Crop region out of range; " + 149 "setting to active array size"); 150 actualCrop.set(activeArray); 151 } 152 153 Rect previewCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize); 154 155 // Make the user-requested crop region the same aspect ratio as the preview stream size 156 Rect cropRegionAsPreview = 157 shrinkToSameAspectRatioCentered(previewCrop, actualCrop); 158 159 if (VERBOSE) { 160 Log.v(TAG, "getClosestAvailableZoomCrop - actualCrop = " + actualCrop); 161 Log.v(TAG, 162 "getClosestAvailableZoomCrop - previewCrop = " + previewCrop); 163 Log.v(TAG, 164 "getClosestAvailableZoomCrop - cropRegionAsPreview = " + cropRegionAsPreview); 165 } 166 167 /* 168 * Iterate all available zoom rectangles and find the closest zoom index 169 */ 170 Rect bestReportedCropRegion = null; 171 Rect bestPreviewCropRegion = null; 172 int bestZoomIndex = -1; 173 174 List<Rect> availableReportedCropRegions = 175 getAvailableZoomCropRectangles(params, activeArray); 176 List<Rect> availablePreviewCropRegions = 177 getAvailablePreviewZoomCropRectangles(params, activeArray, streamSize); 178 179 if (VERBOSE) { 180 Log.v(TAG, 181 "getClosestAvailableZoomCrop - availableReportedCropRegions = " + 182 ListUtils.listToString(availableReportedCropRegions)); 183 Log.v(TAG, 184 "getClosestAvailableZoomCrop - availablePreviewCropRegions = " + 185 ListUtils.listToString(availablePreviewCropRegions)); 186 } 187 188 if (availableReportedCropRegions.size() != availablePreviewCropRegions.size()) { 189 throw new AssertionError("available reported/preview crop region size mismatch"); 190 } 191 192 for (int i = 0; i < availableReportedCropRegions.size(); ++i) { 193 Rect currentPreviewCropRegion = availablePreviewCropRegions.get(i); 194 Rect currentReportedCropRegion = availableReportedCropRegions.get(i); 195 196 boolean isBest; 197 if (bestZoomIndex == -1) { 198 isBest = true; 199 } else if (currentPreviewCropRegion.width() >= cropRegionAsPreview.width() && 200 currentPreviewCropRegion.height() >= cropRegionAsPreview.height()) { 201 isBest = true; 202 } else { 203 isBest = false; 204 } 205 206 // Sizes are sorted largest-to-smallest, so once the available crop is too small, 207 // we the rest are too small. Furthermore, this is the final best crop, 208 // since its the largest crop that still fits the requested crop 209 if (isBest) { 210 bestPreviewCropRegion = currentPreviewCropRegion; 211 bestReportedCropRegion = currentReportedCropRegion; 212 bestZoomIndex = i; 213 } else { 214 break; 215 } 216 } 217 218 if (bestZoomIndex == -1) { 219 // Even in the worst case, we should always at least return 0 here 220 throw new AssertionError("Should've found at least one valid zoom index"); 221 } 222 223 // Write the rectangles in-place 224 reportedCropRegion.set(bestReportedCropRegion); 225 previewCropRegion.set(bestPreviewCropRegion); 226 227 return bestZoomIndex; 228 } 229 230 /** 231 * Calculate the effective crop rectangle for this preview viewport; 232 * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions 233 * without skewing. 234 * 235 * <p>The preview size must be a subset of the active array size; the resulting 236 * rectangle will also be a subset of the active array rectangle.</p> 237 * 238 * <p>The unzoomed crop rectangle is calculated only.</p> 239 * 240 * @param activeArray active array dimensions, in sensor space 241 * @param previewSize size of the preview buffer render target, in pixels (not in sensor space) 242 * @return a rectangle which serves as the preview stream's effective crop region (unzoomed), 243 * in sensor space 244 * 245 * @throws NullPointerException 246 * if any of the args were {@code null} 247 * @throws IllegalArgumentException 248 * if {@code previewSize} is wider or taller than {@code activeArray} 249 */ 250 private static Rect getPreviewCropRectangleUnzoomed(Rect activeArray, Size previewSize) { 251 if (previewSize.getWidth() > activeArray.width()) { 252 throw new IllegalArgumentException("previewSize must not be wider than activeArray"); 253 } else if (previewSize.getHeight() > activeArray.height()) { 254 throw new IllegalArgumentException("previewSize must not be taller than activeArray"); 255 } 256 257 float aspectRatioArray = activeArray.width() * 1.0f / activeArray.height(); 258 float aspectRatioPreview = previewSize.getWidth() * 1.0f / previewSize.getHeight(); 259 260 float cropH, cropW; 261 if (aspectRatioPreview < aspectRatioArray) { 262 // The new width must be smaller than the height, so scale the width by AR 263 cropH = activeArray.height(); 264 cropW = cropH * aspectRatioPreview; 265 } else { 266 // The new height must be smaller (or equal) than the width, so scale the height by AR 267 cropW = activeArray.width(); 268 cropH = cropW / aspectRatioPreview; 269 } 270 271 Matrix translateMatrix = new Matrix(); 272 RectF cropRect = new RectF(/*left*/0, /*top*/0, cropW, cropH); 273 274 // Now center the crop rectangle so its center is in the center of the active array 275 translateMatrix.setTranslate(activeArray.exactCenterX(), activeArray.exactCenterY()); 276 translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); 277 278 translateMatrix.mapRect(/*inout*/cropRect); 279 280 // Round the rect corners towards the nearest integer values 281 return ParamsUtils.createRect(cropRect); 282 } 283 284 /** 285 * Shrink the {@code shrinkTarget} rectangle to snugly fit inside of {@code reference}; 286 * the aspect ratio of {@code shrinkTarget} will change to be the same aspect ratio as 287 * {@code reference}. 288 * 289 * <p>At most a single dimension will scale (down). Both dimensions will never be scaled.</p> 290 * 291 * @param reference the rectangle whose aspect ratio will be used as the new aspect ratio 292 * @param shrinkTarget the rectangle which will be scaled down to have a new aspect ratio 293 * 294 * @return a new rectangle, a subset of {@code shrinkTarget}, 295 * whose aspect ratio will match that of {@code reference} 296 */ 297 private static Rect shrinkToSameAspectRatioCentered(Rect reference, Rect shrinkTarget) { 298 float aspectRatioReference = reference.width() * 1.0f / reference.height(); 299 float aspectRatioShrinkTarget = shrinkTarget.width() * 1.0f / shrinkTarget.height(); 300 301 float cropH, cropW; 302 if (aspectRatioShrinkTarget < aspectRatioReference) { 303 // The new width must be smaller than the height, so scale the width by AR 304 cropH = reference.height(); 305 cropW = cropH * aspectRatioShrinkTarget; 306 } else { 307 // The new height must be smaller (or equal) than the width, so scale the height by AR 308 cropW = reference.width(); 309 cropH = cropW / aspectRatioShrinkTarget; 310 } 311 312 Matrix translateMatrix = new Matrix(); 313 RectF shrunkRect = new RectF(shrinkTarget); 314 315 // Scale the rectangle down, but keep its center in the same place as before 316 translateMatrix.setScale(cropW / reference.width(), cropH / reference.height(), 317 shrinkTarget.exactCenterX(), shrinkTarget.exactCenterY()); 318 319 translateMatrix.mapRect(/*inout*/shrunkRect); 320 321 return ParamsUtils.createRect(shrunkRect); 322 } 323 324 /** 325 * Get the available 'crop' (zoom) rectangles for this camera that will be reported 326 * via a {@code CaptureResult} when a zoom is requested. 327 * 328 * <p>These crops ignores the underlying preview buffer size, and will always be reported 329 * the same values regardless of what configuration of outputs is used.</p> 330 * 331 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 332 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 333 * 334 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 335 * by shrinking the rectangle if necessary.</p> 336 * 337 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 338 * = {@code activeArray size}.</p> 339 * 340 * @param params non-{@code null} camera api1 parameters 341 * @param activeArray active array dimensions, in sensor space 342 * @param streamSize stream size dimensions, in pixels 343 * 344 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 345 */ 346 public static List<Rect> getAvailableZoomCropRectangles( 347 Camera.Parameters params, Rect activeArray) { 348 checkNotNull(params, "params must not be null"); 349 checkNotNull(activeArray, "activeArray must not be null"); 350 351 return getAvailableCropRectangles(params, activeArray, ParamsUtils.createSize(activeArray)); 352 } 353 354 /** 355 * Get the available 'crop' (zoom) rectangles for this camera. 356 * 357 * <p>This is the effective (real) crop that is applied by the camera api1 device 358 * when projecting the zoom onto the intermediate preview buffer. Use this when 359 * deciding which zoom ratio to apply.</p> 360 * 361 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 362 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 363 * 364 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 365 * by shrinking the rectangle if necessary.</p> 366 * 367 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 368 * = {@code activeArray size}.</p> 369 * 370 * @param params non-{@code null} camera api1 parameters 371 * @param activeArray active array dimensions, in sensor space 372 * @param streamSize stream size dimensions, in pixels 373 * 374 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 375 */ 376 public static List<Rect> getAvailablePreviewZoomCropRectangles(Camera.Parameters params, 377 Rect activeArray, Size previewSize) { 378 checkNotNull(params, "params must not be null"); 379 checkNotNull(activeArray, "activeArray must not be null"); 380 checkNotNull(previewSize, "previewSize must not be null"); 381 382 return getAvailableCropRectangles(params, activeArray, previewSize); 383 } 384 385 /** 386 * Get the available 'crop' (zoom) rectangles for this camera. 387 * 388 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 389 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 390 * 391 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 392 * by shrinking the rectangle if necessary.</p> 393 * 394 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 395 * = {@code activeArray size}.</p> 396 * 397 * @param params non-{@code null} camera api1 parameters 398 * @param activeArray active array dimensions, in sensor space 399 * @param streamSize stream size dimensions, in pixels 400 * 401 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 402 */ 403 private static List<Rect> getAvailableCropRectangles(Camera.Parameters params, 404 Rect activeArray, Size streamSize) { 405 checkNotNull(params, "params must not be null"); 406 checkNotNull(activeArray, "activeArray must not be null"); 407 checkNotNull(streamSize, "streamSize must not be null"); 408 409 // TODO: change all uses of Rect activeArray to Size activeArray, 410 // since we want the crop to be active-array relative, not pixel-array relative 411 412 Rect unzoomedStreamCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize); 413 414 if (!params.isZoomSupported()) { 415 // Trivial case: No zoom -> only support the full size as the crop region 416 return new ArrayList<>(Arrays.asList(unzoomedStreamCrop)); 417 } 418 419 List<Rect> zoomCropRectangles = new ArrayList<>(params.getMaxZoom() + 1); 420 Matrix scaleMatrix = new Matrix(); 421 RectF scaledRect = new RectF(); 422 423 for (int zoom : params.getZoomRatios()) { 424 float shrinkRatio = ZOOM_RATIO_MULTIPLIER * 1.0f / zoom; // normalize to 1.0 and smaller 425 426 // set scaledRect to unzoomedStreamCrop 427 ParamsUtils.convertRectF(unzoomedStreamCrop, /*out*/scaledRect); 428 429 scaleMatrix.setScale( 430 shrinkRatio, shrinkRatio, 431 activeArray.exactCenterX(), 432 activeArray.exactCenterY()); 433 434 scaleMatrix.mapRect(scaledRect); 435 436 Rect intRect = ParamsUtils.createRect(scaledRect); 437 438 // Round the rect corners towards the nearest integer values 439 zoomCropRectangles.add(intRect); 440 } 441 442 return zoomCropRectangles; 443 } 444 445 /** 446 * Get the largest possible zoom ratio (normalized to {@code 1.0f} and higher) 447 * that the camera can support. 448 * 449 * <p>If the camera does not support zoom, it always returns {@code 1.0f}.</p> 450 * 451 * @param params non-{@code null} camera api1 parameters 452 * @return normalized max zoom ratio, at least {@code 1.0f} 453 */ 454 public static float getMaxZoomRatio(Camera.Parameters params) { 455 if (!params.isZoomSupported()) { 456 return 1.0f; // no zoom 457 } 458 459 List<Integer> zoomRatios = params.getZoomRatios(); // sorted smallest->largest 460 int zoom = zoomRatios.get(zoomRatios.size() - 1); // largest zoom ratio 461 float zoomRatio = zoom * 1.0f / ZOOM_RATIO_MULTIPLIER; // normalize to 1.0 and smaller 462 463 return zoomRatio; 464 } 465 466 /** 467 * Returns the component-wise zoom ratio (each greater or equal than {@code 1.0}); 468 * largest values means more zoom. 469 * 470 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 471 * @param cropSize size of the crop/zoom 472 * 473 * @return {@link SizeF} with width/height being the component-wise zoom ratio 474 * 475 * @throws NullPointerException if any of the args were {@code null} 476 * @throws IllegalArgumentException if any component of {@code cropSize} was {@code 0} 477 */ 478 private static SizeF getZoomRatio(Size activeArraySize, Size cropSize) { 479 checkNotNull(activeArraySize, "activeArraySize must not be null"); 480 checkNotNull(cropSize, "cropSize must not be null"); 481 checkArgumentPositive(cropSize.getWidth(), "cropSize.width must be positive"); 482 checkArgumentPositive(cropSize.getHeight(), "cropSize.height must be positive"); 483 484 float zoomRatioWidth = activeArraySize.getWidth() * 1.0f / cropSize.getWidth(); 485 float zoomRatioHeight = activeArraySize.getHeight() * 1.0f / cropSize.getHeight(); 486 487 return new SizeF(zoomRatioWidth, zoomRatioHeight); 488 } 489 490 private ParameterUtils() { 491 throw new AssertionError(); 492 } 493} 494