Fix focus when zooming

pull/574/head
Mattia Iavarone 6 years ago
parent 8fc9b94ffc
commit f04c9bcbdf
  1. 192
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Meter.java

@ -119,12 +119,6 @@ public class Meter {
mCallback = callback; mCallback = callback;
} }
@NonNull
private <T> T readCharacteristic(@NonNull CameraCharacteristics.Key<T> key, @NonNull T fallback) {
T value = mCharacteristics.get(key);
return value == null ? fallback : value;
}
/** /**
* Starts a metering sequence. * Starts a metering sequence.
* @param point point * @param point point
@ -139,13 +133,57 @@ public class Meter {
// This is a good Q/A. https://stackoverflow.com/a/33181620/4288782 // This is a good Q/A. https://stackoverflow.com/a/33181620/4288782
// At first, the point is relative to the View system and does not account our own cropping. // At first, the point is relative to the View system and does not account our own cropping.
// Will keep updating these two below. // Will keep updating these two below.
PointF referencePoint = new PointF(mPoint.x, mPoint.y); final PointF referencePoint = new PointF(mPoint.x, mPoint.y);
Size referenceSize /* = previewSurfaceSize */; Size referenceSize = mEngine.mPreview.getSurfaceSize();
// 1. Account for cropping. // 1. Account for cropping.
// This will enlarge the preview size so that aspect ratio matches.
referenceSize = applyPreviewCropping(referenceSize, referencePoint);
// 2. Scale to the preview stream coordinates.
// This will move to the preview stream coordinates by scaling.
referenceSize = applyPreviewScale(referenceSize, referencePoint);
// 3. Rotate to the stream coordinate system.
// This leaves us with sensor stream coordinates.
referenceSize = applyPreviewToSensorRotation(referenceSize, referencePoint);
// 4. Move to the crop region coordinate system.
// The crop region is the union of all currently active streams.
referenceSize = applyCropRegionCoordinates(referenceSize, referencePoint);
// 5. Move to the active array coordinate system.
referenceSize = applyActiveArrayCoordinates(referenceSize, referencePoint);
// 6. Now we can compute the metering regions.
// We want to define them as a fraction of the visible size which (apart from cropping)
// can be obtained through the SENSOR rotated preview stream size.
Size visibleSize = mEngine.getPreviewStreamSize(Reference.SENSOR);
//noinspection ConstantConditions
MeteringRectangle area1 = createMeteringRectangle(referenceSize, referencePoint, visibleSize, 0.05F, 1000);
MeteringRectangle area2 = createMeteringRectangle(referenceSize, referencePoint, visibleSize, 0.1F, 100);
List<MeteringRectangle> areas = Arrays.asList(area1, area2);
// 7. And finally dispatch everything
mAutoFocus.startMetering(mCharacteristics, mBuilder, areas);
mAutoWhiteBalance.startMetering(mCharacteristics, mBuilder, areas);
mAutoExposure.startMetering(mCharacteristics, mBuilder, areas);
// Dispatch to callback
mCallback.onMeteringStarted(mPoint, mGesture);
mMeteringStartTime = System.currentTimeMillis();
}
@SuppressWarnings("UnnecessaryLocalVariable")
@NonNull
private Size applyPreviewCropping(@NonNull Size referenceSize, @NonNull PointF referencePoint) {
Size previewStreamSize = mEngine.getPreviewStreamSize(Reference.VIEW); Size previewStreamSize = mEngine.getPreviewStreamSize(Reference.VIEW);
Size previewSurfaceSize = mEngine.mPreview.getSurfaceSize(); Size previewSurfaceSize = referenceSize;
if (previewStreamSize == null) throw new IllegalStateException("getPreviewStreamSize should not be null at this point."); if (previewStreamSize == null) {
throw new IllegalStateException("getPreviewStreamSize should not be null at this point.");
}
int referenceWidth = previewSurfaceSize.getWidth();
int referenceHeight = previewSurfaceSize.getHeight();
AspectRatio previewStreamAspectRatio = AspectRatio.of(previewStreamSize); AspectRatio previewStreamAspectRatio = AspectRatio.of(previewStreamSize);
AspectRatio previewSurfaceAspectRatio = AspectRatio.of(previewSurfaceSize); AspectRatio previewSurfaceAspectRatio = AspectRatio.of(previewSurfaceSize);
if (mEngine.mPreview.isCropping()) { if (mEngine.mPreview.isCropping()) {
@ -154,30 +192,41 @@ public class Meter {
// of the surface is not on the left size of stream (it's more to the right). // of the surface is not on the left size of stream (it's more to the right).
float scale = previewStreamAspectRatio.toFloat() / previewSurfaceAspectRatio.toFloat(); float scale = previewStreamAspectRatio.toFloat() / previewSurfaceAspectRatio.toFloat();
referencePoint.x += previewSurfaceSize.getWidth() * (scale - 1F) / 2F; referencePoint.x += previewSurfaceSize.getWidth() * (scale - 1F) / 2F;
referenceWidth = Math.round(previewSurfaceSize.getWidth() * scale);
} else { } else {
// Stream is taller. The y coordinate must be increased: a touch on the top side // Stream is taller. The y coordinate must be increased: a touch on the top side
// of the surface is not on the top size of stream (it's a bit lower). // of the surface is not on the top size of stream (it's a bit lower).
float scale = previewSurfaceAspectRatio.toFloat() / previewStreamAspectRatio.toFloat(); float scale = previewSurfaceAspectRatio.toFloat() / previewStreamAspectRatio.toFloat();
referencePoint.x += previewSurfaceSize.getHeight() * (scale - 1F) / 2F; referencePoint.y += previewSurfaceSize.getHeight() * (scale - 1F) / 2F;
referenceHeight = Math.round(previewSurfaceSize.getHeight() * scale);
}
} }
return new Size(referenceWidth, referenceHeight);
} }
// 2. Scale to the stream coordinates (not the surface). @SuppressWarnings("ConstantConditions")
referencePoint.x *= (float) previewStreamSize.getWidth() / previewSurfaceSize.getWidth(); @NonNull
referencePoint.y *= (float) previewStreamSize.getHeight() / previewSurfaceSize.getHeight(); private Size applyPreviewScale(@NonNull Size referenceSize, @NonNull PointF referencePoint) {
referenceSize = previewStreamSize; // The referenceSize how has the same aspect ratio of the previewStreamSize, but they
// can still have different size (that is, a scale operation is needed).
Size previewStreamSize = mEngine.getPreviewStreamSize(Reference.VIEW);
referencePoint.x *= (float) previewStreamSize.getWidth() / referenceSize.getWidth();
referencePoint.y *= (float) previewStreamSize.getHeight() / referenceSize.getHeight();
return previewStreamSize;
}
// 3. Rotate to the stream coordinate system. @SuppressWarnings("SuspiciousNameCombination")
// Not elegant, but the sin/cos way was failing. @NonNull
private Size applyPreviewToSensorRotation(@NonNull Size referenceSize, @NonNull PointF referencePoint) {
// Not elegant, but the sin/cos way was failing for some reason.
int angle = mEngine.getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE); int angle = mEngine.getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE);
boolean flip = angle % 180 != 0; boolean flip = angle % 180 != 0;
float tempX = referencePoint.x; float tempY = referencePoint.y; float tempX = referencePoint.x;
float tempY = referencePoint.y;
if (angle == 0) { if (angle == 0) {
referencePoint.x = tempX; referencePoint.x = tempX;
referencePoint.y = tempY; referencePoint.y = tempY;
} else if (angle == 90) { } else if (angle == 90) {
//noinspection SuspiciousNameCombination
referencePoint.x = tempY; referencePoint.x = tempY;
referencePoint.y = referenceSize.getWidth() - tempX; referencePoint.y = referenceSize.getWidth() - tempX;
} else if (angle == 180) { } else if (angle == 180) {
@ -185,53 +234,42 @@ public class Meter {
referencePoint.y = referenceSize.getHeight() - tempY; referencePoint.y = referenceSize.getHeight() - tempY;
} else if (angle == 270) { } else if (angle == 270) {
referencePoint.x = referenceSize.getHeight() - tempY; referencePoint.x = referenceSize.getHeight() - tempY;
//noinspection SuspiciousNameCombination
referencePoint.y = tempX; referencePoint.y = tempX;
} else { } else {
throw new IllegalStateException("Unexpected angle " + angle); throw new IllegalStateException("Unexpected angle " + angle);
} }
referenceSize = flip ? referenceSize.flip() : referenceSize; return flip ? referenceSize.flip() : referenceSize;
}
// These points are now referencing the stream rect on the sensor array. @NonNull
// But we still have to figure out how the stream rect is laid on the sensor array. private Size applyCropRegionCoordinates(@NonNull Size referenceSize, @NonNull PointF referencePoint) {
// The input point and size refer to the stream rect.
// The stream rect is part of the 'crop region', as described below.
// https://source.android.com/devices/camera/camera3_crop_reprocess.html // https://source.android.com/devices/camera/camera3_crop_reprocess.html
// For sanity, let's assume it is centered. Rect cropRect = mBuilder.get(CaptureRequest.SCALER_CROP_REGION);
// For sanity, let's also assume that the crop region is equal to the stream region. // For now, we don't care about x and y position. Rect should be non-null, but let's be safe.
int cropRectWidth = cropRect == null ? referenceSize.getWidth() : cropRect.width();
// 4. Move to the active sensor array coordinate system. int cropRectHeight = cropRect == null ? referenceSize.getHeight() : cropRect.height();
Rect activeRect = readCharacteristic(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE, // The stream is always centered inside the crop region, and one of the dimensions
new Rect(0, 0, referenceSize.getWidth(), referenceSize.getHeight())); // should always match. We just increase the other one.
referencePoint.x += (activeRect.width() - referenceSize.getWidth()) / 2F; referencePoint.x += (cropRectWidth - referenceSize.getWidth()) / 2F;
referencePoint.y += (activeRect.height() - referenceSize.getHeight()) / 2F; referencePoint.y += (cropRectHeight - referenceSize.getHeight()) / 2F;
referenceSize = new Size(activeRect.width(), activeRect.height()); return new Size(cropRectWidth, cropRectHeight);
}
// 5. Account for zoom! This only works for mZoomValue = 0.
// We must scale down with respect to the reference size center. If mZoomValue = 1,
// This must leave everything unchanged.
float maxZoom = readCharacteristic(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM,
1F /* no zoom */);
float currZoom = 1 + mEngine.mZoomValue * (maxZoom - 1); // 1 ... maxZoom
float currReduction = 1 / currZoom;
float referenceCenterX = referenceSize.getWidth() / 2F;
float referenceCenterY = referenceSize.getHeight() / 2F;
referencePoint.x = referenceCenterX + currReduction * (referencePoint.x - referenceCenterX);
referencePoint.y = referenceCenterY + currReduction * (referencePoint.y - referenceCenterY);
// 6. NOW we can compute the metering regions.
float visibleWidth = referenceSize.getWidth() * currReduction;
float visibleHeight = referenceSize.getHeight() * currReduction;
MeteringRectangle area1 = createMeteringRectangle(referencePoint, referenceSize, visibleWidth, visibleHeight, 0.05F, 1000);
MeteringRectangle area2 = createMeteringRectangle(referencePoint, referenceSize, visibleWidth, visibleHeight, 0.1F, 100);
List<MeteringRectangle> areas = Arrays.asList(area1, area2);
// 7. And finally dispatch everything
mAutoFocus.startMetering(mCharacteristics, mBuilder, areas);
mAutoWhiteBalance.startMetering(mCharacteristics, mBuilder, areas);
mAutoExposure.startMetering(mCharacteristics, mBuilder, areas);
// Dispatch to callback @NonNull
mCallback.onMeteringStarted(mPoint, mGesture); private Size applyActiveArrayCoordinates(@NonNull Size referenceSize, @NonNull PointF referencePoint) {
mMeteringStartTime = System.currentTimeMillis(); // The input point and size refer to the scaler crop region.
// We can query for the crop region position inside the active array, so this is easy.
Rect cropRect = mBuilder.get(CaptureRequest.SCALER_CROP_REGION);
referencePoint.x += cropRect == null ? 0 : cropRect.left;
referencePoint.y += cropRect == null ? 0 : cropRect.top;
// Finally, get the active rect width and height from characteristics.
Rect activeRect = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (activeRect == null) { // Should never happen
activeRect = new Rect(0, 0, referenceSize.getWidth(), referenceSize.getHeight());
}
return new Size(activeRect.width(), activeRect.height());
} }
/** /**
@ -243,16 +281,29 @@ public class Meter {
*/ */
@NonNull @NonNull
private MeteringRectangle createMeteringRectangle( private MeteringRectangle createMeteringRectangle(
@NonNull PointF center, @NonNull Size boundaries, @NonNull Size boundaries,
float visibleWidth, float visibleHeight, @NonNull PointF center,
float factor, int weight) { @NonNull Size visibleSize,
float halfWidth = factor * visibleWidth / 2F; float factor,
float halfHeight = factor * visibleHeight / 2F; int weight) {
float rectangleWidth = factor * visibleSize.getWidth();
float rectangleHeight = factor * visibleSize.getHeight();
float rectangleLeft = center.x - rectangleWidth / 2F;
float rectangleTop = center.y - rectangleHeight / 2F;
// Respect boundaries
if (rectangleLeft < 0) rectangleLeft = 0;
if (rectangleTop < 0) rectangleTop = 0;
if (rectangleLeft + rectangleWidth > boundaries.getWidth()) {
rectangleWidth = boundaries.getWidth() - rectangleLeft;
}
if (rectangleTop + rectangleHeight > boundaries.getHeight()) {
rectangleHeight = boundaries.getHeight() - rectangleTop;
}
return new MeteringRectangle( return new MeteringRectangle(
(int) Math.max(0, center.x - halfWidth), (int) rectangleLeft,
(int) Math.max(0, center.y - halfHeight), (int) rectangleTop,
(int) Math.min(boundaries.getWidth(), halfWidth * 2F), (int) rectangleWidth,
(int) Math.min(boundaries.getHeight(), halfHeight * 2F), (int) rectangleHeight,
weight weight
); );
} }
@ -310,7 +361,8 @@ public class Meter {
mEngine.mHandler.remove(mResetRunnable); mEngine.mHandler.remove(mResetRunnable);
if (mCallback.canResetMetering(mPoint, mGesture)) { if (mCallback.canResetMetering(mPoint, mGesture)) {
LOG.i("Resetting the meter parameters."); LOG.i("Resetting the meter parameters.");
Rect whole = readCharacteristic(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE, new Rect()); Rect whole = mCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
if (whole == null) whole = new Rect();
MeteringRectangle rectangle = new MeteringRectangle(whole, MeteringRectangle.METERING_WEIGHT_DONT_CARE); MeteringRectangle rectangle = new MeteringRectangle(whole, MeteringRectangle.METERING_WEIGHT_DONT_CARE);
mAutoFocus.resetMetering(mCharacteristics, mBuilder, rectangle); mAutoFocus.resetMetering(mCharacteristics, mBuilder, rectangle);
mAutoWhiteBalance.resetMetering(mCharacteristics, mBuilder, rectangle); mAutoWhiteBalance.resetMetering(mCharacteristics, mBuilder, rectangle);

Loading…
Cancel
Save