|
|
@ -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); |
|
|
|