Camera2: Fix cropRegion sometimes exceeding the sensor array size
Bug: 7155264
Bug: 7120431
Change-Id: I92a9c695b97ec40acf26dbdaa877964e41a9fd4c
diff --git a/services/camera/libcameraservice/camera2/Parameters.cpp b/services/camera/libcameraservice/camera2/Parameters.cpp
index 0a72c5f..82783e3 100644
--- a/services/camera/libcameraservice/camera2/Parameters.cpp
+++ b/services/camera/libcameraservice/camera2/Parameters.cpp
@@ -680,7 +680,7 @@
params.set(CameraParameters::KEY_MAX_ZOOM, NUM_ZOOM_STEPS - 1);
camera_metadata_ro_entry_t maxDigitalZoom =
- staticInfo(ANDROID_SCALER_AVAILABLE_MAX_ZOOM, 1, 1);
+ staticInfo(ANDROID_SCALER_AVAILABLE_MAX_ZOOM, /*minCount*/1, /*maxCount*/1);
if (!maxDigitalZoom.count) return NO_INIT;
{
@@ -1505,29 +1505,8 @@
if (res != OK) return res;
delete[] reqMeteringAreas;
- // Need to convert zoom index into a crop rectangle. The rectangle is
- // chosen to maximize its area on the sensor
-
- camera_metadata_ro_entry_t maxDigitalZoom =
- staticInfo(ANDROID_SCALER_AVAILABLE_MAX_ZOOM);
- float zoomIncrement = (maxDigitalZoom.data.f[0] - 1) /
- (NUM_ZOOM_STEPS-1);
- float zoomRatio = 1 + zoomIncrement * zoom;
-
- float zoomLeft, zoomTop, zoomWidth, zoomHeight;
- if (previewWidth >= previewHeight) {
- zoomWidth = fastInfo.arrayWidth / zoomRatio;
- zoomHeight = zoomWidth *
- previewHeight / previewWidth;
- } else {
- zoomHeight = fastInfo.arrayHeight / zoomRatio;
- zoomWidth = zoomHeight *
- previewWidth / previewHeight;
- }
- zoomLeft = (fastInfo.arrayWidth - zoomWidth) / 2;
- zoomTop = (fastInfo.arrayHeight - zoomHeight) / 2;
-
- int32_t reqCropRegion[3] = { zoomLeft, zoomTop, zoomWidth };
+ CropRegion crop = calculateCropRegion();
+ int32_t reqCropRegion[3] = { crop.left, crop.top, crop.width };
res = request->update(ANDROID_SCALER_CROP_REGION,
reqCropRegion, 3);
if (res != OK) return res;
@@ -1880,5 +1859,110 @@
return (y + 1000) * (fastInfo.arrayHeight - 1) / 2000;
}
+Parameters::CropRegion Parameters::calculateCropRegion(void) const {
+
+ float zoomLeft, zoomTop, zoomWidth, zoomHeight;
+
+ // Need to convert zoom index into a crop rectangle. The rectangle is
+ // chosen to maximize its area on the sensor
+
+ camera_metadata_ro_entry_t maxDigitalZoom =
+ staticInfo(ANDROID_SCALER_AVAILABLE_MAX_ZOOM);
+ // For each zoom step by how many pixels more do we change the zoom
+ float zoomIncrement = (maxDigitalZoom.data.f[0] - 1) /
+ (NUM_ZOOM_STEPS-1);
+ // The desired activeAreaWidth/cropAreaWidth ratio (or height if h>w)
+ // via interpolating zoom step into a zoom ratio
+ float zoomRatio = 1 + zoomIncrement * zoom;
+ ALOG_ASSERT( (zoomRatio >= 1.f && zoomRatio <= maxDigitalZoom.data.f[0]),
+ "Zoom ratio calculated out of bounds. Expected 1 - %f, actual: %f",
+ maxDigitalZoom.data.f[0], zoomRatio);
+
+ ALOGV("Zoom maxDigital=%f, increment=%f, ratio=%f, previewWidth=%d, "
+ "previewHeight=%d, activeWidth=%d, activeHeight=%d",
+ maxDigitalZoom.data.f[0], zoomIncrement, zoomRatio, previewWidth,
+ previewHeight, fastInfo.arrayWidth, fastInfo.arrayHeight);
+
+ /*
+ * Assumption: On the HAL side each stream buffer calculates its crop
+ * rectangle as follows:
+ * cropRect = (zoomLeft, zoomRight,
+ * zoomWidth, zoomHeight * zoomWidth / outputWidth);
+ *
+ * Note that if zoomWidth > bufferWidth, the new cropHeight > zoomHeight
+ * (we can then get into trouble if the cropHeight > arrayHeight).
+ * By selecting the zoomRatio based on the smallest outputRatio, we
+ * guarantee this will never happen.
+ */
+
+ // Enumerate all possible output sizes, select the one with the smallest
+ // aspect ratio
+ float minOutputWidth, minOutputHeight, minOutputRatio;
+ {
+ float outputSizes[][2] = {
+ { previewWidth, previewHeight },
+ { videoWidth, videoHeight },
+ /* don't include jpeg thumbnail size - it's valid for
+ it to be set to (0,0), meaning 'no thumbnail' */
+ // { jpegThumbSize[0], jpegThumbSize[1] },
+ { pictureWidth, pictureHeight },
+ };
+
+ minOutputWidth = outputSizes[0][0];
+ minOutputHeight = outputSizes[0][1];
+ minOutputRatio = minOutputWidth / minOutputHeight;
+ for (unsigned int i = 0;
+ i < sizeof(outputSizes) / sizeof(outputSizes[0]);
+ ++i) {
+
+ float outputWidth = outputSizes[i][0];
+ float outputHeight = outputSizes[i][1];
+ float outputRatio = outputWidth / outputHeight;
+
+ if (minOutputRatio > outputRatio) {
+ minOutputRatio = outputRatio;
+ minOutputWidth = outputWidth;
+ minOutputHeight = outputHeight;
+ }
+
+ // and then use this output ratio instead of preview output ratio
+ ALOGV("Enumerating output ratio %f = %f / %f, min is %f",
+ outputRatio, outputWidth, outputHeight, minOutputRatio);
+ }
+ }
+
+ /* Ensure that the width/height never go out of bounds
+ * by scaling across a diffent dimension if an out-of-bounds
+ * possibility exists.
+ *
+ * e.g. if the previewratio < arrayratio and e.g. zoomratio = 1.0, then by
+ * calculating the zoomWidth from zoomHeight we'll actually get a
+ * zoomheight > arrayheight
+ */
+ float arrayRatio = 1.f * fastInfo.arrayWidth / fastInfo.arrayHeight;
+ if (minOutputRatio >= arrayRatio) {
+ // Adjust the height based on the width
+ zoomWidth = fastInfo.arrayWidth / zoomRatio;
+ zoomHeight = zoomWidth *
+ minOutputHeight / minOutputWidth;
+
+ } else {
+ // Adjust the width based on the height
+ zoomHeight = fastInfo.arrayHeight / zoomRatio;
+ zoomWidth = zoomHeight *
+ minOutputWidth / minOutputHeight;
+ }
+ // centering the zoom area within the active area
+ zoomLeft = (fastInfo.arrayWidth - zoomWidth) / 2;
+ zoomTop = (fastInfo.arrayHeight - zoomHeight) / 2;
+
+ ALOGV("Crop region calculated (x=%d,y=%d,w=%f,h=%f) for zoom=%d",
+ (int32_t)zoomLeft, (int32_t)zoomTop, zoomWidth, zoomHeight, this->zoom);
+
+
+ CropRegion crop = { zoomLeft, zoomTop, zoomWidth, zoomHeight };
+ return crop;
+}
+
}; // namespace camera2
}; // namespace android