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 17 package android.hardware.camera2.legacy; 18 19 import android.graphics.Matrix; 20 import android.graphics.Point; 21 import android.graphics.Rect; 22 import android.graphics.RectF; 23 import android.hardware.Camera; 24 import android.hardware.Camera.Area; 25 import android.hardware.camera2.legacy.ParameterUtils.MeteringData; 26 import android.hardware.camera2.legacy.ParameterUtils.ZoomData; 27 import android.hardware.camera2.params.Face; 28 import android.hardware.camera2.params.MeteringRectangle; 29 import android.hardware.camera2.utils.ListUtils; 30 import android.hardware.camera2.utils.ParamsUtils; 31 import android.hardware.camera2.utils.SizeAreaComparator; 32 import android.util.Size; 33 import android.util.SizeF; 34 35 import android.util.Log; 36 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.List; 40 41 import static com.android.internal.util.Preconditions.*; 42 43 /** 44 * Various utilities for dealing with camera API1 parameters. 45 */ 46 @SuppressWarnings("deprecation") 47 public class ParameterUtils { 48 /** Upper/left minimal point of a normalized rectangle */ 49 public static final int NORMALIZED_RECTANGLE_MIN = -1000; 50 /** Lower/right maximal point of a normalized rectangle */ 51 public static final int NORMALIZED_RECTANGLE_MAX = 1000; 52 /** The default normalized rectangle spans the entire size of the preview viewport */ 53 public static final Rect NORMALIZED_RECTANGLE_DEFAULT = new Rect( 54 NORMALIZED_RECTANGLE_MIN, 55 NORMALIZED_RECTANGLE_MIN, 56 NORMALIZED_RECTANGLE_MAX, 57 NORMALIZED_RECTANGLE_MAX); 58 /** The default normalized area uses the default normalized rectangle with a weight=1 */ 59 public static final Camera.Area CAMERA_AREA_DEFAULT = 60 new Camera.Area(new Rect(NORMALIZED_RECTANGLE_DEFAULT), 61 /*weight*/1); 62 /** Empty rectangle {@code 0x0+0,0} */ 63 public static final Rect RECTANGLE_EMPTY = 64 new Rect(/*left*/0, /*top*/0, /*right*/0, /*bottom*/0); 65 66 /** 67 * Calculate effective/reported zoom data from a user-specified crop region. 68 */ 69 public static class ZoomData { 70 /** Zoom index used by {@link Camera.Parameters#setZoom} */ 71 public final int zoomIndex; 72 /** Effective crop-region given the zoom index, coordinates relative to active-array */ 73 public final Rect previewCrop; 74 /** Reported crop-region given the zoom index, coordinates relative to active-array */ 75 public final Rect reportedCrop; 76 ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop)77 public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop) { 78 this.zoomIndex = zoomIndex; 79 this.previewCrop = previewCrop; 80 this.reportedCrop = reportedCrop; 81 } 82 } 83 84 /** 85 * Calculate effective/reported metering data from a user-specified metering region. 86 */ 87 public static class MeteringData { 88 /** 89 * The metering area scaled to the range of [-1000, 1000]. 90 * <p>Values outside of this range are clipped to be within the range.</p> 91 */ 92 public final Camera.Area meteringArea; 93 /** 94 * Effective preview metering region, coordinates relative to active-array. 95 * 96 * <p>Clipped to fit inside of the (effective) preview crop region.</p> 97 */ 98 public final Rect previewMetering; 99 /** 100 * Reported metering region, coordinates relative to active-array. 101 * 102 * <p>Clipped to fit inside of the (reported) resulting crop region.</p> 103 */ 104 public final Rect reportedMetering; 105 MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering)106 public MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering) { 107 this.meteringArea = meteringArea; 108 this.previewMetering = previewMetering; 109 this.reportedMetering = reportedMetering; 110 } 111 } 112 113 /** 114 * A weighted rectangle is an arbitrary rectangle (the coordinate system is unknown) with an 115 * arbitrary weight. 116 * 117 * <p>The user of this class must know what the coordinate system ahead of time; it's 118 * then possible to convert to a more concrete type such as a metering rectangle or a face. 119 * </p> 120 * 121 * <p>When converting to a more concrete type, out-of-range values are clipped; this prevents 122 * possible illegal argument exceptions being thrown at runtime.</p> 123 */ 124 public static class WeightedRectangle { 125 /** Arbitrary rectangle (the range is user-defined); never {@code null}. */ 126 public final Rect rect; 127 /** Arbitrary weight (the range is user-defined). */ 128 public final int weight; 129 130 /** 131 * Create a new weighted-rectangle from a non-{@code null} rectangle; the {@code weight} 132 * can be unbounded. 133 */ WeightedRectangle(Rect rect, int weight)134 public WeightedRectangle(Rect rect, int weight) { 135 this.rect = checkNotNull(rect, "rect must not be null"); 136 this.weight = weight; 137 } 138 139 /** 140 * Convert to a metering rectangle, clipping any of the values to stay within range. 141 * 142 * <p>If values are clipped, a warning is printed to logcat.</p> 143 * 144 * @return a new metering rectangle 145 */ toMetering()146 public MeteringRectangle toMetering() { 147 int weight = clip(this.weight, 148 MeteringRectangle.METERING_WEIGHT_MIN, 149 MeteringRectangle.METERING_WEIGHT_MAX, 150 rect, 151 "weight"); 152 153 int x = clipLower(rect.left, /*lo*/0, rect, "left"); 154 int y = clipLower(rect.top, /*lo*/0, rect, "top"); 155 int w = clipLower(rect.width(), /*lo*/0, rect, "width"); 156 int h = clipLower(rect.height(), /*lo*/0, rect, "height"); 157 158 return new MeteringRectangle(x, y, w, h, weight); 159 } 160 161 /** 162 * Convert to a face; the rect is considered to be the bounds, and the weight 163 * is considered to be the score. 164 * 165 * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX}, 166 * the score is clipped first and a warning is printed to logcat.</p> 167 * 168 * <p>If the id is negative, the id is changed to 0 and a warning is printed to 169 * logcat.</p> 170 * 171 * <p>All other parameters are passed-through as-is.</p> 172 * 173 * @return a new face with the optional features set 174 */ toFace( int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition)175 public Face toFace( 176 int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) { 177 int idSafe = clipLower(id, /*lo*/0, rect, "id"); 178 int score = clip(weight, 179 Face.SCORE_MIN, 180 Face.SCORE_MAX, 181 rect, 182 "score"); 183 184 return new Face(rect, score, idSafe, leftEyePosition, rightEyePosition, mouthPosition); 185 } 186 187 /** 188 * Convert to a face; the rect is considered to be the bounds, and the weight 189 * is considered to be the score. 190 * 191 * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX}, 192 * the score is clipped first and a warning is printed to logcat.</p> 193 * 194 * <p>All other parameters are passed-through as-is.</p> 195 * 196 * @return a new face without the optional features 197 */ toFace()198 public Face toFace() { 199 int score = clip(weight, 200 Face.SCORE_MIN, 201 Face.SCORE_MAX, 202 rect, 203 "score"); 204 205 return new Face(rect, score); 206 } 207 clipLower(int value, int lo, Rect rect, String name)208 private static int clipLower(int value, int lo, Rect rect, String name) { 209 return clip(value, lo, /*hi*/Integer.MAX_VALUE, rect, name); 210 } 211 clip(int value, int lo, int hi, Rect rect, String name)212 private static int clip(int value, int lo, int hi, Rect rect, String name) { 213 if (value < lo) { 214 Log.w(TAG, "toMetering - Rectangle " + rect + " " 215 + name + " too small, clip to " + lo); 216 value = lo; 217 } else if (value > hi) { 218 Log.w(TAG, "toMetering - Rectangle " + rect + " " 219 + name + " too small, clip to " + hi); 220 value = hi; 221 } 222 223 return value; 224 } 225 } 226 227 private static final String TAG = "ParameterUtils"; 228 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 229 230 /** getZoomRatios stores zoom ratios in 1/100 increments, e.x. a zoom of 3.2 is 320 */ 231 private static final int ZOOM_RATIO_MULTIPLIER = 100; 232 233 /** 234 * Convert a camera API1 size into a util size 235 */ convertSize(Camera.Size size)236 public static Size convertSize(Camera.Size size) { 237 checkNotNull(size, "size must not be null"); 238 239 return new Size(size.width, size.height); 240 } 241 242 /** 243 * Convert a camera API1 list of sizes into a util list of sizes 244 */ convertSizeList(List<Camera.Size> sizeList)245 public static List<Size> convertSizeList(List<Camera.Size> sizeList) { 246 checkNotNull(sizeList, "sizeList must not be null"); 247 248 List<Size> sizes = new ArrayList<>(sizeList.size()); 249 for (Camera.Size s : sizeList) { 250 sizes.add(new Size(s.width, s.height)); 251 } 252 return sizes; 253 } 254 255 /** 256 * Convert a camera API1 list of sizes into an array of sizes 257 */ convertSizeListToArray(List<Camera.Size> sizeList)258 public static Size[] convertSizeListToArray(List<Camera.Size> sizeList) { 259 checkNotNull(sizeList, "sizeList must not be null"); 260 261 Size[] array = new Size[sizeList.size()]; 262 int ctr = 0; 263 for (Camera.Size s : sizeList) { 264 array[ctr++] = new Size(s.width, s.height); 265 } 266 return array; 267 } 268 269 /** 270 * Check if the camera API1 list of sizes contains a size with the given dimens. 271 */ containsSize(List<Camera.Size> sizeList, int width, int height)272 public static boolean containsSize(List<Camera.Size> sizeList, int width, int height) { 273 checkNotNull(sizeList, "sizeList must not be null"); 274 for (Camera.Size s : sizeList) { 275 if (s.height == height && s.width == width) { 276 return true; 277 } 278 } 279 return false; 280 } 281 282 /** 283 * Returns the largest supported picture size, as compared by its area. 284 */ getLargestSupportedJpegSizeByArea(Camera.Parameters params)285 public static Size getLargestSupportedJpegSizeByArea(Camera.Parameters params) { 286 checkNotNull(params, "params must not be null"); 287 288 List<Size> supportedJpegSizes = convertSizeList(params.getSupportedPictureSizes()); 289 return SizeAreaComparator.findLargestByArea(supportedJpegSizes); 290 } 291 292 /** 293 * Convert a camera area into a human-readable string. 294 */ stringFromArea(Camera.Area area)295 public static String stringFromArea(Camera.Area area) { 296 if (area == null) { 297 return null; 298 } else { 299 StringBuilder sb = new StringBuilder(); 300 Rect r = area.rect; 301 302 sb.setLength(0); 303 sb.append("(["); sb.append(r.left); sb.append(','); 304 sb.append(r.top); sb.append("]["); sb.append(r.right); 305 sb.append(','); sb.append(r.bottom); sb.append(']'); 306 307 sb.append(','); 308 sb.append(area.weight); 309 sb.append(')'); 310 311 return sb.toString(); 312 } 313 } 314 315 /** 316 * Convert a camera area list into a human-readable string 317 * @param areaList a list of areas (null is ok) 318 */ stringFromAreaList(List<Camera.Area> areaList)319 public static String stringFromAreaList(List<Camera.Area> areaList) { 320 StringBuilder sb = new StringBuilder(); 321 322 if (areaList == null) { 323 return null; 324 } 325 326 int i = 0; 327 for (Camera.Area area : areaList) { 328 if (area == null) { 329 sb.append("null"); 330 } else { 331 sb.append(stringFromArea(area)); 332 } 333 334 if (i != areaList.size() - 1) { 335 sb.append(", "); 336 } 337 338 i++; 339 } 340 341 return sb.toString(); 342 } 343 344 /** 345 * Calculate the closest zoom index for the user-requested crop region by rounding 346 * up to the closest (largest or equal) possible zoom crop. 347 * 348 * <p>If the requested crop region exceeds the size of the active array, it is 349 * shrunk to fit inside of the active array first.</p> 350 * 351 * <p>Since all api1 camera devices only support a discrete set of zooms, we have 352 * to translate the per-pixel-granularity requested crop region into a per-zoom-index 353 * granularity.</p> 354 * 355 * <p>Furthermore, since the zoom index and zoom levels also depends on the field-of-view 356 * of the preview, the current preview {@code streamSize} is also used.</p> 357 * 358 * <p>The calculated crop regions are then written to in-place to {@code reportedCropRegion} 359 * and {@code previewCropRegion}, in coordinates relative to the active array.</p> 360 * 361 * @param params non-{@code null} camera api1 parameters 362 * @param activeArray active array dimensions, in sensor space 363 * @param streamSize stream size dimensions, in pixels 364 * @param cropRegion user-specified crop region, in active array coordinates 365 * @param reportedCropRegion (out parameter) what the result for {@code cropRegion} looks like 366 * @param previewCropRegion (out parameter) what the visual preview crop is 367 * @return 368 * the zoom index inclusively between 0 and {@code Parameters#getMaxZoom}, 369 * where 0 means the camera is not zoomed 370 * 371 * @throws NullPointerException if any of the args were {@code null} 372 */ getClosestAvailableZoomCrop( Camera.Parameters params, Rect activeArray, Size streamSize, Rect cropRegion, Rect reportedCropRegion, Rect previewCropRegion)373 public static int getClosestAvailableZoomCrop( 374 Camera.Parameters params, Rect activeArray, Size streamSize, Rect cropRegion, 375 /*out*/ 376 Rect reportedCropRegion, 377 Rect previewCropRegion) { 378 checkNotNull(params, "params must not be null"); 379 checkNotNull(activeArray, "activeArray must not be null"); 380 checkNotNull(streamSize, "streamSize must not be null"); 381 checkNotNull(reportedCropRegion, "reportedCropRegion must not be null"); 382 checkNotNull(previewCropRegion, "previewCropRegion must not be null"); 383 384 Rect actualCrop = new Rect(cropRegion); 385 386 /* 387 * Shrink requested crop region to fit inside of the active array size 388 */ 389 if (!actualCrop.intersect(activeArray)) { 390 Log.w(TAG, "getClosestAvailableZoomCrop - Crop region out of range; " + 391 "setting to active array size"); 392 actualCrop.set(activeArray); 393 } 394 395 Rect previewCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize); 396 397 // Make the user-requested crop region the same aspect ratio as the preview stream size 398 Rect cropRegionAsPreview = 399 shrinkToSameAspectRatioCentered(previewCrop, actualCrop); 400 401 if (VERBOSE) { 402 Log.v(TAG, "getClosestAvailableZoomCrop - actualCrop = " + actualCrop); 403 Log.v(TAG, 404 "getClosestAvailableZoomCrop - previewCrop = " + previewCrop); 405 Log.v(TAG, 406 "getClosestAvailableZoomCrop - cropRegionAsPreview = " + cropRegionAsPreview); 407 } 408 409 /* 410 * Iterate all available zoom rectangles and find the closest zoom index 411 */ 412 Rect bestReportedCropRegion = null; 413 Rect bestPreviewCropRegion = null; 414 int bestZoomIndex = -1; 415 416 List<Rect> availableReportedCropRegions = 417 getAvailableZoomCropRectangles(params, activeArray); 418 List<Rect> availablePreviewCropRegions = 419 getAvailablePreviewZoomCropRectangles(params, activeArray, streamSize); 420 421 if (VERBOSE) { 422 Log.v(TAG, 423 "getClosestAvailableZoomCrop - availableReportedCropRegions = " + 424 ListUtils.listToString(availableReportedCropRegions)); 425 Log.v(TAG, 426 "getClosestAvailableZoomCrop - availablePreviewCropRegions = " + 427 ListUtils.listToString(availablePreviewCropRegions)); 428 } 429 430 if (availableReportedCropRegions.size() != availablePreviewCropRegions.size()) { 431 throw new AssertionError("available reported/preview crop region size mismatch"); 432 } 433 434 for (int i = 0; i < availableReportedCropRegions.size(); ++i) { 435 Rect currentPreviewCropRegion = availablePreviewCropRegions.get(i); 436 Rect currentReportedCropRegion = availableReportedCropRegions.get(i); 437 438 boolean isBest; 439 if (bestZoomIndex == -1) { 440 isBest = true; 441 } else if (currentPreviewCropRegion.width() >= cropRegionAsPreview.width() && 442 currentPreviewCropRegion.height() >= cropRegionAsPreview.height()) { 443 isBest = true; 444 } else { 445 isBest = false; 446 } 447 448 // Sizes are sorted largest-to-smallest, so once the available crop is too small, 449 // we the rest are too small. Furthermore, this is the final best crop, 450 // since its the largest crop that still fits the requested crop 451 if (isBest) { 452 bestPreviewCropRegion = currentPreviewCropRegion; 453 bestReportedCropRegion = currentReportedCropRegion; 454 bestZoomIndex = i; 455 } else { 456 break; 457 } 458 } 459 460 if (bestZoomIndex == -1) { 461 // Even in the worst case, we should always at least return 0 here 462 throw new AssertionError("Should've found at least one valid zoom index"); 463 } 464 465 // Write the rectangles in-place 466 reportedCropRegion.set(bestReportedCropRegion); 467 previewCropRegion.set(bestPreviewCropRegion); 468 469 return bestZoomIndex; 470 } 471 472 /** 473 * Calculate the effective crop rectangle for this preview viewport; 474 * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions 475 * without skewing. 476 * 477 * <p>The preview size must be a subset of the active array size; the resulting 478 * rectangle will also be a subset of the active array rectangle.</p> 479 * 480 * <p>The unzoomed crop rectangle is calculated only.</p> 481 * 482 * @param activeArray active array dimensions, in sensor space 483 * @param previewSize size of the preview buffer render target, in pixels (not in sensor space) 484 * @return a rectangle which serves as the preview stream's effective crop region (unzoomed), 485 * in sensor space 486 * 487 * @throws NullPointerException 488 * if any of the args were {@code null} 489 * @throws IllegalArgumentException 490 * if {@code previewSize} is wider or taller than {@code activeArray} 491 */ getPreviewCropRectangleUnzoomed(Rect activeArray, Size previewSize)492 private static Rect getPreviewCropRectangleUnzoomed(Rect activeArray, Size previewSize) { 493 if (previewSize.getWidth() > activeArray.width()) { 494 throw new IllegalArgumentException("previewSize must not be wider than activeArray"); 495 } else if (previewSize.getHeight() > activeArray.height()) { 496 throw new IllegalArgumentException("previewSize must not be taller than activeArray"); 497 } 498 499 float aspectRatioArray = activeArray.width() * 1.0f / activeArray.height(); 500 float aspectRatioPreview = previewSize.getWidth() * 1.0f / previewSize.getHeight(); 501 502 float cropH, cropW; 503 if (aspectRatioPreview < aspectRatioArray) { 504 // The new width must be smaller than the height, so scale the width by AR 505 cropH = activeArray.height(); 506 cropW = cropH * aspectRatioPreview; 507 } else { 508 // The new height must be smaller (or equal) than the width, so scale the height by AR 509 cropW = activeArray.width(); 510 cropH = cropW / aspectRatioPreview; 511 } 512 513 Matrix translateMatrix = new Matrix(); 514 RectF cropRect = new RectF(/*left*/0, /*top*/0, cropW, cropH); 515 516 // Now center the crop rectangle so its center is in the center of the active array 517 translateMatrix.setTranslate(activeArray.exactCenterX(), activeArray.exactCenterY()); 518 translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); 519 520 translateMatrix.mapRect(/*inout*/cropRect); 521 522 // Round the rect corners towards the nearest integer values 523 return ParamsUtils.createRect(cropRect); 524 } 525 526 /** 527 * Shrink the {@code shrinkTarget} rectangle to snugly fit inside of {@code reference}; 528 * the aspect ratio of {@code shrinkTarget} will change to be the same aspect ratio as 529 * {@code reference}. 530 * 531 * <p>At most a single dimension will scale (down). Both dimensions will never be scaled.</p> 532 * 533 * @param reference the rectangle whose aspect ratio will be used as the new aspect ratio 534 * @param shrinkTarget the rectangle which will be scaled down to have a new aspect ratio 535 * 536 * @return a new rectangle, a subset of {@code shrinkTarget}, 537 * whose aspect ratio will match that of {@code reference} 538 */ shrinkToSameAspectRatioCentered(Rect reference, Rect shrinkTarget)539 private static Rect shrinkToSameAspectRatioCentered(Rect reference, Rect shrinkTarget) { 540 float aspectRatioReference = reference.width() * 1.0f / reference.height(); 541 float aspectRatioShrinkTarget = shrinkTarget.width() * 1.0f / shrinkTarget.height(); 542 543 float cropH, cropW; 544 if (aspectRatioShrinkTarget < aspectRatioReference) { 545 // The new width must be smaller than the height, so scale the width by AR 546 cropH = reference.height(); 547 cropW = cropH * aspectRatioShrinkTarget; 548 } else { 549 // The new height must be smaller (or equal) than the width, so scale the height by AR 550 cropW = reference.width(); 551 cropH = cropW / aspectRatioShrinkTarget; 552 } 553 554 Matrix translateMatrix = new Matrix(); 555 RectF shrunkRect = new RectF(shrinkTarget); 556 557 // Scale the rectangle down, but keep its center in the same place as before 558 translateMatrix.setScale(cropW / reference.width(), cropH / reference.height(), 559 shrinkTarget.exactCenterX(), shrinkTarget.exactCenterY()); 560 561 translateMatrix.mapRect(/*inout*/shrunkRect); 562 563 return ParamsUtils.createRect(shrunkRect); 564 } 565 566 /** 567 * Get the available 'crop' (zoom) rectangles for this camera that will be reported 568 * via a {@code CaptureResult} when a zoom is requested. 569 * 570 * <p>These crops ignores the underlying preview buffer size, and will always be reported 571 * the same values regardless of what configuration of outputs is used.</p> 572 * 573 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 574 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 575 * 576 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 577 * by shrinking the rectangle if necessary.</p> 578 * 579 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 580 * = {@code activeArray size}.</p> 581 * 582 * @param params non-{@code null} camera api1 parameters 583 * @param activeArray active array dimensions, in sensor space 584 * @param streamSize stream size dimensions, in pixels 585 * 586 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 587 */ getAvailableZoomCropRectangles( Camera.Parameters params, Rect activeArray)588 public static List<Rect> getAvailableZoomCropRectangles( 589 Camera.Parameters params, Rect activeArray) { 590 checkNotNull(params, "params must not be null"); 591 checkNotNull(activeArray, "activeArray must not be null"); 592 593 return getAvailableCropRectangles(params, activeArray, ParamsUtils.createSize(activeArray)); 594 } 595 596 /** 597 * Get the available 'crop' (zoom) rectangles for this camera. 598 * 599 * <p>This is the effective (real) crop that is applied by the camera api1 device 600 * when projecting the zoom onto the intermediate preview buffer. Use this when 601 * deciding which zoom ratio to apply.</p> 602 * 603 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 604 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 605 * 606 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 607 * by shrinking the rectangle if necessary.</p> 608 * 609 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 610 * = {@code activeArray size}.</p> 611 * 612 * @param params non-{@code null} camera api1 parameters 613 * @param activeArray active array dimensions, in sensor space 614 * @param streamSize stream size dimensions, in pixels 615 * 616 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 617 */ getAvailablePreviewZoomCropRectangles(Camera.Parameters params, Rect activeArray, Size previewSize)618 public static List<Rect> getAvailablePreviewZoomCropRectangles(Camera.Parameters params, 619 Rect activeArray, Size previewSize) { 620 checkNotNull(params, "params must not be null"); 621 checkNotNull(activeArray, "activeArray must not be null"); 622 checkNotNull(previewSize, "previewSize must not be null"); 623 624 return getAvailableCropRectangles(params, activeArray, previewSize); 625 } 626 627 /** 628 * Get the available 'crop' (zoom) rectangles for this camera. 629 * 630 * <p>When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, 631 * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).</p> 632 * 633 * <p>Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, 634 * by shrinking the rectangle if necessary.</p> 635 * 636 * <p>To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} 637 * = {@code activeArray size}.</p> 638 * 639 * @param params non-{@code null} camera api1 parameters 640 * @param activeArray active array dimensions, in sensor space 641 * @param streamSize stream size dimensions, in pixels 642 * 643 * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed 644 */ getAvailableCropRectangles(Camera.Parameters params, Rect activeArray, Size streamSize)645 private static List<Rect> getAvailableCropRectangles(Camera.Parameters params, 646 Rect activeArray, Size streamSize) { 647 checkNotNull(params, "params must not be null"); 648 checkNotNull(activeArray, "activeArray must not be null"); 649 checkNotNull(streamSize, "streamSize must not be null"); 650 651 // TODO: change all uses of Rect activeArray to Size activeArray, 652 // since we want the crop to be active-array relative, not pixel-array relative 653 654 Rect unzoomedStreamCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize); 655 656 if (!params.isZoomSupported()) { 657 // Trivial case: No zoom -> only support the full size as the crop region 658 return new ArrayList<>(Arrays.asList(unzoomedStreamCrop)); 659 } 660 661 List<Rect> zoomCropRectangles = new ArrayList<>(params.getMaxZoom() + 1); 662 Matrix scaleMatrix = new Matrix(); 663 RectF scaledRect = new RectF(); 664 665 for (int zoom : params.getZoomRatios()) { 666 float shrinkRatio = ZOOM_RATIO_MULTIPLIER * 1.0f / zoom; // normalize to 1.0 and smaller 667 668 // set scaledRect to unzoomedStreamCrop 669 ParamsUtils.convertRectF(unzoomedStreamCrop, /*out*/scaledRect); 670 671 scaleMatrix.setScale( 672 shrinkRatio, shrinkRatio, 673 activeArray.exactCenterX(), 674 activeArray.exactCenterY()); 675 676 scaleMatrix.mapRect(scaledRect); 677 678 Rect intRect = ParamsUtils.createRect(scaledRect); 679 680 // Round the rect corners towards the nearest integer values 681 zoomCropRectangles.add(intRect); 682 } 683 684 return zoomCropRectangles; 685 } 686 687 /** 688 * Get the largest possible zoom ratio (normalized to {@code 1.0f} and higher) 689 * that the camera can support. 690 * 691 * <p>If the camera does not support zoom, it always returns {@code 1.0f}.</p> 692 * 693 * @param params non-{@code null} camera api1 parameters 694 * @return normalized max zoom ratio, at least {@code 1.0f} 695 */ getMaxZoomRatio(Camera.Parameters params)696 public static float getMaxZoomRatio(Camera.Parameters params) { 697 if (!params.isZoomSupported()) { 698 return 1.0f; // no zoom 699 } 700 701 List<Integer> zoomRatios = params.getZoomRatios(); // sorted smallest->largest 702 int zoom = zoomRatios.get(zoomRatios.size() - 1); // largest zoom ratio 703 float zoomRatio = zoom * 1.0f / ZOOM_RATIO_MULTIPLIER; // normalize to 1.0 and smaller 704 705 return zoomRatio; 706 } 707 708 /** 709 * Returns the component-wise zoom ratio (each greater or equal than {@code 1.0}); 710 * largest values means more zoom. 711 * 712 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 713 * @param cropSize size of the crop/zoom 714 * 715 * @return {@link SizeF} with width/height being the component-wise zoom ratio 716 * 717 * @throws NullPointerException if any of the args were {@code null} 718 * @throws IllegalArgumentException if any component of {@code cropSize} was {@code 0} 719 */ getZoomRatio(Size activeArraySize, Size cropSize)720 private static SizeF getZoomRatio(Size activeArraySize, Size cropSize) { 721 checkNotNull(activeArraySize, "activeArraySize must not be null"); 722 checkNotNull(cropSize, "cropSize must not be null"); 723 checkArgumentPositive(cropSize.getWidth(), "cropSize.width must be positive"); 724 checkArgumentPositive(cropSize.getHeight(), "cropSize.height must be positive"); 725 726 float zoomRatioWidth = activeArraySize.getWidth() * 1.0f / cropSize.getWidth(); 727 float zoomRatioHeight = activeArraySize.getHeight() * 1.0f / cropSize.getHeight(); 728 729 return new SizeF(zoomRatioWidth, zoomRatioHeight); 730 } 731 732 /** 733 * Convert the user-specified crop region into zoom data; which can be used 734 * to set the parameters to a specific zoom index, or to report back to the user what the 735 * actual zoom was, or for other calculations requiring the current preview crop region. 736 * 737 * <p>None of the parameters are mutated.</p> 738 * 739 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 740 * @param cropRegion the user-specified crop region 741 * @param previewSize the current preview size (in pixels) 742 * @param params the current camera parameters (not mutated) 743 * 744 * @return the zoom index, and the effective/reported crop regions (relative to active array) 745 */ convertScalerCropRegion(Rect activeArraySize, Rect cropRegion, Size previewSize, Camera.Parameters params)746 public static ZoomData convertScalerCropRegion(Rect activeArraySize, Rect 747 cropRegion, Size previewSize, Camera.Parameters params) { 748 Rect activeArraySizeOnly = new Rect( 749 /*left*/0, /*top*/0, 750 activeArraySize.width(), activeArraySize.height()); 751 752 Rect userCropRegion = cropRegion; 753 754 if (userCropRegion == null) { 755 userCropRegion = activeArraySizeOnly; 756 } 757 758 if (VERBOSE) { 759 Log.v(TAG, "convertScalerCropRegion - user crop region was " + userCropRegion); 760 } 761 762 final Rect reportedCropRegion = new Rect(); 763 final Rect previewCropRegion = new Rect(); 764 final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly, 765 previewSize, userCropRegion, 766 /*out*/reportedCropRegion, /*out*/previewCropRegion); 767 768 if (VERBOSE) { 769 Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " + 770 "zoomIndex = " + zoomIdx + 771 ", reported crop region = " + reportedCropRegion + 772 ", preview crop region = " + previewCropRegion); 773 } 774 775 return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion); 776 } 777 778 /** 779 * Calculate the actual/effective/reported normalized rectangle data from a metering 780 * rectangle. 781 * 782 * <p>If any of the rectangles are out-of-range of their intended bounding box, 783 * the {@link #RECTANGLE_EMPTY empty rectangle} is substituted instead 784 * (with a weight of {@code 0}).</p> 785 * 786 * <p>The metering rectangle is bound by the crop region (effective/reported respectively). 787 * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.</p> 788 * 789 * <p>No parameters are mutated; returns the new metering data.</p> 790 * 791 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 792 * @param meteringRect the user-specified metering rectangle 793 * @param zoomData the calculated zoom data corresponding to this request 794 * 795 * @return the metering area, the reported/effective metering rectangles 796 */ convertMeteringRectangleToLegacy( Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData)797 public static MeteringData convertMeteringRectangleToLegacy( 798 Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData) { 799 Rect previewCrop = zoomData.previewCrop; 800 801 float scaleW = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f / 802 previewCrop.width(); 803 float scaleH = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f / 804 previewCrop.height(); 805 806 Matrix transform = new Matrix(); 807 // Move the preview crop so that top,left is at (0,0), otherwise after scaling 808 // the corner bounds will be outside of [-1000, 1000] 809 transform.setTranslate(-previewCrop.left, -previewCrop.top); 810 // Scale into [0, 2000] range about the center of the preview 811 transform.postScale(scaleW, scaleH); 812 // Move so that top left of a typical rect is at [-1000, -1000] 813 transform.postTranslate(/*dx*/NORMALIZED_RECTANGLE_MIN, /*dy*/NORMALIZED_RECTANGLE_MIN); 814 815 /* 816 * Calculate the preview metering region (effective), and the camera1 api 817 * normalized metering region. 818 */ 819 Rect normalizedRegionUnbounded = ParamsUtils.mapRect(transform, meteringRect.getRect()); 820 821 /* 822 * Try to intersect normalized area with [-1000, 1000] rectangle; otherwise 823 * it's completely out of range 824 */ 825 Rect normalizedIntersected = new Rect(normalizedRegionUnbounded); 826 827 Camera.Area meteringArea; 828 if (!normalizedIntersected.intersect(NORMALIZED_RECTANGLE_DEFAULT)) { 829 Log.w(TAG, 830 "convertMeteringRectangleToLegacy - metering rectangle too small, " + 831 "no metering will be done"); 832 normalizedIntersected.set(RECTANGLE_EMPTY); 833 meteringArea = new Camera.Area(RECTANGLE_EMPTY, 834 MeteringRectangle.METERING_WEIGHT_DONT_CARE); 835 } else { 836 meteringArea = new Camera.Area(normalizedIntersected, 837 meteringRect.getMeteringWeight()); 838 } 839 840 /* 841 * Calculate effective preview metering region 842 */ 843 Rect previewMetering = meteringRect.getRect(); 844 if (!previewMetering.intersect(previewCrop)) { 845 previewMetering.set(RECTANGLE_EMPTY); 846 } 847 848 /* 849 * Calculate effective reported metering region 850 * - Transform the calculated metering area back into active array space 851 * - Clip it to be a subset of the reported crop region 852 */ 853 Rect reportedMetering; 854 { 855 Camera.Area normalizedAreaUnbounded = new Camera.Area( 856 normalizedRegionUnbounded, meteringRect.getMeteringWeight()); 857 WeightedRectangle reportedMeteringRect = convertCameraAreaToActiveArrayRectangle( 858 activeArray, zoomData, normalizedAreaUnbounded, /*usePreviewCrop*/false); 859 reportedMetering = reportedMeteringRect.rect; 860 } 861 862 if (VERBOSE) { 863 Log.v(TAG, String.format( 864 "convertMeteringRectangleToLegacy - activeArray = %s, meteringRect = %s, " + 865 "previewCrop = %s, meteringArea = %s, previewMetering = %s, " + 866 "reportedMetering = %s, normalizedRegionUnbounded = %s", 867 activeArray, meteringRect, 868 previewCrop, stringFromArea(meteringArea), previewMetering, 869 reportedMetering, normalizedRegionUnbounded)); 870 } 871 872 return new MeteringData(meteringArea, previewMetering, reportedMetering); 873 } 874 875 /** 876 * Convert the normalized camera area from [-1000, 1000] coordinate space 877 * into the active array-based coordinate space. 878 * 879 * <p>Values out of range are clipped to be within the resulting (reported) crop 880 * region. It is possible to have values larger than the preview crop.</p> 881 * 882 * <p>Weights out of range of [0, 1000] are clipped to be within the range.</p> 883 * 884 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 885 * @param zoomData the calculated zoom data corresponding to this request 886 * @param area the normalized camera area 887 * 888 * @return the weighed rectangle in active array coordinate space, with the weight 889 */ convertCameraAreaToActiveArrayRectangle( Rect activeArray, ZoomData zoomData, Camera.Area area)890 public static WeightedRectangle convertCameraAreaToActiveArrayRectangle( 891 Rect activeArray, ZoomData zoomData, Camera.Area area) { 892 return convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, area, 893 /*usePreviewCrop*/true); 894 } 895 896 /** 897 * Convert an api1 face into an active-array based api2 face. 898 * 899 * <p>Out-of-ranges scores and ids will be clipped to be within range (with a warning).</p> 900 * 901 * @param face a non-{@code null} api1 face 902 * @param activeArraySize active array size of the sensor (e.g. max jpeg size) 903 * @param zoomData the calculated zoom data corresponding to this request 904 * 905 * @return a non-{@code null} api2 face 906 * 907 * @throws NullPointerException if the {@code face} was {@code null} 908 */ convertFaceFromLegacy(Camera.Face face, Rect activeArray, ZoomData zoomData)909 public static Face convertFaceFromLegacy(Camera.Face face, Rect activeArray, 910 ZoomData zoomData) { 911 checkNotNull(face, "face must not be null"); 912 913 Face api2Face; 914 915 Camera.Area fakeArea = new Camera.Area(face.rect, /*weight*/1); 916 917 WeightedRectangle faceRect = 918 convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea); 919 920 Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth; 921 if (leftEye != null && rightEye != null && mouth != null && leftEye.x != -2000 && 922 leftEye.y != -2000 && rightEye.x != -2000 && rightEye.y != -2000 && 923 mouth.x != -2000 && mouth.y != -2000) { 924 leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, 925 leftEye, /*usePreviewCrop*/true); 926 rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, 927 leftEye, /*usePreviewCrop*/true); 928 mouth = convertCameraPointToActiveArrayPoint(activeArray, zoomData, 929 leftEye, /*usePreviewCrop*/true); 930 931 api2Face = faceRect.toFace(face.id, leftEye, rightEye, mouth); 932 } else { 933 api2Face = faceRect.toFace(); 934 } 935 936 return api2Face; 937 } 938 convertCameraPointToActiveArrayPoint( Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop)939 private static Point convertCameraPointToActiveArrayPoint( 940 Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop) { 941 Rect pointedRect = new Rect(point.x, point.y, point.x, point.y); 942 Camera.Area pointedArea = new Area(pointedRect, /*weight*/1); 943 944 WeightedRectangle adjustedRect = 945 convertCameraAreaToActiveArrayRectangle(activeArray, 946 zoomData, pointedArea, usePreviewCrop); 947 948 Point transformedPoint = new Point(adjustedRect.rect.left, adjustedRect.rect.top); 949 950 return transformedPoint; 951 } 952 convertCameraAreaToActiveArrayRectangle( Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop)953 private static WeightedRectangle convertCameraAreaToActiveArrayRectangle( 954 Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) { 955 Rect previewCrop = zoomData.previewCrop; 956 Rect reportedCrop = zoomData.reportedCrop; 957 958 float scaleW = previewCrop.width() * 1.0f / 959 (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN); 960 float scaleH = previewCrop.height() * 1.0f / 961 (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN); 962 963 /* 964 * Calculate the reported metering region from the non-intersected normalized region 965 * by scaling and translating back into active array-relative coordinates. 966 */ 967 Matrix transform = new Matrix(); 968 969 // Move top left from (-1000, -1000) to (0, 0) 970 transform.setTranslate(/*dx*/NORMALIZED_RECTANGLE_MAX, /*dy*/NORMALIZED_RECTANGLE_MAX); 971 972 // Scale from [0, 2000] back into the preview rectangle 973 transform.postScale(scaleW, scaleH); 974 975 // Move the rect so that the [-1000,-1000] point ends up at the preview [left, top] 976 transform.postTranslate(previewCrop.left, previewCrop.top); 977 978 Rect cropToIntersectAgainst = usePreviewCrop ? previewCrop : reportedCrop; 979 980 // Now apply the transformation backwards to get the reported metering region 981 Rect reportedMetering = ParamsUtils.mapRect(transform, area.rect); 982 // Intersect it with the crop region, to avoid reporting out-of-bounds 983 // metering regions 984 if (!reportedMetering.intersect(cropToIntersectAgainst)) { 985 reportedMetering.set(RECTANGLE_EMPTY); 986 } 987 988 int weight = area.weight; 989 if (weight < MeteringRectangle.METERING_WEIGHT_MIN) { 990 Log.w(TAG, 991 "convertCameraAreaToMeteringRectangle - rectangle " 992 + stringFromArea(area) + " has too small weight, clip to 0"); 993 weight = 0; 994 } 995 996 return new WeightedRectangle(reportedMetering, area.weight); 997 } 998 999 ParameterUtils()1000 private ParameterUtils() { 1001 throw new AssertionError(); 1002 } 1003 } 1004