/* * 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 android.hardware.camera2.legacy; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.Camera; import android.hardware.Camera.Area; import android.hardware.camera2.params.Face; import android.hardware.camera2.params.MeteringRectangle; import android.hardware.camera2.utils.ListUtils; import android.hardware.camera2.utils.ParamsUtils; import android.hardware.camera2.utils.SizeAreaComparator; import android.util.Size; import android.util.SizeF; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static com.android.internal.util.Preconditions.*; /** * Various utilities for dealing with camera API1 parameters. */ @SuppressWarnings("deprecation") public class ParameterUtils { /** Upper/left minimal point of a normalized rectangle */ public static final int NORMALIZED_RECTANGLE_MIN = -1000; /** Lower/right maximal point of a normalized rectangle */ public static final int NORMALIZED_RECTANGLE_MAX = 1000; /** The default normalized rectangle spans the entire size of the preview viewport */ public static final Rect NORMALIZED_RECTANGLE_DEFAULT = new Rect( NORMALIZED_RECTANGLE_MIN, NORMALIZED_RECTANGLE_MIN, NORMALIZED_RECTANGLE_MAX, NORMALIZED_RECTANGLE_MAX); /** The default normalized area uses the default normalized rectangle with a weight=1 */ public static final Camera.Area CAMERA_AREA_DEFAULT = new Camera.Area(new Rect(NORMALIZED_RECTANGLE_DEFAULT), /*weight*/1); /** Empty rectangle {@code 0x0+0,0} */ public static final Rect RECTANGLE_EMPTY = new Rect(/*left*/0, /*top*/0, /*right*/0, /*bottom*/0); private static final double ASPECT_RATIO_TOLERANCE = 0.05f; /** * Calculate effective/reported zoom data from a user-specified crop region. */ public static class ZoomData { /** Zoom index used by {@link Camera.Parameters#setZoom} */ public final int zoomIndex; /** Effective crop-region given the zoom index, coordinates relative to active-array */ public final Rect previewCrop; /** Reported crop-region given the zoom index, coordinates relative to active-array */ public final Rect reportedCrop; public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop) { this.zoomIndex = zoomIndex; this.previewCrop = previewCrop; this.reportedCrop = reportedCrop; } } /** * Calculate effective/reported metering data from a user-specified metering region. */ public static class MeteringData { /** * The metering area scaled to the range of [-1000, 1000]. *
Values outside of this range are clipped to be within the range.
*/ public final Camera.Area meteringArea; /** * Effective preview metering region, coordinates relative to active-array. * *Clipped to fit inside of the (effective) preview crop region.
*/ public final Rect previewMetering; /** * Reported metering region, coordinates relative to active-array. * *Clipped to fit inside of the (reported) resulting crop region.
*/ public final Rect reportedMetering; public MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering) { this.meteringArea = meteringArea; this.previewMetering = previewMetering; this.reportedMetering = reportedMetering; } } /** * A weighted rectangle is an arbitrary rectangle (the coordinate system is unknown) with an * arbitrary weight. * *The user of this class must know what the coordinate system ahead of time; it's * then possible to convert to a more concrete type such as a metering rectangle or a face. *
* *When converting to a more concrete type, out-of-range values are clipped; this prevents * possible illegal argument exceptions being thrown at runtime.
*/ public static class WeightedRectangle { /** Arbitrary rectangle (the range is user-defined); never {@code null}. */ public final Rect rect; /** Arbitrary weight (the range is user-defined). */ public final int weight; /** * Create a new weighted-rectangle from a non-{@code null} rectangle; the {@code weight} * can be unbounded. */ public WeightedRectangle(Rect rect, int weight) { this.rect = checkNotNull(rect, "rect must not be null"); this.weight = weight; } /** * Convert to a metering rectangle, clipping any of the values to stay within range. * *If values are clipped, a warning is printed to logcat.
* * @return a new metering rectangle */ public MeteringRectangle toMetering() { int weight = clip(this.weight, MeteringRectangle.METERING_WEIGHT_MIN, MeteringRectangle.METERING_WEIGHT_MAX, rect, "weight"); int x = clipLower(rect.left, /*lo*/0, rect, "left"); int y = clipLower(rect.top, /*lo*/0, rect, "top"); int w = clipLower(rect.width(), /*lo*/0, rect, "width"); int h = clipLower(rect.height(), /*lo*/0, rect, "height"); return new MeteringRectangle(x, y, w, h, weight); } /** * Convert to a face; the rect is considered to be the bounds, and the weight * is considered to be the score. * *If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX}, * the score is clipped first and a warning is printed to logcat.
* *If the id is negative, the id is changed to 0 and a warning is printed to * logcat.
* *All other parameters are passed-through as-is.
* * @return a new face with the optional features set */ public Face toFace( int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) { int idSafe = clipLower(id, /*lo*/0, rect, "id"); int score = clip(weight, Face.SCORE_MIN, Face.SCORE_MAX, rect, "score"); return new Face(rect, score, idSafe, leftEyePosition, rightEyePosition, mouthPosition); } /** * Convert to a face; the rect is considered to be the bounds, and the weight * is considered to be the score. * *If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX}, * the score is clipped first and a warning is printed to logcat.
* *All other parameters are passed-through as-is.
* * @return a new face without the optional features */ public Face toFace() { int score = clip(weight, Face.SCORE_MIN, Face.SCORE_MAX, rect, "score"); return new Face(rect, score); } private static int clipLower(int value, int lo, Rect rect, String name) { return clip(value, lo, /*hi*/Integer.MAX_VALUE, rect, name); } private static int clip(int value, int lo, int hi, Rect rect, String name) { if (value < lo) { Log.w(TAG, "toMetering - Rectangle " + rect + " " + name + " too small, clip to " + lo); value = lo; } else if (value > hi) { Log.w(TAG, "toMetering - Rectangle " + rect + " " + name + " too small, clip to " + hi); value = hi; } return value; } } private static final String TAG = "ParameterUtils"; private static final boolean DEBUG = false; /** getZoomRatios stores zoom ratios in 1/100 increments, e.x. a zoom of 3.2 is 320 */ private static final int ZOOM_RATIO_MULTIPLIER = 100; /** * Convert a camera API1 size into a util size */ public static Size convertSize(Camera.Size size) { checkNotNull(size, "size must not be null"); return new Size(size.width, size.height); } /** * Convert a camera API1 list of sizes into a util list of sizes */ public static ListIf the requested crop region exceeds the size of the active array, it is * shrunk to fit inside of the active array first.
* *Since all api1 camera devices only support a discrete set of zooms, we have * to translate the per-pixel-granularity requested crop region into a per-zoom-index * granularity.
* *Furthermore, since the zoom index and zoom levels also depends on the field-of-view * of the preview, the current preview {@code streamSize} is also used.
* *The calculated crop regions are then written to in-place to {@code reportedCropRegion} * and {@code previewCropRegion}, in coordinates relative to the active array.
* * @param params non-{@code null} camera api1 parameters * @param activeArray active array dimensions, in sensor space * @param streamSize stream size dimensions, in pixels * @param cropRegion user-specified crop region, in active array coordinates * @param reportedCropRegion (out parameter) what the result for {@code cropRegion} looks like * @param previewCropRegion (out parameter) what the visual preview crop is * @return * the zoom index inclusively between 0 and {@code Parameters#getMaxZoom}, * where 0 means the camera is not zoomed * * @throws NullPointerException if any of the args were {@code null} */ public static int getClosestAvailableZoomCrop( Camera.Parameters params, Rect activeArray, Size streamSize, Rect cropRegion, /*out*/ Rect reportedCropRegion, Rect previewCropRegion) { checkNotNull(params, "params must not be null"); checkNotNull(activeArray, "activeArray must not be null"); checkNotNull(streamSize, "streamSize must not be null"); checkNotNull(reportedCropRegion, "reportedCropRegion must not be null"); checkNotNull(previewCropRegion, "previewCropRegion must not be null"); Rect actualCrop = new Rect(cropRegion); /* * Shrink requested crop region to fit inside of the active array size */ if (!actualCrop.intersect(activeArray)) { Log.w(TAG, "getClosestAvailableZoomCrop - Crop region out of range; " + "setting to active array size"); actualCrop.set(activeArray); } Rect previewCrop = getPreviewCropRectangleUnzoomed(activeArray, streamSize); // Make the user-requested crop region the same aspect ratio as the preview stream size Rect cropRegionAsPreview = shrinkToSameAspectRatioCentered(previewCrop, actualCrop); if (DEBUG) { Log.v(TAG, "getClosestAvailableZoomCrop - actualCrop = " + actualCrop); Log.v(TAG, "getClosestAvailableZoomCrop - previewCrop = " + previewCrop); Log.v(TAG, "getClosestAvailableZoomCrop - cropRegionAsPreview = " + cropRegionAsPreview); } /* * Iterate all available zoom rectangles and find the closest zoom index */ Rect bestReportedCropRegion = null; Rect bestPreviewCropRegion = null; int bestZoomIndex = -1; ListThe preview size must be a subset of the active array size; the resulting * rectangle will also be a subset of the active array rectangle.
* *The unzoomed crop rectangle is calculated only.
* * @param activeArray active array dimensions, in sensor space * @param previewSize size of the preview buffer render target, in pixels (not in sensor space) * @return a rectangle which serves as the preview stream's effective crop region (unzoomed), * in sensor space * * @throws NullPointerException * if any of the args were {@code null} * @throws IllegalArgumentException * if {@code previewSize} is wider or taller than {@code activeArray} */ private static Rect getPreviewCropRectangleUnzoomed(Rect activeArray, Size previewSize) { if (previewSize.getWidth() > activeArray.width()) { throw new IllegalArgumentException("previewSize must not be wider than activeArray"); } else if (previewSize.getHeight() > activeArray.height()) { throw new IllegalArgumentException("previewSize must not be taller than activeArray"); } float aspectRatioArray = activeArray.width() * 1.0f / activeArray.height(); float aspectRatioPreview = previewSize.getWidth() * 1.0f / previewSize.getHeight(); float cropH, cropW; if (Math.abs(aspectRatioPreview - aspectRatioArray) < ASPECT_RATIO_TOLERANCE) { cropH = activeArray.height(); cropW = activeArray.width(); } else if (aspectRatioPreview < aspectRatioArray) { // The new width must be smaller than the height, so scale the width by AR cropH = activeArray.height(); cropW = cropH * aspectRatioPreview; } else { // The new height must be smaller (or equal) than the width, so scale the height by AR cropW = activeArray.width(); cropH = cropW / aspectRatioPreview; } Matrix translateMatrix = new Matrix(); RectF cropRect = new RectF(/*left*/0, /*top*/0, cropW, cropH); // Now center the crop rectangle so its center is in the center of the active array translateMatrix.setTranslate(activeArray.exactCenterX(), activeArray.exactCenterY()); translateMatrix.postTranslate(-cropRect.centerX(), -cropRect.centerY()); translateMatrix.mapRect(/*inout*/cropRect); // Round the rect corners towards the nearest integer values return ParamsUtils.createRect(cropRect); } /** * Shrink the {@code shrinkTarget} rectangle to snugly fit inside of {@code reference}; * the aspect ratio of {@code shrinkTarget} will change to be the same aspect ratio as * {@code reference}. * *At most a single dimension will scale (down). Both dimensions will never be scaled.
* * @param reference the rectangle whose aspect ratio will be used as the new aspect ratio * @param shrinkTarget the rectangle which will be scaled down to have a new aspect ratio * * @return a new rectangle, a subset of {@code shrinkTarget}, * whose aspect ratio will match that of {@code reference} */ private static Rect shrinkToSameAspectRatioCentered(Rect reference, Rect shrinkTarget) { float aspectRatioReference = reference.width() * 1.0f / reference.height(); float aspectRatioShrinkTarget = shrinkTarget.width() * 1.0f / shrinkTarget.height(); float cropH, cropW; if (aspectRatioShrinkTarget < aspectRatioReference) { // The new width must be smaller than the height, so scale the width by AR cropH = reference.height(); cropW = cropH * aspectRatioShrinkTarget; } else { // The new height must be smaller (or equal) than the width, so scale the height by AR cropW = reference.width(); cropH = cropW / aspectRatioShrinkTarget; } Matrix translateMatrix = new Matrix(); RectF shrunkRect = new RectF(shrinkTarget); // Scale the rectangle down, but keep its center in the same place as before translateMatrix.setScale(cropW / reference.width(), cropH / reference.height(), shrinkTarget.exactCenterX(), shrinkTarget.exactCenterY()); translateMatrix.mapRect(/*inout*/shrunkRect); return ParamsUtils.createRect(shrunkRect); } /** * Get the available 'crop' (zoom) rectangles for this camera that will be reported * via a {@code CaptureResult} when a zoom is requested. * *These crops ignores the underlying preview buffer size, and will always be reported * the same values regardless of what configuration of outputs is used.
* *When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).
* *Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, * by shrinking the rectangle if necessary.
* *To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} * = {@code activeArray size}.
* * @param params non-{@code null} camera api1 parameters * @param activeArray active array dimensions, in sensor space * @param streamSize stream size dimensions, in pixels * * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed */ public static ListThis is the effective (real) crop that is applied by the camera api1 device * when projecting the zoom onto the intermediate preview buffer. Use this when * deciding which zoom ratio to apply.
* *When zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).
* *Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, * by shrinking the rectangle if necessary.
* *To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} * = {@code activeArray size}.
* * @param params non-{@code null} camera api1 parameters * @param activeArray active array dimensions, in sensor space * @param streamSize stream size dimensions, in pixels * * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed */ public static ListWhen zoom is supported, this will return a list of {@code 1 + #getMaxZoom} size, * where each crop rectangle corresponds to a zoom ratio (and is centered at the middle).
* *Each crop rectangle is changed to have the same aspect ratio as {@code streamSize}, * by shrinking the rectangle if necessary.
* *To get the reported crop region when applying a zoom to the sensor, use {@code streamSize} * = {@code activeArray size}.
* * @param params non-{@code null} camera api1 parameters * @param activeArray active array dimensions, in sensor space * @param streamSize stream size dimensions, in pixels * * @return a list of available zoom rectangles, sorted from least zoomed to most zoomed */ private static ListIf the camera does not support zoom, it always returns {@code 1.0f}.
* * @param params non-{@code null} camera api1 parameters * @return normalized max zoom ratio, at least {@code 1.0f} */ public static float getMaxZoomRatio(Camera.Parameters params) { if (!params.isZoomSupported()) { return 1.0f; // no zoom } ListNone of the parameters are mutated.
* * @param activeArraySize active array size of the sensor (e.g. max jpeg size) * @param cropRegion the user-specified crop region * @param previewSize the current preview size (in pixels) * @param params the current camera parameters (not mutated) * * @return the zoom index, and the effective/reported crop regions (relative to active array) */ public static ZoomData convertScalerCropRegion(Rect activeArraySize, Rect cropRegion, Size previewSize, Camera.Parameters params) { Rect activeArraySizeOnly = new Rect( /*left*/0, /*top*/0, activeArraySize.width(), activeArraySize.height()); Rect userCropRegion = cropRegion; if (userCropRegion == null) { userCropRegion = activeArraySizeOnly; } if (DEBUG) { Log.v(TAG, "convertScalerCropRegion - user crop region was " + userCropRegion); } final Rect reportedCropRegion = new Rect(); final Rect previewCropRegion = new Rect(); final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly, previewSize, userCropRegion, /*out*/reportedCropRegion, /*out*/previewCropRegion); if (DEBUG) { Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " + "zoomIndex = " + zoomIdx + ", reported crop region = " + reportedCropRegion + ", preview crop region = " + previewCropRegion); } return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion); } /** * Calculate the actual/effective/reported normalized rectangle data from a metering * rectangle. * *If any of the rectangles are out-of-range of their intended bounding box, * the {@link #RECTANGLE_EMPTY empty rectangle} is substituted instead * (with a weight of {@code 0}).
* *The metering rectangle is bound by the crop region (effective/reported respectively). * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.
* *No parameters are mutated; returns the new metering data.
* * @param activeArraySize active array size of the sensor (e.g. max jpeg size) * @param meteringRect the user-specified metering rectangle * @param zoomData the calculated zoom data corresponding to this request * * @return the metering area, the reported/effective metering rectangles */ public static MeteringData convertMeteringRectangleToLegacy( Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData) { Rect previewCrop = zoomData.previewCrop; float scaleW = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f / previewCrop.width(); float scaleH = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f / previewCrop.height(); Matrix transform = new Matrix(); // Move the preview crop so that top,left is at (0,0), otherwise after scaling // the corner bounds will be outside of [-1000, 1000] transform.setTranslate(-previewCrop.left, -previewCrop.top); // Scale into [0, 2000] range about the center of the preview transform.postScale(scaleW, scaleH); // Move so that top left of a typical rect is at [-1000, -1000] transform.postTranslate(/*dx*/NORMALIZED_RECTANGLE_MIN, /*dy*/NORMALIZED_RECTANGLE_MIN); /* * Calculate the preview metering region (effective), and the camera1 api * normalized metering region. */ Rect normalizedRegionUnbounded = ParamsUtils.mapRect(transform, meteringRect.getRect()); /* * Try to intersect normalized area with [-1000, 1000] rectangle; otherwise * it's completely out of range */ Rect normalizedIntersected = new Rect(normalizedRegionUnbounded); Camera.Area meteringArea; if (!normalizedIntersected.intersect(NORMALIZED_RECTANGLE_DEFAULT)) { Log.w(TAG, "convertMeteringRectangleToLegacy - metering rectangle too small, " + "no metering will be done"); normalizedIntersected.set(RECTANGLE_EMPTY); meteringArea = new Camera.Area(RECTANGLE_EMPTY, MeteringRectangle.METERING_WEIGHT_DONT_CARE); } else { meteringArea = new Camera.Area(normalizedIntersected, meteringRect.getMeteringWeight()); } /* * Calculate effective preview metering region */ Rect previewMetering = meteringRect.getRect(); if (!previewMetering.intersect(previewCrop)) { previewMetering.set(RECTANGLE_EMPTY); } /* * Calculate effective reported metering region * - Transform the calculated metering area back into active array space * - Clip it to be a subset of the reported crop region */ Rect reportedMetering; { Camera.Area normalizedAreaUnbounded = new Camera.Area( normalizedRegionUnbounded, meteringRect.getMeteringWeight()); WeightedRectangle reportedMeteringRect = convertCameraAreaToActiveArrayRectangle( activeArray, zoomData, normalizedAreaUnbounded, /*usePreviewCrop*/false); reportedMetering = reportedMeteringRect.rect; } if (DEBUG) { Log.v(TAG, String.format( "convertMeteringRectangleToLegacy - activeArray = %s, meteringRect = %s, " + "previewCrop = %s, meteringArea = %s, previewMetering = %s, " + "reportedMetering = %s, normalizedRegionUnbounded = %s", activeArray, meteringRect, previewCrop, stringFromArea(meteringArea), previewMetering, reportedMetering, normalizedRegionUnbounded)); } return new MeteringData(meteringArea, previewMetering, reportedMetering); } /** * Convert the normalized camera area from [-1000, 1000] coordinate space * into the active array-based coordinate space. * *Values out of range are clipped to be within the resulting (reported) crop * region. It is possible to have values larger than the preview crop.
* *Weights out of range of [0, 1000] are clipped to be within the range.
* * @param activeArraySize active array size of the sensor (e.g. max jpeg size) * @param zoomData the calculated zoom data corresponding to this request * @param area the normalized camera area * * @return the weighed rectangle in active array coordinate space, with the weight */ public static WeightedRectangle convertCameraAreaToActiveArrayRectangle( Rect activeArray, ZoomData zoomData, Camera.Area area) { return convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, area, /*usePreviewCrop*/true); } /** * Convert an api1 face into an active-array based api2 face. * *Out-of-ranges scores and ids will be clipped to be within range (with a warning).
* * @param face a non-{@code null} api1 face * @param activeArraySize active array size of the sensor (e.g. max jpeg size) * @param zoomData the calculated zoom data corresponding to this request * * @return a non-{@code null} api2 face * * @throws NullPointerException if the {@code face} was {@code null} */ public static Face convertFaceFromLegacy(Camera.Face face, Rect activeArray, ZoomData zoomData) { checkNotNull(face, "face must not be null"); Face api2Face; Camera.Area fakeArea = new Camera.Area(face.rect, /*weight*/1); WeightedRectangle faceRect = convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, fakeArea); Point leftEye = face.leftEye, rightEye = face.rightEye, mouth = face.mouth; if (leftEye != null && rightEye != null && mouth != null && leftEye.x != -2000 && leftEye.y != -2000 && rightEye.x != -2000 && rightEye.y != -2000 && mouth.x != -2000 && mouth.y != -2000) { leftEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, leftEye, /*usePreviewCrop*/true); rightEye = convertCameraPointToActiveArrayPoint(activeArray, zoomData, leftEye, /*usePreviewCrop*/true); mouth = convertCameraPointToActiveArrayPoint(activeArray, zoomData, leftEye, /*usePreviewCrop*/true); api2Face = faceRect.toFace(face.id, leftEye, rightEye, mouth); } else { api2Face = faceRect.toFace(); } return api2Face; } private static Point convertCameraPointToActiveArrayPoint( Rect activeArray, ZoomData zoomData, Point point, boolean usePreviewCrop) { Rect pointedRect = new Rect(point.x, point.y, point.x, point.y); Camera.Area pointedArea = new Area(pointedRect, /*weight*/1); WeightedRectangle adjustedRect = convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, pointedArea, usePreviewCrop); Point transformedPoint = new Point(adjustedRect.rect.left, adjustedRect.rect.top); return transformedPoint; } private static WeightedRectangle convertCameraAreaToActiveArrayRectangle( Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) { Rect previewCrop = zoomData.previewCrop; Rect reportedCrop = zoomData.reportedCrop; float scaleW = previewCrop.width() * 1.0f / (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN); float scaleH = previewCrop.height() * 1.0f / (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN); /* * Calculate the reported metering region from the non-intersected normalized region * by scaling and translating back into active array-relative coordinates. */ Matrix transform = new Matrix(); // Move top left from (-1000, -1000) to (0, 0) transform.setTranslate(/*dx*/NORMALIZED_RECTANGLE_MAX, /*dy*/NORMALIZED_RECTANGLE_MAX); // Scale from [0, 2000] back into the preview rectangle transform.postScale(scaleW, scaleH); // Move the rect so that the [-1000,-1000] point ends up at the preview [left, top] transform.postTranslate(previewCrop.left, previewCrop.top); Rect cropToIntersectAgainst = usePreviewCrop ? previewCrop : reportedCrop; // Now apply the transformation backwards to get the reported metering region Rect reportedMetering = ParamsUtils.mapRect(transform, area.rect); // Intersect it with the crop region, to avoid reporting out-of-bounds // metering regions if (!reportedMetering.intersect(cropToIntersectAgainst)) { reportedMetering.set(RECTANGLE_EMPTY); } int weight = area.weight; if (weight < MeteringRectangle.METERING_WEIGHT_MIN) { Log.w(TAG, "convertCameraAreaToMeteringRectangle - rectangle " + stringFromArea(area) + " has too small weight, clip to 0"); weight = 0; } return new WeightedRectangle(reportedMetering, area.weight); } private ParameterUtils() { throw new AssertionError(); } }