/*
|
* Copyright (C) 2014 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
package com.android.ex.camera2.portability;
|
|
import static android.hardware.camera2.CaptureRequest.*;
|
|
import android.graphics.Matrix;
|
import android.graphics.Rect;
|
import android.graphics.RectF;
|
import android.hardware.camera2.CameraAccessException;
|
import android.hardware.camera2.CameraDevice;
|
import android.hardware.camera2.params.MeteringRectangle;
|
import android.location.Location;
|
import android.util.Range;
|
|
import com.android.ex.camera2.portability.CameraCapabilities.FlashMode;
|
import com.android.ex.camera2.portability.CameraCapabilities.FocusMode;
|
import com.android.ex.camera2.portability.CameraCapabilities.SceneMode;
|
import com.android.ex.camera2.portability.CameraCapabilities.WhiteBalance;
|
import com.android.ex.camera2.portability.debug.Log;
|
import com.android.ex.camera2.utils.Camera2RequestSettingsSet;
|
|
import java.util.List;
|
import java.util.Objects;
|
|
/**
|
* The subclass of {@link CameraSettings} for Android Camera 2 API.
|
*/
|
public class AndroidCamera2Settings extends CameraSettings {
|
private static final Log.Tag TAG = new Log.Tag("AndCam2Set");
|
|
private final Builder mTemplateSettings;
|
private final Camera2RequestSettingsSet mRequestSettings;
|
/** Sensor's active array bounds. */
|
private final Rect mActiveArray;
|
/** Crop rectangle for digital zoom (measured WRT the active array). */
|
private final Rect mCropRectangle;
|
/** Bounds of visible preview portion (measured WRT the active array). */
|
private Rect mVisiblePreviewRectangle;
|
|
/**
|
* Create a settings representation that answers queries of unspecified
|
* options in the same way as the provided template would.
|
*
|
* <p>The default settings provided by the given template are only ever used
|
* for reporting back to the client app (i.e. when it queries an option
|
* it didn't explicitly set first). {@link Camera2RequestSettingsSet}s
|
* generated by an instance of this class will have any settings not
|
* modified using one of that instance's mutators forced to default, so that
|
* their effective values when submitting a capture request will be those of
|
* the template that is provided to the camera framework at that time.</p>
|
*
|
* @param camera Device from which to draw default settings
|
* (non-{@code null}).
|
* @param template Specific template to use for the defaults.
|
* @param activeArray Boundary coordinates of the sensor's active array
|
* (non-{@code null}).
|
* @param preview Dimensions of preview streams.
|
* @param photo Dimensions of captured images.
|
*
|
* @throws IllegalArgumentException If {@code camera} or {@code activeArray}
|
* is {@code null}.
|
* @throws CameraAccessException Upon internal framework/driver failure.
|
*/
|
public AndroidCamera2Settings(CameraDevice camera, int template, Rect activeArray,
|
Size preview, Size photo) throws CameraAccessException {
|
if (camera == null) {
|
throw new NullPointerException("camera must not be null");
|
}
|
if (activeArray == null) {
|
throw new NullPointerException("activeArray must not be null");
|
}
|
|
mTemplateSettings = camera.createCaptureRequest(template);
|
mRequestSettings = new Camera2RequestSettingsSet();
|
mActiveArray = activeArray;
|
mCropRectangle = new Rect(0, 0, activeArray.width(), activeArray.height());
|
|
mSizesLocked = false;
|
|
Range<Integer> previewFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
|
if (previewFpsRange != null) {
|
setPreviewFpsRange(previewFpsRange.getLower(), previewFpsRange.getUpper());
|
}
|
setPreviewSize(preview);
|
// TODO: mCurrentPreviewFormat
|
setPhotoSize(photo);
|
mJpegCompressQuality = queryTemplateDefaultOrMakeOneUp(JPEG_QUALITY, (byte) 0);
|
// TODO: mCurrentPhotoFormat
|
// NB: We're assuming that templates won't be zoomed in by default.
|
mCurrentZoomRatio = CameraCapabilities.ZOOM_RATIO_UNZOOMED;
|
// TODO: mCurrentZoomIndex
|
mExposureCompensationIndex =
|
queryTemplateDefaultOrMakeOneUp(CONTROL_AE_EXPOSURE_COMPENSATION, 0);
|
|
mCurrentFlashMode = flashModeFromRequest();
|
Integer currentFocusMode = mTemplateSettings.get(CONTROL_AF_MODE);
|
if (currentFocusMode != null) {
|
mCurrentFocusMode = AndroidCamera2Capabilities.focusModeFromInt(currentFocusMode);
|
}
|
Integer currentSceneMode = mTemplateSettings.get(CONTROL_SCENE_MODE);
|
if (currentSceneMode != null) {
|
mCurrentSceneMode = AndroidCamera2Capabilities.sceneModeFromInt(currentSceneMode);
|
}
|
Integer whiteBalance = mTemplateSettings.get(CONTROL_AWB_MODE);
|
if (whiteBalance != null) {
|
mWhiteBalance = AndroidCamera2Capabilities.whiteBalanceFromInt(whiteBalance);
|
}
|
|
mVideoStabilizationEnabled = queryTemplateDefaultOrMakeOneUp(
|
CONTROL_VIDEO_STABILIZATION_MODE, CONTROL_VIDEO_STABILIZATION_MODE_OFF) ==
|
CONTROL_VIDEO_STABILIZATION_MODE_ON;
|
mAutoExposureLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AE_LOCK, false);
|
mAutoWhiteBalanceLocked = queryTemplateDefaultOrMakeOneUp(CONTROL_AWB_LOCK, false);
|
// TODO: mRecordingHintEnabled
|
// TODO: mGpsData
|
android.util.Size exifThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
|
if (exifThumbnailSize != null) {
|
mExifThumbnailSize =
|
new Size(exifThumbnailSize.getWidth(), exifThumbnailSize.getHeight());
|
}
|
}
|
|
public AndroidCamera2Settings(AndroidCamera2Settings other) {
|
super(other);
|
mTemplateSettings = other.mTemplateSettings;
|
mRequestSettings = new Camera2RequestSettingsSet(other.mRequestSettings);
|
mActiveArray = other.mActiveArray;
|
mCropRectangle = new Rect(other.mCropRectangle);
|
}
|
|
@Override
|
public CameraSettings copy() {
|
return new AndroidCamera2Settings(this);
|
}
|
|
private <T> T queryTemplateDefaultOrMakeOneUp(Key<T> key, T defaultDefault) {
|
T val = mTemplateSettings.get(key);
|
if (val != null) {
|
return val;
|
} else {
|
// Spoof the default so matchesTemplateDefault excludes this key from generated sets.
|
// This approach beats a simple sentinel because it provides basic boolean support.
|
mTemplateSettings.set(key, defaultDefault);
|
return defaultDefault;
|
}
|
}
|
|
private FlashMode flashModeFromRequest() {
|
Integer autoExposure = mTemplateSettings.get(CONTROL_AE_MODE);
|
if (autoExposure != null) {
|
switch (autoExposure) {
|
case CONTROL_AE_MODE_ON:
|
return FlashMode.OFF;
|
case CONTROL_AE_MODE_ON_AUTO_FLASH:
|
return FlashMode.AUTO;
|
case CONTROL_AE_MODE_ON_ALWAYS_FLASH: {
|
if (mTemplateSettings.get(FLASH_MODE) == FLASH_MODE_TORCH) {
|
return FlashMode.TORCH;
|
} else {
|
return FlashMode.ON;
|
}
|
}
|
case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
|
return FlashMode.RED_EYE;
|
}
|
}
|
return null;
|
}
|
|
@Override
|
public void setZoomRatio(float ratio) {
|
super.setZoomRatio(ratio);
|
|
// Compute the crop rectangle to be passed to the framework
|
mCropRectangle.set(0, 0,
|
toIntConstrained(
|
mActiveArray.width() / mCurrentZoomRatio, 0, mActiveArray.width()),
|
toIntConstrained(
|
mActiveArray.height() / mCurrentZoomRatio, 0, mActiveArray.height()));
|
mCropRectangle.offsetTo((mActiveArray.width() - mCropRectangle.width()) / 2,
|
(mActiveArray.height() - mCropRectangle.height()) / 2);
|
|
// Compute the effective crop rectangle to be used for computing focus/metering coordinates
|
mVisiblePreviewRectangle =
|
effectiveCropRectFromRequested(mCropRectangle, mCurrentPreviewSize);
|
}
|
|
private boolean matchesTemplateDefault(Key<?> setting) {
|
if (setting == CONTROL_AE_REGIONS) {
|
return mMeteringAreas.size() == 0;
|
} else if (setting == CONTROL_AF_REGIONS) {
|
return mFocusAreas.size() == 0;
|
} else if (setting == CONTROL_AE_TARGET_FPS_RANGE) {
|
Range<Integer> defaultFpsRange = mTemplateSettings.get(CONTROL_AE_TARGET_FPS_RANGE);
|
return (mPreviewFpsRangeMin == 0 && mPreviewFpsRangeMax == 0) ||
|
(defaultFpsRange != null && mPreviewFpsRangeMin == defaultFpsRange.getLower() &&
|
mPreviewFpsRangeMax == defaultFpsRange.getUpper());
|
} else if (setting == JPEG_QUALITY) {
|
return Objects.equals(mJpegCompressQuality,
|
mTemplateSettings.get(JPEG_QUALITY));
|
} else if (setting == CONTROL_AE_EXPOSURE_COMPENSATION) {
|
return Objects.equals(mExposureCompensationIndex,
|
mTemplateSettings.get(CONTROL_AE_EXPOSURE_COMPENSATION));
|
} else if (setting == CONTROL_VIDEO_STABILIZATION_MODE) {
|
Integer videoStabilization = mTemplateSettings.get(CONTROL_VIDEO_STABILIZATION_MODE);
|
return (videoStabilization != null &&
|
(mVideoStabilizationEnabled && videoStabilization ==
|
CONTROL_VIDEO_STABILIZATION_MODE_ON) ||
|
(!mVideoStabilizationEnabled && videoStabilization ==
|
CONTROL_VIDEO_STABILIZATION_MODE_OFF));
|
} else if (setting == CONTROL_AE_LOCK) {
|
return Objects.equals(mAutoExposureLocked, mTemplateSettings.get(CONTROL_AE_LOCK));
|
} else if (setting == CONTROL_AWB_LOCK) {
|
return Objects.equals(mAutoWhiteBalanceLocked, mTemplateSettings.get(CONTROL_AWB_LOCK));
|
} else if (setting == JPEG_THUMBNAIL_SIZE) {
|
if (mExifThumbnailSize == null) {
|
// It doesn't matter if this is true or false since setting this
|
// to null in the request settings will use the default anyway.
|
return false;
|
}
|
android.util.Size defaultThumbnailSize = mTemplateSettings.get(JPEG_THUMBNAIL_SIZE);
|
return (mExifThumbnailSize.width() == 0 && mExifThumbnailSize.height() == 0) ||
|
(defaultThumbnailSize != null &&
|
mExifThumbnailSize.width() == defaultThumbnailSize.getWidth() &&
|
mExifThumbnailSize.height() == defaultThumbnailSize.getHeight());
|
}
|
Log.w(TAG, "Settings implementation checked default of unhandled option key");
|
// Since this class isn't equipped to handle it, claim it matches the default to prevent
|
// updateRequestSettingOrForceToDefault from going with the user-provided preference
|
return true;
|
}
|
|
private <T> void updateRequestSettingOrForceToDefault(Key<T> setting, T possibleChoice) {
|
mRequestSettings.set(setting, matchesTemplateDefault(setting) ? null : possibleChoice);
|
}
|
|
public Camera2RequestSettingsSet getRequestSettings() {
|
updateRequestSettingOrForceToDefault(CONTROL_AE_REGIONS,
|
legacyAreasToMeteringRectangles(mMeteringAreas));
|
updateRequestSettingOrForceToDefault(CONTROL_AF_REGIONS,
|
legacyAreasToMeteringRectangles(mFocusAreas));
|
updateRequestSettingOrForceToDefault(CONTROL_AE_TARGET_FPS_RANGE,
|
new Range(mPreviewFpsRangeMin, mPreviewFpsRangeMax));
|
// TODO: mCurrentPreviewFormat
|
updateRequestSettingOrForceToDefault(JPEG_QUALITY, mJpegCompressQuality);
|
// TODO: mCurrentPhotoFormat
|
mRequestSettings.set(SCALER_CROP_REGION, mCropRectangle);
|
// TODO: mCurrentZoomIndex
|
updateRequestSettingOrForceToDefault(CONTROL_AE_EXPOSURE_COMPENSATION,
|
mExposureCompensationIndex);
|
updateRequestFlashMode();
|
updateRequestFocusMode();
|
updateRequestSceneMode();
|
updateRequestWhiteBalance();
|
updateRequestSettingOrForceToDefault(CONTROL_VIDEO_STABILIZATION_MODE,
|
mVideoStabilizationEnabled ?
|
CONTROL_VIDEO_STABILIZATION_MODE_ON : CONTROL_VIDEO_STABILIZATION_MODE_OFF);
|
// OIS shouldn't be on if software video stabilization is.
|
mRequestSettings.set(LENS_OPTICAL_STABILIZATION_MODE,
|
mVideoStabilizationEnabled ? LENS_OPTICAL_STABILIZATION_MODE_OFF :
|
null);
|
updateRequestSettingOrForceToDefault(CONTROL_AE_LOCK, mAutoExposureLocked);
|
updateRequestSettingOrForceToDefault(CONTROL_AWB_LOCK, mAutoWhiteBalanceLocked);
|
// TODO: mRecordingHintEnabled
|
updateRequestGpsData();
|
if (mExifThumbnailSize != null) {
|
updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE,
|
new android.util.Size(
|
mExifThumbnailSize.width(), mExifThumbnailSize.height()));
|
} else {
|
updateRequestSettingOrForceToDefault(JPEG_THUMBNAIL_SIZE, null);
|
}
|
|
return mRequestSettings;
|
}
|
|
private MeteringRectangle[] legacyAreasToMeteringRectangles(
|
List<android.hardware.Camera.Area> reference) {
|
MeteringRectangle[] transformed = null;
|
if (reference.size() > 0) {
|
transformed = new MeteringRectangle[reference.size()];
|
for (int index = 0; index < reference.size(); ++index) {
|
android.hardware.Camera.Area source = reference.get(index);
|
Rect rectangle = source.rect;
|
|
// Old API coordinates were [-1000,1000]; new ones are [0,ACTIVE_ARRAY_SIZE).
|
// We're also going from preview image--relative to sensor active array--relative.
|
double oldLeft = (rectangle.left + 1000) / 2000.0;
|
double oldTop = (rectangle.top + 1000) / 2000.0;
|
double oldRight = (rectangle.right + 1000) / 2000.0;
|
double oldBottom = (rectangle.bottom + 1000) / 2000.0;
|
int left = mCropRectangle.left + toIntConstrained(
|
mCropRectangle.width() * oldLeft, 0, mCropRectangle.width() - 1);
|
int top = mCropRectangle.top + toIntConstrained(
|
mCropRectangle.height() * oldTop, 0, mCropRectangle.height() - 1);
|
int right = mCropRectangle.left + toIntConstrained(
|
mCropRectangle.width() * oldRight, 0, mCropRectangle.width() - 1);
|
int bottom = mCropRectangle.top + toIntConstrained(
|
mCropRectangle.height() * oldBottom, 0, mCropRectangle.height() - 1);
|
transformed[index] = new MeteringRectangle(left, top, right - left, bottom - top,
|
source.weight);
|
}
|
}
|
return transformed;
|
}
|
|
private int toIntConstrained(double original, int min, int max) {
|
original = Math.max(original, min);
|
original = Math.min(original, max);
|
return (int) original;
|
}
|
|
private void updateRequestFlashMode() {
|
Integer aeMode = null;
|
Integer flashMode = null;
|
if (mCurrentFlashMode != null) {
|
switch (mCurrentFlashMode) {
|
case AUTO: {
|
aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
|
break;
|
}
|
case OFF: {
|
aeMode = CONTROL_AE_MODE_ON;
|
flashMode = FLASH_MODE_OFF;
|
break;
|
}
|
case ON: {
|
aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
|
flashMode = FLASH_MODE_SINGLE;
|
break;
|
}
|
case TORCH: {
|
flashMode = FLASH_MODE_TORCH;
|
break;
|
}
|
case RED_EYE: {
|
aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
|
break;
|
}
|
default: {
|
Log.w(TAG, "Unable to convert to API 2 flash mode: " + mCurrentFlashMode);
|
break;
|
}
|
}
|
}
|
mRequestSettings.set(CONTROL_AE_MODE, aeMode);
|
mRequestSettings.set(FLASH_MODE, flashMode);
|
}
|
|
private void updateRequestFocusMode() {
|
Integer mode = null;
|
if (mCurrentFocusMode != null) {
|
switch (mCurrentFocusMode) {
|
case AUTO: {
|
mode = CONTROL_AF_MODE_AUTO;
|
break;
|
}
|
case CONTINUOUS_PICTURE: {
|
mode = CONTROL_AF_MODE_CONTINUOUS_PICTURE;
|
break;
|
}
|
case CONTINUOUS_VIDEO: {
|
mode = CONTROL_AF_MODE_CONTINUOUS_VIDEO;
|
break;
|
}
|
case EXTENDED_DOF: {
|
mode = CONTROL_AF_MODE_EDOF;
|
break;
|
}
|
case FIXED: {
|
mode = CONTROL_AF_MODE_OFF;
|
break;
|
}
|
// TODO: We cannot support INFINITY
|
case MACRO: {
|
mode = CONTROL_AF_MODE_MACRO;
|
break;
|
}
|
default: {
|
Log.w(TAG, "Unable to convert to API 2 focus mode: " + mCurrentFocusMode);
|
break;
|
}
|
}
|
}
|
mRequestSettings.set(CONTROL_AF_MODE, mode);
|
}
|
|
private void updateRequestSceneMode() {
|
Integer mode = null;
|
if (mCurrentSceneMode != null) {
|
switch (mCurrentSceneMode) {
|
case AUTO: {
|
mode = CONTROL_SCENE_MODE_DISABLED;
|
break;
|
}
|
case ACTION: {
|
mode = CONTROL_SCENE_MODE_ACTION;
|
break;
|
}
|
case BARCODE: {
|
mode = CONTROL_SCENE_MODE_BARCODE;
|
break;
|
}
|
case BEACH: {
|
mode = CONTROL_SCENE_MODE_BEACH;
|
break;
|
}
|
case CANDLELIGHT: {
|
mode = CONTROL_SCENE_MODE_CANDLELIGHT;
|
break;
|
}
|
case FIREWORKS: {
|
mode = CONTROL_SCENE_MODE_FIREWORKS;
|
break;
|
}
|
case HDR: {
|
mode = CONTROL_SCENE_MODE_HDR;
|
break;
|
}
|
case LANDSCAPE: {
|
mode = CONTROL_SCENE_MODE_LANDSCAPE;
|
break;
|
}
|
case NIGHT: {
|
mode = CONTROL_SCENE_MODE_NIGHT;
|
break;
|
}
|
// TODO: We cannot support NIGHT_PORTRAIT
|
case PARTY: {
|
mode = CONTROL_SCENE_MODE_PARTY;
|
break;
|
}
|
case PORTRAIT: {
|
mode = CONTROL_SCENE_MODE_PORTRAIT;
|
break;
|
}
|
case SNOW: {
|
mode = CONTROL_SCENE_MODE_SNOW;
|
break;
|
}
|
case SPORTS: {
|
mode = CONTROL_SCENE_MODE_SPORTS;
|
break;
|
}
|
case STEADYPHOTO: {
|
mode = CONTROL_SCENE_MODE_STEADYPHOTO;
|
break;
|
}
|
case SUNSET: {
|
mode = CONTROL_SCENE_MODE_SUNSET;
|
break;
|
}
|
case THEATRE: {
|
mode = CONTROL_SCENE_MODE_THEATRE;
|
break;
|
}
|
default: {
|
Log.w(TAG, "Unable to convert to API 2 scene mode: " + mCurrentSceneMode);
|
break;
|
}
|
}
|
}
|
mRequestSettings.set(CONTROL_SCENE_MODE, mode);
|
}
|
|
private void updateRequestWhiteBalance() {
|
Integer mode = null;
|
if (mWhiteBalance != null) {
|
switch (mWhiteBalance) {
|
case AUTO: {
|
mode = CONTROL_AWB_MODE_AUTO;
|
break;
|
}
|
case CLOUDY_DAYLIGHT: {
|
mode = CONTROL_AWB_MODE_CLOUDY_DAYLIGHT;
|
break;
|
}
|
case DAYLIGHT: {
|
mode = CONTROL_AWB_MODE_DAYLIGHT;
|
break;
|
}
|
case FLUORESCENT: {
|
mode = CONTROL_AWB_MODE_FLUORESCENT;
|
break;
|
}
|
case INCANDESCENT: {
|
mode = CONTROL_AWB_MODE_INCANDESCENT;
|
break;
|
}
|
case SHADE: {
|
mode = CONTROL_AWB_MODE_SHADE;
|
break;
|
}
|
case TWILIGHT: {
|
mode = CONTROL_AWB_MODE_TWILIGHT;
|
break;
|
}
|
case WARM_FLUORESCENT: {
|
mode = CONTROL_AWB_MODE_WARM_FLUORESCENT;
|
break;
|
}
|
default: {
|
Log.w(TAG, "Unable to convert to API 2 white balance: " + mWhiteBalance);
|
break;
|
}
|
}
|
}
|
mRequestSettings.set(CONTROL_AWB_MODE, mode);
|
}
|
|
private void updateRequestGpsData() {
|
if (mGpsData == null || mGpsData.processingMethod == null) {
|
// It's a hack since we always use GPS time stamp but does
|
// not use other fields sometimes. Setting processing
|
// method to null means the other fields should not be used.
|
mRequestSettings.set(JPEG_GPS_LOCATION, null);
|
} else {
|
Location location = new Location(mGpsData.processingMethod);
|
location.setTime(mGpsData.timeStamp);
|
location.setAltitude(mGpsData.altitude);
|
location.setLatitude(mGpsData.latitude);
|
location.setLongitude(mGpsData.longitude);
|
mRequestSettings.set(JPEG_GPS_LOCATION, location);
|
}
|
}
|
|
/**
|
* Calculate the effective crop rectangle for this preview viewport;
|
* assumes the preview is centered to the sensor and scaled to fit across one of the dimensions
|
* without skewing.
|
*
|
* <p>Assumes the zoom level of the provided desired crop rectangle.</p>
|
*
|
* @param requestedCrop Desired crop rectangle, in active array space.
|
* @param previewSize Size of the preview buffer render target, in pixels (not in sensor space).
|
* @return A rectangle that serves as the preview stream's effective crop region (unzoomed), in
|
* sensor space.
|
*
|
* @throws NullPointerException
|
* If any of the args were {@code null}.
|
*/
|
private static Rect effectiveCropRectFromRequested(Rect requestedCrop, Size previewSize) {
|
float aspectRatioArray = requestedCrop.width() * 1.0f / requestedCrop.height();
|
float aspectRatioPreview = previewSize.width() * 1.0f / previewSize.height();
|
|
float cropHeight, cropWidth;
|
if (aspectRatioPreview < aspectRatioArray) {
|
// The new width must be smaller than the height, so scale the width by AR
|
cropHeight = requestedCrop.height();
|
cropWidth = cropHeight * aspectRatioPreview;
|
} else {
|
// The new height must be smaller (or equal) than the width, so scale the height by AR
|
cropWidth = requestedCrop.width();
|
cropHeight = cropWidth / aspectRatioPreview;
|
}
|
|
Matrix translateMatrix = new Matrix();
|
RectF cropRect = new RectF(/*left*/0, /*top*/0, cropWidth, cropHeight);
|
|
// Now center the crop rectangle so its center is in the center of the active array
|
translateMatrix.setTranslate(requestedCrop.exactCenterX(), requestedCrop.exactCenterY());
|
translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY());
|
|
translateMatrix.mapRect(/*inout*/cropRect);
|
|
// Round the rect corners towards the nearest integer values
|
Rect result = new Rect();
|
cropRect.roundOut(result);
|
return result;
|
}
|
}
|