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 com.android.ex.camera2.portability; 18 19 import static android.hardware.camera2.CaptureRequest.*; 20 21 import android.graphics.Matrix; 22 import android.graphics.Rect; 23 import android.graphics.RectF; 24 import android.hardware.camera2.CameraAccessException; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.params.MeteringRectangle; 27 import android.location.Location; 28 import android.util.Range; 29 30 import com.android.ex.camera2.portability.CameraCapabilities.FlashMode; 31 import com.android.ex.camera2.portability.CameraCapabilities.FocusMode; 32 import com.android.ex.camera2.portability.CameraCapabilities.SceneMode; 33 import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance; 34 import com.android.ex.camera2.portability.debug.Log; 35 import com.android.ex.camera2.utils.Camera2RequestSettingsSet; 36 37 import java.util.List; 38 import java.util.Objects; 39 40 /** 41 * The subclass of {@link CameraSettings} for Android Camera 2 API. 42 */ 43 public 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 */ AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray, Size preview, Size photo)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 AndroidCamera2Settings(AndroidCamera2Settings other)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 copy()147 public CameraSettings copy() { 148 return new AndroidCamera2Settings(this); 149 } 150 queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault)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 flashModeFromRequest()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 setZoomRatio(float ratio)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 matchesTemplateDefault(Key<?> setting)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<Integer> 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 if (mExifThumbnailSize == null) { 232 // It doesn't matter if this is true or false since setting this 233 // to null in the request settings will use the default anyway. 234 return false; 235 } 236 android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE); 237 return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) || 238 (defaultThumbnailSize != null && 239 mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() && 240 mExifThumbnailSize.height() == defaultThumbnailSize.getHeight()); 241 } 242 Log.w(TAG, "Settings implementation checked default of unhandled option key"); 243 // Since this class isn't equipped to handle it, claim it matches the default to prevent 244 // updateRequestSettingOrForceToDefault from going with the user-provided preference 245 return true; 246 } 247 updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice)248 private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) { 249 mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice); 250 } 251 getRequestSettings()252 public Camera2RequestSettingsSet getRequestSettings() { 253 updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS, 254 legacyAreasToMeteringRectangles(mMeteringAreas)); 255 updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS, 256 legacyAreasToMeteringRectangles(mFocusAreas)); 257 updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE, 258 new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax)); 259 // TODO: mCurrentPreviewFormat 260 updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality); 261 // TODO: mCurrentPhotoFormat 262 mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle); 263 // TODO: mCurrentZoomIndex 264 updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION, 265 mExposureCompensationIndex); 266 updateRequestFlashMode(); 267 updateRequestFocusMode(); 268 updateRequestSceneMode(); 269 updateRequestWhiteBalance(); 270 updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE, 271 mVideoStabilizationEnabled ? 272 CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF); 273 // OIS shouldn't be on if software video stabilization is. 274 mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE, 275 mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF : 276 null); 277 updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked); 278 updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked); 279 // TODO: mRecordingHintEnabled 280 updateRequestGpsData(); 281 if (mExifThumbnailSize != null) { 282 updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, 283 new android.util.Size( 284 mExifThumbnailSize.width(), mExifThumbnailSize.height())); 285 } else { 286 updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, null); 287 } 288 289 return mRequestSettings; 290 } 291 legacyAreasToMeteringRectangles( List<android.hardware.Camera.Area> reference)292 private MeteringRectangle[] legacyAreasToMeteringRectangles( 293 List<android.hardware.Camera.Area> reference) { 294 MeteringRectangle[] transformed = null; 295 if (reference.size() > 0) { 296 transformed = new MeteringRectangle[reference.size()]; 297 for (int index = 0; index < reference.size(); ++index) { 298 android.hardware.Camera.Area source = reference.get(index); 299 Rect rectangle = source.rect; 300 301 // Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE). 302 // We're also going from preview image--relative to sensor active array--relative. 303 double oldLeft = (rectangle.left + 1000) / 2000.0; 304 double oldTop = (rectangle.top + 1000) / 2000.0; 305 double oldRight = (rectangle.right + 1000) / 2000.0; 306 double oldBottom = (rectangle.bottom + 1000) / 2000.0; 307 int left = mCropRectangle.left + toIntConstrained( 308 mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1); 309 int top = mCropRectangle.top + toIntConstrained( 310 mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1); 311 int right = mCropRectangle.left + toIntConstrained( 312 mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1); 313 int bottom = mCropRectangle.top + toIntConstrained( 314 mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1); 315 transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top, 316 source.weight); 317 } 318 } 319 return transformed; 320 } 321 toIntConstrained(double original, int min, int max)322 private int toIntConstrained(double original, int min, int max) { 323 original = Math.max(original, min); 324 original = Math.min(original, max); 325 return (int) original; 326 } 327 updateRequestFlashMode()328 private void updateRequestFlashMode() { 329 Integer aeMode = null; 330 Integer flashMode = null; 331 if (mCurrentFlashMode != null) { 332 switch (mCurrentFlashMode) { 333 case AUTO: { 334 aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH; 335 break; 336 } 337 case OFF: { 338 aeMode = CONTROL_AE_MODE_ON; 339 flashMode = FLASH_MODE_OFF; 340 break; 341 } 342 case ON: { 343 aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH; 344 flashMode = FLASH_MODE_SINGLE; 345 break; 346 } 347 case TORCH: { 348 flashMode = FLASH_MODE_TORCH; 349 break; 350 } 351 case RED_EYE: { 352 aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE; 353 break; 354 } 355 default: { 356 Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode); 357 break; 358 } 359 } 360 } 361 mRequestSettings.set(CONTROL_AE_MODE, aeMode); 362 mRequestSettings.set(FLASH_MODE, flashMode); 363 } 364 updateRequestFocusMode()365 private void updateRequestFocusMode() { 366 Integer mode = null; 367 if (mCurrentFocusMode != null) { 368 switch (mCurrentFocusMode) { 369 case AUTO: { 370 mode = CONTROL_AF_MODE_AUTO; 371 break; 372 } 373 case CONTINUOUS_PICTURE: { 374 mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE; 375 break; 376 } 377 case CONTINUOUS_VIDEO: { 378 mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO; 379 break; 380 } 381 case EXTENDED_DOF: { 382 mode = CONTROL_AF_MODE_EDOF; 383 break; 384 } 385 case FIXED: { 386 mode = CONTROL_AF_MODE_OFF; 387 break; 388 } 389 // TODO: We cannot support INFINITY 390 case MACRO: { 391 mode = CONTROL_AF_MODE_MACRO; 392 break; 393 } 394 default: { 395 Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode); 396 break; 397 } 398 } 399 } 400 mRequestSettings.set(CONTROL_AF_MODE, mode); 401 } 402 updateRequestSceneMode()403 private void updateRequestSceneMode() { 404 Integer mode = null; 405 if (mCurrentSceneMode != null) { 406 switch (mCurrentSceneMode) { 407 case AUTO: { 408 mode = CONTROL_SCENE_MODE_DISABLED; 409 break; 410 } 411 case ACTION: { 412 mode = CONTROL_SCENE_MODE_ACTION; 413 break; 414 } 415 case BARCODE: { 416 mode = CONTROL_SCENE_MODE_BARCODE; 417 break; 418 } 419 case BEACH: { 420 mode = CONTROL_SCENE_MODE_BEACH; 421 break; 422 } 423 case CANDLELIGHT: { 424 mode = CONTROL_SCENE_MODE_CANDLELIGHT; 425 break; 426 } 427 case FIREWORKS: { 428 mode = CONTROL_SCENE_MODE_FIREWORKS; 429 break; 430 } 431 case HDR: { 432 mode = CONTROL_SCENE_MODE_HDR; 433 break; 434 } 435 case LANDSCAPE: { 436 mode = CONTROL_SCENE_MODE_LANDSCAPE; 437 break; 438 } 439 case NIGHT: { 440 mode = CONTROL_SCENE_MODE_NIGHT; 441 break; 442 } 443 // TODO: We cannot support NIGHT_PORTRAIT 444 case PARTY: { 445 mode = CONTROL_SCENE_MODE_PARTY; 446 break; 447 } 448 case PORTRAIT: { 449 mode = CONTROL_SCENE_MODE_PORTRAIT; 450 break; 451 } 452 case SNOW: { 453 mode = CONTROL_SCENE_MODE_SNOW; 454 break; 455 } 456 case SPORTS: { 457 mode = CONTROL_SCENE_MODE_SPORTS; 458 break; 459 } 460 case STEADYPHOTO: { 461 mode = CONTROL_SCENE_MODE_STEADYPHOTO; 462 break; 463 } 464 case SUNSET: { 465 mode = CONTROL_SCENE_MODE_SUNSET; 466 break; 467 } 468 case THEATRE: { 469 mode = CONTROL_SCENE_MODE_THEATRE; 470 break; 471 } 472 default: { 473 Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode); 474 break; 475 } 476 } 477 } 478 mRequestSettings.set(CONTROL_SCENE_MODE, mode); 479 } 480 updateRequestWhiteBalance()481 private void updateRequestWhiteBalance() { 482 Integer mode = null; 483 if (mWhiteBalance != null) { 484 switch (mWhiteBalance) { 485 case AUTO: { 486 mode = CONTROL_AWB_MODE_AUTO; 487 break; 488 } 489 case CLOUDY_DAYLIGHT: { 490 mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT; 491 break; 492 } 493 case DAYLIGHT: { 494 mode = CONTROL_AWB_MODE_DAYLIGHT; 495 break; 496 } 497 case FLUORESCENT: { 498 mode = CONTROL_AWB_MODE_FLUORESCENT; 499 break; 500 } 501 case INCANDESCENT: { 502 mode = CONTROL_AWB_MODE_INCANDESCENT; 503 break; 504 } 505 case SHADE: { 506 mode = CONTROL_AWB_MODE_SHADE; 507 break; 508 } 509 case TWILIGHT: { 510 mode = CONTROL_AWB_MODE_TWILIGHT; 511 break; 512 } 513 case WARM_FLUORESCENT: { 514 mode = CONTROL_AWB_MODE_WARM_FLUORESCENT; 515 break; 516 } 517 default: { 518 Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance); 519 break; 520 } 521 } 522 } 523 mRequestSettings.set(CONTROL_AWB_MODE, mode); 524 } 525 updateRequestGpsData()526 private void updateRequestGpsData() { 527 if (mGpsData == null || mGpsData.processingMethod == null) { 528 // It's a hack since we always use GPS time stamp but does 529 // not use other fields sometimes. Setting processing 530 // method to null means the other fields should not be used. 531 mRequestSettings.set(JPEG_GPS_LOCATION, null); 532 } else { 533 Location location = new Location(mGpsData.processingMethod); 534 location.setTime(mGpsData.timeStamp); 535 location.setAltitude(mGpsData.altitude); 536 location.setLatitude(mGpsData.latitude); 537 location.setLongitude(mGpsData.longitude); 538 mRequestSettings.set(JPEG_GPS_LOCATION, location); 539 } 540 } 541 542 /** 543 * Calculate the effective crop rectangle for this preview viewport; 544 * assumes the preview is centered to the sensor and scaled to fit across one of the dimensions 545 * without skewing. 546 * 547 * <p>Assumes the zoom level of the provided desired crop rectangle.</p> 548 * 549 * @param requestedCrop Desired crop rectangle, in active array space. 550 * @param previewSize Size of the preview buffer render target, in pixels (not in sensor space). 551 * @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in 552 * sensor space. 553 * 554 * @throws NullPointerException 555 * If any of the args were {@code null}. 556 */ effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize)557 private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) { 558 float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height(); 559 float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height(); 560 561 float cropHeight, cropWidth; 562 if (aspectRatioPreview < aspectRatioArray) { 563 // The new width must be smaller than the height, so scale the width by AR 564 cropHeight = requestedCrop.height(); 565 cropWidth = cropHeight * aspectRatioPreview; 566 } else { 567 // The new height must be smaller (or equal) than the width, so scale the height by AR 568 cropWidth = requestedCrop.width(); 569 cropHeight = cropWidth / aspectRatioPreview; 570 } 571 572 Matrix translateMatrix = new Matrix(); 573 RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight); 574 575 // Now center the crop rectangle so its center is in the center of the active array 576 translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY()); 577 translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); 578 579 translateMatrix.mapRect(/*inout*/cropRect); 580 581 // Round the rect corners towards the nearest integer values 582 Rect result = new Rect(); 583 cropRect.roundOut(result); 584 return result; 585 } 586 } 587