AndroidCamera2Settings.java revision 2569329d6cff25bfe9941df539df14a0aeb4c4f4
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 com.android.ex.camera2.portability; 18 19import static android.hardware.camera2.CaptureRequest.*; 20 21import android.graphics.Matrix; 22import android.graphics.Rect; 23import android.graphics.RectF; 24import android.hardware.camera2.CameraAccessException; 25import android.hardware.camera2.CameraDevice; 26import android.hardware.camera2.params.MeteringRectangle; 27import android.location.Location; 28import android.util.Range; 29 30import com.android.ex.camera2.portability.CameraCapabilities.FlashMode; 31import com.android.ex.camera2.portability.CameraCapabilities.FocusMode; 32import com.android.ex.camera2.portability.CameraCapabilities.SceneMode; 33import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance; 34import com.android.ex.camera2.portability.debug.Log; 35import com.android.ex.camera2.utils.Camera2RequestSettingsSet; 36 37import java.util.List; 38import java.util.Objects; 39 40/** 41 * The subclass of {@link CameraSettings} for Android Camera 2 API. 42 */ 43public class AndroidCamera2Settings extends CameraSettings { 44 private static final Log.Tag TAG = new Log.Tag("AndCam2Set"); 45 46 private final Builder mTemplateSettings; 47 private final Camera2RequestSettingsSet mRequestSettings; 48 /** Sensor's active array bounds. */ 49 private final Rect mActiveArray; 50 /** Crop rectangle for digital zoom (measured WRT the active array). */ 51 private final Rect mCropRectangle; 52 /** Bounds of visible preview portion (measured WRT the active array). */ 53 private Rect mVisiblePreviewRectangle; 54 55 /** 56 * Create a settings representation that answers queries of unspecified 57 * options in the same way as the provided template would. 58 * 59 * <p>The default settings provided by the given template are only ever used 60 * for reporting back to the client app (i.e. when it queries an option 61 * it didn't explicitly set first). {@link Camera2RequestSettingsSet}s 62 * generated by an instance of this class will have any settings not 63 * modified using one of that instance's mutators forced to default, so that 64 * their effective values when submitting a capture request will be those of 65 * the template that is provided to the camera framework at that time.</p> 66 * 67 * @param camera Device from which to draw default settings 68 * (non-{@code null}). 69 * @param template Specific template to use for the defaults. 70 * @param activeArray Boundary coordinates of the sensor's active array 71 * (non-{@code null}). 72 * @param preview Dimensions of preview streams. 73 * @param photo Dimensions of captured images. 74 * 75 * @throws IllegalArgumentException If {@code camera} or {@code activeArray} 76 * is {@code null}. 77 * @throws CameraAccessException Upon internal framework/driver failure. 78 */ 79 public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray, 80 Size preview, Size photo) throws CameraAccessException { 81 if (camera == null) { 82 throw new NullPointerException("camera must not be null"); 83 } 84 if (activeArray == null) { 85 throw new NullPointerException("activeArray must not be null"); 86 } 87 88 mTemplateSettings = camera.createCaptureRequest(template); 89 mRequestSettings = new Camera2RequestSettingsSet(); 90 mActiveArray = activeArray; 91 mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height()); 92 93 mSizesLocked = false; 94 95 Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); 96 if (previewFpsRange != null) { 97 setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper()); 98 } 99 setPreviewSize(preview); 100 // TODO: mCurrentPreviewFormat 101 setPhotoSize(photo); 102 mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0); 103 // TODO: mCurrentPhotoFormat 104 // NB: We're assuming that templates won't be zoomed in by default. 105 mCurrentZoomRatio = CameraCapabilities.ZOOM_RATIO_UNZOOMED; 106 // TODO: mCurrentZoomIndex 107 mExposureCompensationIndex = 108 queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0); 109 110 mCurrentFlashMode = flashModeFromRequest(); 111 Integer currentFocusMode = mTemplateSettings.get(CONTROL_AF_MODE); 112 if (currentFocusMode != null) { 113 mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt(currentFocusMode); 114 } 115 Integer currentSceneMode = mTemplateSettings.get(CONTROL_SCENE_MODE); 116 if (currentSceneMode != null) { 117 mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt(currentSceneMode); 118 } 119 Integer whiteBalance = mTemplateSettings.get(CONTROL_AWB_MODE); 120 if (whiteBalance != null) { 121 mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt(whiteBalance); 122 } 123 124 mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp( 125 CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) == 126 CONTROL_VIDEO_STABILIZATION_MODE_ON; 127 mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false); 128 mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false); 129 // TODO: mRecordingHintEnabled 130 // TODO: mGpsData 131 android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); 132 if (exifThumbnailSize != null) { 133 mExifThumbnailSize = 134 new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight()); 135 } 136 } 137 138 public AndroidCamera2Settings(AndroidCamera2Settings other) { 139 super(other); 140 mTemplateSettings = other.mTemplateSettings; 141 mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings); 142 mActiveArray = other.mActiveArray; 143 mCropRectangle = new Rect(other.mCropRectangle); 144 } 145 146 @Override 147 public CameraSettings copy() { 148 return new AndroidCamera2Settings(this); 149 } 150 151 private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) { 152 T val = mTemplateSettings.get(key); 153 if (val != null) { 154 return val; 155 } else { 156 // Spoof the default so matchesTemplateDefault excludes this key from generated sets. 157 // This approach beats a simple sentinel because it provides basic boolean support. 158 mTemplateSettings.set(key, defaultDefault); 159 return defaultDefault; 160 } 161 } 162 163 private FlashMode flashModeFromRequest() { 164 Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE); 165 if (autoExposure != null) { 166 switch (autoExposure) { 167 case CONTROL_AE_MODE_ON: 168 return FlashMode.OFF; 169 case CONTROL_AE_MODE_ON_AUTO_FLASH: 170 return FlashMode.AUTO; 171 case CONTROL_AE_MODE_ON_ALWAYS_FLASH: { 172 if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) { 173 return FlashMode.TORCH; 174 } else { 175 return FlashMode.ON; 176 } 177 } 178 case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: 179 return FlashMode.RED_EYE; 180 } 181 } 182 return null; 183 } 184 185 @Override 186 public void setZoomRatio(float ratio) { 187 super.setZoomRatio(ratio); 188 189 // Compute the crop rectangle to be passed to the framework 190 mCropRectangle.set(0, 0, 191 toIntConstrained( 192 mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()), 193 toIntConstrained( 194 mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height())); 195 mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2, 196 (mActiveArray.height() - mCropRectangle.height()) / 2); 197 198 // Compute the effective crop rectangle to be used for computing focus/metering coordinates 199 mVisiblePreviewRectangle = 200 effectiveCropRectFromRequested(mCropRectangle, mCurrentPreviewSize); 201 } 202 203 private boolean matchesTemplateDefault(Key<?> setting) { 204 if (setting == CONTROL_AE_REGIONS) { 205 return mMeteringAreas.size() == 0; 206 } else if (setting == CONTROL_AF_REGIONS) { 207 return mFocusAreas.size() == 0; 208 } else if (setting == CONTROL_AE_TARGET_FPS_RANGE) { 209 Range defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE); 210 return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) || 211 (defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() && 212 mPreviewFpsRangeMax == defaultFpsRange.getUpper()); 213 } else if (setting == JPEG_QUALITY) { 214 return Objects.equals(mJpegCompressQuality, 215 mTemplateSettings.get(JPEG_QUALITY)); 216 } else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) { 217 return Objects.equals(mExposureCompensationIndex, 218 mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION)); 219 } else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) { 220 Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE); 221 return (videoStabilization != null && 222 (mVideoStabilizationEnabled && videoStabilization == 223 CONTROL_VIDEO_STABILIZATION_MODE_ON) || 224 (!mVideoStabilizationEnabled && videoStabilization == 225 CONTROL_VIDEO_STABILIZATION_MODE_OFF)); 226 } else if (setting == CONTROL_AE_LOCK) { 227 return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK)); 228 } else if (setting == CONTROL_AWB_LOCK) { 229 return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK)); 230 } else if (setting == JPEG_THUMBNAIL_SIZE) { 231 android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); 232 return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) || 233 (defaultThumbnailSize != null && 234 mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() && 235 mExifThumbnailSize.height() == defaultThumbnailSize.getHeight()); 236 } 237 Log.w(TAG, "Settings implementation checked default of unhandled option key"); 238 // Since this class isn't equipped to handle it, claim it matches the default to prevent 239 // updateRequestSettingOrForceToDefault from going with the user-provided preference 240 return true; 241 } 242 243 private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) { 244 mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice); 245 } 246 247 public Camera2RequestSettingsSet getRequestSettings() { 248 updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS, 249 legacyAreasToMeteringRectangles(mMeteringAreas)); 250 updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS, 251 legacyAreasToMeteringRectangles(mFocusAreas)); 252 updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE, 253 new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax)); 254 // TODO: mCurrentPreviewFormat 255 updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality); 256 // TODO: mCurrentPhotoFormat 257 mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle); 258 // TODO: mCurrentZoomIndex 259 updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION, 260 mExposureCompensationIndex); 261 updateRequestFlashMode(); 262 updateRequestFocusMode(); 263 updateRequestSceneMode(); 264 updateRequestWhiteBalance(); 265 updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE, 266 mVideoStabilizationEnabled ? 267 CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF); 268 // OIS shouldn't be on if software video stabilization is. 269 mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE, 270 mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF : 271 null); 272 updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked); 273 updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked); 274 // TODO: mRecordingHintEnabled 275 updateRequestGpsData(); 276 updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, 277 new android.util.Size( 278 mExifThumbnailSize.width(), mExifThumbnailSize.height())); 279 280 return mRequestSettings; 281 } 282 283 private MeteringRectangle[] legacyAreasToMeteringRectangles( 284 List<android.hardware.Camera.Area> reference) { 285 MeteringRectangle[] transformed = null; 286 if (reference.size() > 0) { 287 transformed = new MeteringRectangle[reference.size()]; 288 for (int index = 0; index < reference.size(); ++index) { 289 android.hardware.Camera.Area source = reference.get(index); 290 Rect rectangle = source.rect; 291 292 // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE). 293 // We're also going from preview image--relative to sensor active array--relative. 294 double oldLeft = (rectangle.left + 1000) / 2000.0; 295 double oldTop = (rectangle.top + 1000) / 2000.0; 296 double oldRight = (rectangle.right + 1000) / 2000.0; 297 double oldBottom = (rectangle.bottom + 1000) / 2000.0; 298 int left = mCropRectangle.left + toIntConstrained( 299 mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1); 300 int top = mCropRectangle.top + toIntConstrained( 301 mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1); 302 int right = mCropRectangle.left + toIntConstrained( 303 mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1); 304 int bottom = mCropRectangle.top + toIntConstrained( 305 mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1); 306 transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top, 307 source.weight); 308 } 309 } 310 return transformed; 311 } 312 313 private int toIntConstrained(double original, int min, int max) { 314 original = Math.max(original, min); 315 original = Math.min(original, max); 316 return (int) original; 317 } 318 319 private void updateRequestFlashMode() { 320 Integer aeMode = null; 321 Integer flashMode = null; 322 if (mCurrentFlashMode != null) { 323 switch (mCurrentFlashMode) { 324 case AUTO: { 325 aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH; 326 break; 327 } 328 case OFF: { 329 aeMode = CONTROL_AE_MODE_ON; 330 flashMode = FLASH_MODE_OFF; 331 break; 332 } 333 case ON: { 334 aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH; 335 flashMode = FLASH_MODE_SINGLE; 336 break; 337 } 338 case TORCH: { 339 flashMode = FLASH_MODE_TORCH; 340 break; 341 } 342 case RED_EYE: { 343 aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE; 344 break; 345 } 346 default: { 347 Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode); 348 break; 349 } 350 } 351 } 352 mRequestSettings.set(CONTROL_AE_MODE, aeMode); 353 mRequestSettings.set(FLASH_MODE, flashMode); 354 } 355 356 private void updateRequestFocusMode() { 357 Integer mode = null; 358 if (mCurrentFocusMode != null) { 359 switch (mCurrentFocusMode) { 360 case AUTO: { 361 mode = CONTROL_AF_MODE_AUTO; 362 break; 363 } 364 case CONTINUOUS_PICTURE: { 365 mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE; 366 break; 367 } 368 case CONTINUOUS_VIDEO: { 369 mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO; 370 break; 371 } 372 case EXTENDED_DOF: { 373 mode = CONTROL_AF_MODE_EDOF; 374 break; 375 } 376 case FIXED: { 377 mode = CONTROL_AF_MODE_OFF; 378 break; 379 } 380 // TODO: We cannot support INFINITY 381 case MACRO: { 382 mode = CONTROL_AF_MODE_MACRO; 383 break; 384 } 385 default: { 386 Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode); 387 break; 388 } 389 } 390 } 391 mRequestSettings.set(CONTROL_AF_MODE, mode); 392 } 393 394 private void updateRequestSceneMode() { 395 Integer mode = null; 396 if (mCurrentSceneMode != null) { 397 switch (mCurrentSceneMode) { 398 case AUTO: { 399 mode = CONTROL_SCENE_MODE_DISABLED; 400 break; 401 } 402 case ACTION: { 403 mode = CONTROL_SCENE_MODE_ACTION; 404 break; 405 } 406 case BARCODE: { 407 mode = CONTROL_SCENE_MODE_BARCODE; 408 break; 409 } 410 case BEACH: { 411 mode = CONTROL_SCENE_MODE_BEACH; 412 break; 413 } 414 case CANDLELIGHT: { 415 mode = CONTROL_SCENE_MODE_CANDLELIGHT; 416 break; 417 } 418 case FIREWORKS: { 419 mode = CONTROL_SCENE_MODE_FIREWORKS; 420 break; 421 } 422 // TODO: We cannot support HDR 423 case LANDSCAPE: { 424 mode = CONTROL_SCENE_MODE_LANDSCAPE; 425 break; 426 } 427 case NIGHT: { 428 mode = CONTROL_SCENE_MODE_NIGHT; 429 break; 430 } 431 // TODO: We cannot support NIGHT_PORTRAIT 432 case PARTY: { 433 mode = CONTROL_SCENE_MODE_PARTY; 434 break; 435 } 436 case PORTRAIT: { 437 mode = CONTROL_SCENE_MODE_PORTRAIT; 438 break; 439 } 440 case SNOW: { 441 mode = CONTROL_SCENE_MODE_SNOW; 442 break; 443 } 444 case SPORTS: { 445 mode = CONTROL_SCENE_MODE_SPORTS; 446 break; 447 } 448 case STEADYPHOTO: { 449 mode = CONTROL_SCENE_MODE_STEADYPHOTO; 450 break; 451 } 452 case SUNSET: { 453 mode = CONTROL_SCENE_MODE_SUNSET; 454 break; 455 } 456 case THEATRE: { 457 mode = CONTROL_SCENE_MODE_THEATRE; 458 break; 459 } 460 default: { 461 Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode); 462 break; 463 } 464 } 465 } 466 mRequestSettings.set(CONTROL_SCENE_MODE, mode); 467 } 468 469 private void updateRequestWhiteBalance() { 470 Integer mode = null; 471 if (mWhiteBalance != null) { 472 switch (mWhiteBalance) { 473 case AUTO: { 474 mode = CONTROL_AWB_MODE_AUTO; 475 break; 476 } 477 case CLOUDY_DAYLIGHT: { 478 mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT; 479 break; 480 } 481 case DAYLIGHT: { 482 mode = CONTROL_AWB_MODE_DAYLIGHT; 483 break; 484 } 485 case FLUORESCENT: { 486 mode = CONTROL_AWB_MODE_FLUORESCENT; 487 break; 488 } 489 case INCANDESCENT: { 490 mode = CONTROL_AWB_MODE_INCANDESCENT; 491 break; 492 } 493 case SHADE: { 494 mode = CONTROL_AWB_MODE_SHADE; 495 break; 496 } 497 case TWILIGHT: { 498 mode = CONTROL_AWB_MODE_TWILIGHT; 499 break; 500 } 501 case WARM_FLUORESCENT: { 502 mode = CONTROL_AWB_MODE_WARM_FLUORESCENT; 503 break; 504 } 505 default: { 506 Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance); 507 break; 508 } 509 } 510 } 511 mRequestSettings.set(CONTROL_AWB_MODE, mode); 512 } 513 514 private void updateRequestGpsData() { 515 if (mGpsData == null || mGpsData.processingMethod == null) { 516 // It's a hack since we always use GPS time stamp but does 517 // not use other fields sometimes. Setting processing 518 // method to null means the other fields should not be used. 519 mRequestSettings.set(JPEG_GPS_LOCATION, null); 520 } else { 521 Location location = new Location(mGpsData.processingMethod); 522 location.setTime(mGpsData.timeStamp); 523 location.setAltitude(mGpsData.altitude); 524 location.setLatitude(mGpsData.latitude); 525 location.setLongitude(mGpsData.longitude); 526 mRequestSettings.set(JPEG_GPS_LOCATION, location); 527 } 528 } 529 530 /** 531 * Calculate the effective crop rectangle for this preview viewport; 532 * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions 533 * without skewing. 534 * 535 * <p>Assumes the zoom level of the provided desired crop rectangle.</p> 536 * 537 * @param requestedCrop Desired crop rectangle, in active array space. 538 * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space). 539 * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in 540 * sensor space. 541 * 542 * @throws NullPointerException 543 * If any of the args were {@code null}. 544 */ 545 private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) { 546 float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height(); 547 float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height(); 548 549 float cropHeight, cropWidth; 550 if (aspectRatioPreview < aspectRatioArray) { 551 // The new width must be smaller than the height, so scale the width by AR 552 cropHeight = requestedCrop.height(); 553 cropWidth = cropHeight * aspectRatioPreview; 554 } else { 555 // The new height must be smaller (or equal) than the width, so scale the height by AR 556 cropWidth = requestedCrop.width(); 557 cropHeight = cropWidth / aspectRatioPreview; 558 } 559 560 Matrix translateMatrix = new Matrix(); 561 RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight); 562 563 // Now center the crop rectangle so its center is in the center of the active array 564 translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY()); 565 translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); 566 567 translateMatrix.mapRect(/*inout*/cropRect); 568 569 // Round the rect corners towards the nearest integer values 570 Rect result = new Rect(); 571 cropRect.roundOut(result); 572 return result; 573 } 574} 575