diff options
author | Igor Murashkin <iam@google.com> | 2014-07-09 14:21:29 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2014-07-11 12:59:49 -0700 |
commit | 7ee78d1ee3ee068897b9313af2ed6446675c1be0 (patch) | |
tree | 0c0b5f4205df8046acb9bf42a75ee4158c7782b6 /core | |
parent | 12b6bb44c103ee2a93a267f80dde714b3842c134 (diff) | |
download | frameworks_base-7ee78d1ee3ee068897b9313af2ed6446675c1be0.zip frameworks_base-7ee78d1ee3ee068897b9313af2ed6446675c1be0.tar.gz frameworks_base-7ee78d1ee3ee068897b9313af2ed6446675c1be0.tar.bz2 |
camera2: (legacy) Implement AF/AE regions for request and result
* Also stop lying about max AE regions (now reports the truth)
Change-Id: I567a1e6c58ed0bfb77cc09787393ea18eba5d613
Diffstat (limited to 'core')
6 files changed, 606 insertions, 129 deletions
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java index d505365..149f4c8 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java @@ -85,7 +85,7 @@ public class LegacyMetadataMapper { * being set to true. */ static final boolean LIE_ABOUT_AE_STATE = true; - static final boolean LIE_ABOUT_AE_MAX_REGIONS = true; + static final boolean LIE_ABOUT_AE_MAX_REGIONS = false; static final boolean LIE_ABOUT_AF = true; static final boolean LIE_ABOUT_AF_MAX_REGIONS = true; static final boolean LIE_ABOUT_AWB = true; @@ -201,7 +201,7 @@ public class LegacyMetadataMapper { /* * info.supportedHardwareLevel */ - m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED); + m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY); /* * scaler.availableStream*, scaler.available*Durations, sensor.info.maxFrameDuration diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java index 17dda08..cc18865 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java @@ -41,12 +41,6 @@ public class LegacyRequestMapper { private static final String TAG = "LegacyRequestMapper"; private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); - /** The default normalized camera area spans the entire size of the preview viewport */ - private static final Camera.Area CAMERA_AREA_DEFAULT = - new Camera.Area( - new Rect(/*left*/-1000, /*top*/-1000, /*right*/1000, /*bottom*/1000), - /*weight*/1); - /** * Set the legacy parameters using the {@link LegacyRequest legacy request}. * @@ -61,40 +55,20 @@ public class LegacyRequestMapper { Size previewSize = legacyRequest.previewSize; Camera.Parameters params = legacyRequest.parameters; + Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + /* * scaler.cropRegion */ + ParameterUtils.ZoomData zoomData; { - Rect activeArraySize = characteristics.get( - CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - Rect activeArraySizeOnly = new Rect( - /*left*/0, /*top*/0, - activeArraySize.width(), activeArraySize.height()); - - Rect userCropRegion = request.get(SCALER_CROP_REGION); + zoomData = ParameterUtils.convertScalerCropRegion(activeArray, + request.get(SCALER_CROP_REGION), + previewSize, + params); - if (userCropRegion == null) { - userCropRegion = activeArraySizeOnly; - } - - if (VERBOSE) { - Log.v(TAG, "convertRequestToMetadata - user crop region was " + userCropRegion); - } - - Rect reportedCropRegion = new Rect(); - Rect previewCropRegion = new Rect(); - int zoomIndex = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly, - previewSize, userCropRegion, - /*out*/reportedCropRegion, /*out*/previewCropRegion); - - if (VERBOSE) { - Log.v(TAG, "convertRequestToMetadata - zoom calculated to: " + - "zoomIndex = " + zoomIndex + - ", reported crop region = " + reportedCropRegion + - ", preview crop region = " + previewCropRegion); - } if (params.isZoomSupported()) { - params.setZoom(zoomIndex); + params.setZoom(zoomData.zoomIndex); } else if (VERBOSE) { Log.v(TAG, "convertRequestToMetadata - zoom is not supported"); } @@ -126,49 +100,29 @@ public class LegacyRequestMapper { } /* - * control.aeRegions - * -- ORDER OF EXECUTION MATTERS: - * -- This must be done after the crop region (zoom) was already set in the parameters + * control.aeRegions, afRegions */ { - MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS); - int maxNumMeteringAreas = params.getMaxNumMeteringAreas(); - if (aeRegions != null && maxNumMeteringAreas > 0) { - // Add all non-zero weight regions to the list - List<MeteringRectangle> meteringRectangleList = new ArrayList<>(); - for (MeteringRectangle rect : aeRegions) { - if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) { - meteringRectangleList.add(rect); - } - } - - // Ignore any regions beyond our maximum supported count - int countMeteringAreas = - Math.min(maxNumMeteringAreas, meteringRectangleList.size()); - List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas); - Rect activeArray = characteristics.get( - CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - - for (int i = 0; i < countMeteringAreas; ++i) { - MeteringRectangle rect = meteringRectangleList.get(i); - - Camera.Area area = convertMeteringRectangleToLegacy(activeArray, rect); - meteringAreaList.add(area); - } + // aeRegions + { + MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS); + int maxNumMeteringAreas = params.getMaxNumMeteringAreas(); + List<Camera.Area> meteringAreaList = convertMeteringRegionsToLegacy( + activeArray, zoomData, aeRegions, maxNumMeteringAreas, + /*regionName*/"AE"); params.setMeteringAreas(meteringAreaList); + } - if (maxNumMeteringAreas < meteringRectangleList.size()) { - Log.w(TAG, - "convertRequestToMetadata - Too many requested AE regions, " - + "ignoring all beyond the first " + maxNumMeteringAreas); - } - } else { - if (maxNumMeteringAreas > 0) { - params.setMeteringAreas(Arrays.asList(CAMERA_AREA_DEFAULT)); - } else { - params.setMeteringAreas(null); - } + // afRegions + { + MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS); + int maxNumFocusAreas = params.getMaxNumFocusAreas(); + List<Camera.Area> focusAreaList = convertMeteringRegionsToLegacy( + activeArray, zoomData, afRegions, maxNumFocusAreas, + /*regionName*/"AF"); + + params.setFocusAreas(focusAreaList); } } @@ -208,6 +162,52 @@ public class LegacyRequestMapper { } + private static List<Camera.Area> convertMeteringRegionsToLegacy( + Rect activeArray, ParameterUtils.ZoomData zoomData, + MeteringRectangle[] meteringRegions, int maxNumMeteringAreas, String regionName) { + if (meteringRegions == null || maxNumMeteringAreas <= 0) { + if (maxNumMeteringAreas > 0) { + return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT); + } else { + return null; + } + } + + // Add all non-zero weight regions to the list + List<MeteringRectangle> meteringRectangleList = new ArrayList<>(); + for (MeteringRectangle rect : meteringRegions) { + if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) { + meteringRectangleList.add(rect); + } + } + + // Ignore any regions beyond our maximum supported count + int countMeteringAreas = + Math.min(maxNumMeteringAreas, meteringRectangleList.size()); + List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas); + + for (int i = 0; i < countMeteringAreas; ++i) { + MeteringRectangle rect = meteringRectangleList.get(i); + + ParameterUtils.MeteringData meteringData = + ParameterUtils.convertMeteringRectangleToLegacy(activeArray, rect, zoomData); + meteringAreaList.add(meteringData.meteringArea); + } + + if (maxNumMeteringAreas < meteringRectangleList.size()) { + Log.w(TAG, + "convertMeteringRegionsToLegacy - Too many requested " + regionName + + " regions, ignoring all beyond the first " + maxNumMeteringAreas); + } + + if (VERBOSE) { + Log.v(TAG, "convertMeteringRegionsToLegacy - " + regionName + " areas = " + + ParameterUtils.stringFromAreaList(meteringAreaList)); + } + + return meteringAreaList; + } + private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) { int flashMode = getOrDefault(r, FLASH_MODE, FLASH_MODE_OFF); int aeMode = getOrDefault(r, CONTROL_AE_MODE, CONTROL_AE_MODE_ON); @@ -275,21 +275,6 @@ public class LegacyRequestMapper { return legacyFps; } - private static Camera.Area convertMeteringRectangleToLegacy( - Rect activeArray, MeteringRectangle meteringRect) { - // TODO: use matrix transform magic here - - Rect rect = new Rect(); - - // TODO: Take the cropRegion (zooming) into account here - - // TODO: crop to be within [-1000, 1000] range for both X and Y if the values end up too big - //return new Camera.Area(rect, meteringRect.getMeteringWeight()); - - Log.w(TAG, "convertMeteringRectangleToLegacy - TODO: support metering rects"); - return CAMERA_AREA_DEFAULT; - } - private static <T> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue) { checkNotNull(r, "r must not be null"); checkNotNull(key, "key must not be null"); diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java index 79287c1..b592b8c 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java +++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java @@ -24,9 +24,16 @@ import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle; +import android.hardware.camera2.legacy.ParameterUtils.ZoomData; +import android.hardware.camera2.params.MeteringRectangle; +import android.hardware.camera2.utils.ListUtils; import android.util.Log; import android.util.Size; +import java.util.ArrayList; +import java.util.List; + import static com.android.internal.util.Preconditions.*; import static android.hardware.camera2.CaptureResult.*; @@ -54,6 +61,11 @@ public class LegacyResultMapper { CameraMetadataNative result = new CameraMetadataNative(); + Rect activeArraySize = characteristics.get( + CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); + ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize, + request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params); + /* * control */ @@ -66,7 +78,7 @@ public class LegacyResultMapper { /* * control.ae* */ - mapAe(result, /*out*/params); + mapAe(result, activeArraySize, zoomData, /*out*/params); // control.awbLock result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock()); @@ -92,7 +104,7 @@ public class LegacyResultMapper { /* * scaler */ - mapScaler(result, characteristics, request, previewSize, params); + mapScaler(result, zoomData, /*out*/params); /* * sensor @@ -104,7 +116,8 @@ public class LegacyResultMapper { return result; } - private static void mapAe(CameraMetadataNative m, /*out*/Parameters p) { + private static void mapAe(CameraMetadataNative m, + Rect activeArray, ZoomData zoomData, /*out*/Parameters p) { // control.aeAntiBandingMode { int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault( @@ -123,15 +136,52 @@ public class LegacyResultMapper { } // control.aeRegions + { + if (VERBOSE) { + String meteringAreas = p.get("metering-areas"); + Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas); + } - /* - * TODO: Use the *resulting* crop region to calculate intersection with - * metering region - * - * Report the sensor-relative metering region in the result even - * if that's not actually the real thing (similar to how we do it - * for zooming) - */ + MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray, + zoomData, p.getMeteringAreas(), "AE"); + + m.set(CONTROL_AE_REGIONS, meteringRectArray); + } + + // control.afRegions + { + if (VERBOSE) { + String focusAreas = p.get("focus-areas"); + Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas); + } + + MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray, + zoomData, p.getFocusAreas(), "AF"); + + m.set(CONTROL_AF_REGIONS, meteringRectArray); + } + } + + private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData, + List<Camera.Area> meteringAreaList, String regionName) { + List<MeteringRectangle> meteringRectList = new ArrayList<>(); + if (meteringAreaList != null) { + for (Camera.Area area : meteringAreaList) { + WeightedRectangle rect = + ParameterUtils.convertCameraAreaToActiveArrayRectangle( + activeArray, zoomData, area); + + meteringRectList.add(rect.toMetering()); + } + } + + if (VERBOSE) { + Log.v(TAG, + "Metering rectangles for " + regionName + ": " + + ListUtils.listToString(meteringRectList)); + } + + return meteringRectList.toArray(new MeteringRectangle[0]); } @@ -169,33 +219,13 @@ public class LegacyResultMapper { /** Map results for scaler.* */ private static void mapScaler(CameraMetadataNative m, - CameraCharacteristics characteristics, - CaptureRequest request, - Size previewSize, + ZoomData zoomData, /*out*/Parameters p) { /* * scaler.cropRegion */ { - Rect activeArraySize = characteristics.get( - CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); - Rect activeArraySizeOnly = new Rect( - /*left*/0, /*top*/0, - activeArraySize.width(), activeArraySize.height()); - - Rect userCropRegion = request.get(CaptureRequest.SCALER_CROP_REGION); - - if (userCropRegion == null) { - userCropRegion = activeArraySizeOnly; - } - - Rect reportedCropRegion = new Rect(); - Rect previewCropRegion = new Rect(); - ParameterUtils.getClosestAvailableZoomCrop(p, activeArraySizeOnly, - previewSize, userCropRegion, - /*out*/reportedCropRegion, /*out*/previewCropRegion); - - m.set(SCALER_CROP_REGION, reportedCropRegion); + m.set(SCALER_CROP_REGION, zoomData.reportedCrop); } } } diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java index c3b1bbd..efd12f2 100644 --- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java +++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java @@ -17,9 +17,15 @@ 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.legacy.ParameterUtils.MeteringData; +import android.hardware.camera2.legacy.ParameterUtils.ZoomData; +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; @@ -38,6 +44,181 @@ import static com.android.internal.util.Preconditions.*; * Various utilities for dealing with camera API1 parameters. */ 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); + + /** + * 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]. + * <p>Values outside of this range are clipped to be within the range.</p> + */ + public final Camera.Area meteringArea; + /** + * Effective preview metering region, coordinates relative to active-array. + * + * <p>Clipped to fit inside of the (effective) preview crop region.</p> + */ + public final Rect previewMetering; + /** + * Reported metering region, coordinates relative to active-array. + * + * <p>Clipped to fit inside of the (reported) resulting crop region.</p> + */ + 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. + * + * <p>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. + * </p> + * + * <p>When converting to a more concrete type, out-of-range values are clipped; this prevents + * possible illegal argument exceptions being thrown at runtime.</p> + */ + 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. + * + * <p>If values are clipped, a warning is printed to logcat.</p> + * + * @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. + * + * <p>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.</p> + * + * <p>All other parameters are passed-through as-is.</p> + * + * @return a new face with the optional features set + */ + public Face toFace( + int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) { + int score = clip(weight, + Face.SCORE_MIN, + Face.SCORE_MAX, + rect, + "score"); + + return new Face(rect, score, id, leftEyePosition, rightEyePosition, mouthPosition); + } + + /** + * Convert to a face; the rect is considered to be the bounds, and the weight + * is considered to be the score. + * + * <p>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.</p> + * + * <p>All other parameters are passed-through as-is.</p> + * + * @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 VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); @@ -100,6 +281,35 @@ public class ParameterUtils { } /** + * Convert a camera area list into a human-readable string + * @param areaList a list of areas (null is ok) + */ + public static String stringFromAreaList(List<Camera.Area> areaList) { + StringBuilder sb = new StringBuilder(); + + if (areaList == null) { + return null; + } + + int i = 0; + for (Camera.Area area : areaList) { + if (area == null) { + sb.append("null"); + } else { + sb.append(stringFromArea(area)); + } + + if (i != areaList.size() - 1) { + sb.append(", "); + } + + i++; + } + + return sb.toString(); + } + + /** * Calculate the closest zoom index for the user-requested crop region by rounding * up to the closest (largest or equal) possible zoom crop. * @@ -487,6 +697,217 @@ public class ParameterUtils { return new SizeF(zoomRatioWidth, zoomRatioHeight); } + /** + * Convert the user-specified crop region into zoom data; which can be used + * to set the parameters to a specific zoom index, or to report back to the user what the + * actual zoom was, or for other calculations requiring the current preview crop region. + * + * <p>None of the parameters are mutated.</p> + * + * @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 (VERBOSE) { + 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 (VERBOSE) { + 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. + * + * <p>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}).</p> + * + * <p>The metering rectangle is bound by the crop region (effective/reported respectively). + * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.</p> + * + * <p>No parameters are mutated; returns the new metering data.</p> + * + * @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 (VERBOSE) { + 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. + * + * <p>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.</p> + * + * <p>Weights out of range of [0, 1000] are clipped to be within the range.</p> + * + * @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); + } + + 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(); } diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java index 93fd053..b1cea57 100644 --- a/core/java/android/hardware/camera2/params/MeteringRectangle.java +++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java @@ -100,6 +100,8 @@ public final class MeteringRectangle { /** * Create a new metering rectangle. * + * <p>The point {@code xy}'s data is copied; the reference is not retained.</p> + * * @param xy a non-{@code null} {@link Point} with both x,y >= 0 * @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0 * @param meteringWeight weight >= 0 @@ -121,6 +123,8 @@ public final class MeteringRectangle { /** * Create a new metering rectangle. * + * <p>The rectangle data is copied; the reference is not retained.</p> + * * @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0 * @param meteringWeight weight >= 0 * @@ -185,7 +189,7 @@ public final class MeteringRectangle { /** * Convenience method to create the upper-left (X,Y) coordinate as a {@link Point}. * - * @return {@code (x,y)} point with both x,y >= 0 + * @return a new {@code (x,y)} {@link Point} with both x,y >= 0 */ public Point getUpperLeftPoint() { return new Point(mX, mY); @@ -196,7 +200,7 @@ public final class MeteringRectangle { * * <p>This strips away the X,Y,weight from the rectangle.</p> * - * @return a Size with non-negative width and height + * @return a new {@link Size} with non-negative width and height */ public Size getSize() { return new Size(mWidth, mHeight); @@ -207,7 +211,7 @@ public final class MeteringRectangle { * * <p>This strips away the weight from the rectangle.</p> * - * @return a {@link Rect} with non-negative x1, y1, x2, y2 + * @return a new {@link Rect} with non-negative x1, y1, x2, y2 */ public Rect getRect() { return new Rect(mX, mY, mX + mWidth, mY + mHeight); @@ -250,4 +254,16 @@ public final class MeteringRectangle { public int hashCode() { return HashCodeHelpers.hashCode(mX, mY, mWidth, mHeight, mWeight); } + + /** + * Return the metering rectangle as a string representation + * {@code "(x:%d, y:%d, w:%d, h:%d, wt:%d)"} where each {@code %d} respectively represents + * the x, y, width, height, and weight points. + * + * @return string representation of the metering rectangle + */ + @Override + public String toString() { + return String.format("(x:%d, y:%d, w:%d, h:%d, wt:%d)", mX, mY, mWidth, mHeight, mWeight); + } } diff --git a/core/java/android/hardware/camera2/utils/ParamsUtils.java b/core/java/android/hardware/camera2/utils/ParamsUtils.java index cd39f71..6c0fd2d 100644 --- a/core/java/android/hardware/camera2/utils/ParamsUtils.java +++ b/core/java/android/hardware/camera2/utils/ParamsUtils.java @@ -16,6 +16,7 @@ package android.hardware.camera2.utils; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.util.Size; @@ -45,7 +46,9 @@ public class ParamsUtils { /** * Create a {@link Rect} from a {@code RectF} by creating a new rectangle with - * each corner (left, top, right, bottom) rounded towards the nearest integer value. + * each corner (left, top, right, bottom) rounded towards the nearest integer bounding box. + * + * <p>In particular (left, top) is floored, and (right, bottom) is ceiled.</p> * * @param size a non-{@code null} rect * @@ -57,12 +60,34 @@ public class ParamsUtils { checkNotNull(rect, "rect must not be null"); Rect r = new Rect(); - rect.round(r); + rect.roundOut(r); return r; } /** + * Map the rectangle in {@code rect} with the transform in {@code transform} into + * a new rectangle, with each corner (left, top, right, bottom) rounded towards the nearest + * integer bounding box. + * + * <p>None of the arguments are mutated.</p> + * + * @param transform a non-{@code null} transformation matrix + * @param rect a non-{@code null} rectangle + * @return a new rectangle that was transformed by {@code transform} + * + * @throws NullPointerException if any of the args were {@code null} + */ + public static Rect mapRect(Matrix transform, Rect rect) { + checkNotNull(transform, "transform must not be null"); + checkNotNull(rect, "rect must not be null"); + + RectF rectF = new RectF(rect); + transform.mapRect(rectF); + return createRect(rectF); + } + + /** * Create a {@link Size} from a {@code Rect} by creating a new size whose width * and height are the same as the rectangle's width and heights. * @@ -79,10 +104,10 @@ public class ParamsUtils { } /** - * Convert an integral rectangle ({@code size}) to a floating point rectangle + * Convert an integral rectangle ({@code source}) to a floating point rectangle * ({@code destination}) in-place. * - * @param size the originating integer rectangle will be read from here + * @param source the originating integer rectangle will be read from here * @param destination the resulting floating point rectangle will be written out to here * * @throws NullPointerException if {@code rect} was {@code null} |