summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorIgor Murashkin <iam@google.com>2014-07-09 14:21:29 -0700
committerIgor Murashkin <iam@google.com>2014-07-11 12:59:49 -0700
commit7ee78d1ee3ee068897b9313af2ed6446675c1be0 (patch)
tree0c0b5f4205df8046acb9bf42a75ee4158c7782b6 /core
parent12b6bb44c103ee2a93a267f80dde714b3842c134 (diff)
downloadframeworks_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')
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java4
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java159
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyResultMapper.java96
-rw-r--r--core/java/android/hardware/camera2/legacy/ParameterUtils.java421
-rw-r--r--core/java/android/hardware/camera2/params/MeteringRectangle.java22
-rw-r--r--core/java/android/hardware/camera2/utils/ParamsUtils.java33
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}