parent
03b0fb5971
commit
47b7cdd514
@ -0,0 +1,55 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
import com.otaliastudios.cameraview.engine.action.BaseAction; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public abstract class BaseMeter extends BaseAction { |
||||
|
||||
private final List<MeteringRectangle> areas; |
||||
private boolean isSuccessful; |
||||
private boolean skipIfPossible; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected BaseMeter(@NonNull List<MeteringRectangle> areas, boolean skipIfPossible) { |
||||
this.areas = areas; |
||||
this.skipIfPossible = skipIfPossible; |
||||
} |
||||
|
||||
@Override |
||||
protected final void onStart(@NonNull ActionHolder holder) { |
||||
super.onStart(holder); |
||||
boolean isSkipped = skipIfPossible && checkShouldSkip(holder); |
||||
boolean isSupported = checkIsSupported(holder); |
||||
if (isSupported && !isSkipped) { |
||||
onStarted(holder, areas); |
||||
} else { |
||||
setSuccessful(true); |
||||
setState(STATE_COMPLETED); |
||||
} |
||||
} |
||||
|
||||
protected abstract void onStarted(@NonNull ActionHolder holder, |
||||
@NonNull List<MeteringRectangle> areas); |
||||
|
||||
protected abstract boolean checkShouldSkip(@NonNull ActionHolder holder); |
||||
|
||||
protected abstract boolean checkIsSupported(@NonNull ActionHolder holder); |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected void setSuccessful(boolean successful) { |
||||
isSuccessful = successful; |
||||
} |
||||
|
||||
public boolean isSuccessful() { |
||||
return isSuccessful; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.graphics.Rect; |
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
import com.otaliastudios.cameraview.engine.action.BaseAction; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public abstract class BaseReset extends BaseAction { |
||||
|
||||
private boolean resetArea; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected BaseReset(boolean resetArea) { |
||||
this.resetArea = resetArea; |
||||
} |
||||
|
||||
@Override |
||||
protected final void onStart(@NonNull ActionHolder holder) { |
||||
super.onStart(holder); |
||||
MeteringRectangle area = null; |
||||
if (resetArea) { |
||||
Rect rect = readCharacteristic(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE, new Rect()); |
||||
area = new MeteringRectangle(rect, MeteringRectangle.METERING_WEIGHT_DONT_CARE); |
||||
} |
||||
onStarted(holder, area); |
||||
} |
||||
|
||||
protected abstract void onStarted(@NonNull ActionHolder holder, |
||||
@Nullable MeteringRectangle area); |
||||
} |
@ -0,0 +1,141 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class ExposureMeter extends BaseMeter { |
||||
|
||||
private static final String TAG = ExposureMeter.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
private static final int STATE_WAITING_PRECAPTURE = 0; |
||||
private static final int STATE_WAITING_PRECAPTURE_END = 1; |
||||
|
||||
public ExposureMeter(@NonNull List<MeteringRectangle> areas, boolean skipIfPossible) { |
||||
super(areas, skipIfPossible); |
||||
} |
||||
|
||||
// TODO set trigger to null?
|
||||
|
||||
@Override |
||||
protected boolean checkIsSupported(@NonNull ActionHolder holder) { |
||||
// In our case, this means checking if we support the AE precapture trigger.
|
||||
boolean isNotLegacy = readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) |
||||
!= CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
||||
Integer aeMode = holder.getBuilder(this).get(CaptureRequest.CONTROL_AE_MODE); |
||||
boolean isAEOn = aeMode != null && |
||||
(aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH |
||||
|| aeMode == CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE |
||||
|| aeMode == 5 /* CameraCharacteristics.CONTROL_AE_MODE_ON_EXTERNAL_FLASH, API 28 */); |
||||
boolean result = isNotLegacy && isAEOn; |
||||
LOG.i("checkIsSupported:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull ActionHolder holder) { |
||||
Integer aeState = holder.getLastResult(this).get(CaptureResult.CONTROL_AE_STATE); |
||||
boolean result = aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED; |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @NonNull List<MeteringRectangle> areas) { |
||||
// Launch the precapture trigger.
|
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, |
||||
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); |
||||
|
||||
// Check the regions.
|
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AE, 0); |
||||
if (!areas.isEmpty() && maxRegions > 0) { |
||||
int max = Math.min(maxRegions, areas.size()); |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_REGIONS, |
||||
areas.subList(0, max).toArray(new MeteringRectangle[]{})); |
||||
} |
||||
|
||||
// Apply
|
||||
holder.applyBuilder(this); |
||||
setState(STATE_WAITING_PRECAPTURE); |
||||
} |
||||
|
||||
@Override |
||||
public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, |
||||
@NonNull TotalCaptureResult result) { |
||||
super.onCaptureCompleted(holder, request, result); |
||||
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); |
||||
Integer aeTriggerState = result.get(CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER); |
||||
LOG.i("onCaptureCompleted:", "aeState:", aeState, "aeTriggerState:", aeTriggerState); |
||||
if (aeState == null) return; |
||||
|
||||
if (getState() == STATE_WAITING_PRECAPTURE) { |
||||
switch (aeState) { |
||||
case CaptureResult.CONTROL_AE_STATE_PRECAPTURE: { |
||||
setState(STATE_WAITING_PRECAPTURE_END); |
||||
break; |
||||
} |
||||
case CaptureResult.CONTROL_AE_STATE_CONVERGED: |
||||
case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: { |
||||
// PRECAPTURE is a transient state. Being here might mean that precapture run
|
||||
// and was successful, OR that the trigger was not even received yet. To
|
||||
// distinguish, check the trigger state.
|
||||
if (aeTriggerState != null |
||||
&& aeTriggerState == CaptureResult.CONTROL_AE_PRECAPTURE_TRIGGER_START) { |
||||
setSuccessful(true); |
||||
setState(STATE_COMPLETED); |
||||
} |
||||
break; |
||||
} |
||||
case CaptureResult.CONTROL_AE_STATE_LOCKED: { |
||||
// There's nothing we can do, AE was locked, triggers are ignored.
|
||||
setSuccessful(false); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureResult.CONTROL_AE_STATE_INACTIVE: |
||||
case CaptureResult.CONTROL_AE_STATE_SEARCHING: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (getState() == STATE_WAITING_PRECAPTURE_END) { |
||||
switch (aeState) { |
||||
case CaptureResult.CONTROL_AE_STATE_CONVERGED: |
||||
case CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED: { |
||||
setSuccessful(true); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureResult.CONTROL_AE_STATE_LOCKED: { |
||||
// There's nothing we can do, AE was locked, triggers are ignored.
|
||||
setSuccessful(false); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureResult.CONTROL_AE_STATE_PRECAPTURE: |
||||
case CaptureResult.CONTROL_AE_STATE_INACTIVE: |
||||
case CaptureResult.CONTROL_AE_STATE_SEARCHING: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,50 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class ExposureReset extends BaseReset { |
||||
|
||||
private static final String TAG = ExposureReset.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public ExposureReset(boolean resetArea) { |
||||
super(resetArea); |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @Nullable MeteringRectangle area) { |
||||
boolean changed = false; |
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AE, 0); |
||||
if (area != null && maxRegions > 0) { |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_REGIONS, |
||||
new MeteringRectangle[]{area}); |
||||
changed = true; |
||||
} |
||||
|
||||
// NOTE: precapture might not be supported, in which case I think it will be ignored.
|
||||
Integer trigger = holder.getBuilder(this).get(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER); |
||||
LOG.w("onStarted:", "current precapture trigger is", trigger); |
||||
if (trigger == null || trigger == CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) { |
||||
LOG.w("onStarted:", "canceling precapture."); |
||||
int newTrigger = Build.VERSION.SDK_INT >= 23 |
||||
? CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL |
||||
: CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE; |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, newTrigger); |
||||
changed = true; |
||||
} |
||||
|
||||
if (changed) holder.applyBuilder(this); |
||||
setState(STATE_COMPLETED); |
||||
} |
||||
} |
@ -0,0 +1,89 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class FocusMeter extends BaseMeter { |
||||
|
||||
private static final String TAG = FocusMeter.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public FocusMeter(@NonNull List<MeteringRectangle> areas, boolean skipIfPossible) { |
||||
super(areas, skipIfPossible); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkIsSupported(@NonNull ActionHolder holder) { |
||||
// Exclude OFF and EDOF as per docs. These do no support the trigger.
|
||||
Integer afMode = holder.getBuilder(this).get(CaptureRequest.CONTROL_AF_MODE); |
||||
boolean result = afMode != null && |
||||
(afMode == CameraCharacteristics.CONTROL_AF_MODE_AUTO |
||||
|| afMode == CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_PICTURE |
||||
|| afMode == CameraCharacteristics.CONTROL_AF_MODE_CONTINUOUS_VIDEO |
||||
|| afMode == CameraCharacteristics.CONTROL_AF_MODE_MACRO); |
||||
LOG.i("checkIsSupported:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull ActionHolder holder) { |
||||
Integer afState = holder.getLastResult(this).get(CaptureResult.CONTROL_AF_STATE); |
||||
boolean result = afState != null && |
||||
(afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || |
||||
afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED); |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @NonNull List<MeteringRectangle> areas) { |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_TRIGGER, |
||||
CaptureRequest.CONTROL_AF_TRIGGER_START); |
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AF, 0); |
||||
if (!areas.isEmpty() && maxRegions > 0) { |
||||
int max = Math.min(maxRegions, areas.size()); |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_REGIONS, |
||||
areas.subList(0, max).toArray(new MeteringRectangle[]{})); |
||||
} |
||||
holder.applyBuilder(this); |
||||
} |
||||
|
||||
// TODO Set trigger to null?
|
||||
|
||||
@Override |
||||
public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, |
||||
@NonNull TotalCaptureResult result) { |
||||
super.onCaptureCompleted(holder, request, result); |
||||
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); |
||||
LOG.i("onCaptureCompleted:", "afState:", afState); |
||||
if (afState == null) return; |
||||
switch (afState) { |
||||
case CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED: { |
||||
setSuccessful(true); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED: { |
||||
setSuccessful(false); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AF_STATE_INACTIVE: break; |
||||
case CaptureRequest.CONTROL_AF_STATE_ACTIVE_SCAN: break; |
||||
default: break; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class FocusReset extends BaseReset { |
||||
|
||||
private static final String TAG = FocusReset.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public FocusReset(boolean resetArea) { |
||||
super(resetArea); |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @Nullable MeteringRectangle area) { |
||||
boolean changed = false; |
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AF, 0); |
||||
if (area != null && maxRegions > 0) { |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_REGIONS, |
||||
new MeteringRectangle[]{area}); |
||||
changed = true; |
||||
} |
||||
|
||||
// NOTE: trigger might not be supported, in which case I think it will be ignored.
|
||||
Integer trigger = holder.getBuilder(this).get(CaptureRequest.CONTROL_AF_TRIGGER); |
||||
LOG.w("onStarted:", "current focus trigger is", trigger); |
||||
if (trigger == null || trigger == CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START) { |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AF_TRIGGER, |
||||
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL); |
||||
changed = true; |
||||
} |
||||
|
||||
if (changed) holder.applyBuilder(this); |
||||
setState(STATE_COMPLETED); |
||||
} |
||||
} |
@ -0,0 +1,251 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.graphics.PointF; |
||||
import android.graphics.Rect; |
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.engine.CameraEngine; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
import com.otaliastudios.cameraview.engine.action.ActionWrapper; |
||||
import com.otaliastudios.cameraview.engine.action.Actions; |
||||
import com.otaliastudios.cameraview.engine.action.BaseAction; |
||||
import com.otaliastudios.cameraview.engine.offset.Axis; |
||||
import com.otaliastudios.cameraview.engine.offset.Reference; |
||||
import com.otaliastudios.cameraview.size.AspectRatio; |
||||
import com.otaliastudios.cameraview.size.Size; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class MeterAction extends ActionWrapper { |
||||
|
||||
private List<BaseMeter> meters; |
||||
private BaseAction action; |
||||
private ActionHolder holder; |
||||
private final PointF point; |
||||
private final CameraEngine engine; |
||||
private final boolean skipIfPossible; |
||||
|
||||
public MeterAction(@NonNull CameraEngine engine, @Nullable PointF point, |
||||
boolean skipIfPossible) { |
||||
this.point = point; |
||||
this.engine = engine; |
||||
this.skipIfPossible = skipIfPossible; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public BaseAction getAction() { |
||||
return action; |
||||
} |
||||
|
||||
@Nullable |
||||
public PointF getPoint() { |
||||
return point; |
||||
} |
||||
|
||||
public boolean isSuccessful() { |
||||
for (BaseMeter meter : meters) { |
||||
if (!meter.isSuccessful()) { |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
protected void onStart(@NonNull ActionHolder holder) { |
||||
initialize(holder); |
||||
super.onStart(holder); |
||||
} |
||||
|
||||
private void initialize(@NonNull ActionHolder holder) { |
||||
this.holder = holder; |
||||
List<MeteringRectangle> areas = new ArrayList<>(); |
||||
if (point != null) { |
||||
// 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.
|
||||
// Will keep updating these two below.
|
||||
final PointF referencePoint = new PointF(point.x, point.y); |
||||
Size referenceSize = engine.getPreview().getSurfaceSize(); |
||||
|
||||
// 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 = engine.getPreviewStreamSize(Reference.SENSOR); |
||||
//noinspection ConstantConditions
|
||||
MeteringRectangle area1 = createMeteringRectangle(referenceSize, referencePoint, |
||||
visibleSize, 0.05F, 1000); |
||||
MeteringRectangle area2 = createMeteringRectangle(referenceSize, referencePoint, |
||||
visibleSize, 0.1F, 100); |
||||
areas.add(area1); |
||||
areas.add(area2); |
||||
} |
||||
|
||||
BaseMeter ae = new ExposureMeter(areas, skipIfPossible); |
||||
BaseMeter af = new FocusMeter(areas, skipIfPossible); |
||||
BaseMeter awb = new WhiteBalanceMeter(areas, skipIfPossible); |
||||
meters = Arrays.asList(ae, af, awb); |
||||
action = Actions.together(ae, af, awb); |
||||
} |
||||
|
||||
@SuppressWarnings("UnnecessaryLocalVariable") |
||||
@NonNull |
||||
private Size applyPreviewCropping(@NonNull Size referenceSize, @NonNull PointF referencePoint) { |
||||
Size previewStreamSize = engine.getPreviewStreamSize(Reference.VIEW); |
||||
Size previewSurfaceSize = referenceSize; |
||||
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 previewSurfaceAspectRatio = AspectRatio.of(previewSurfaceSize); |
||||
if (engine.getPreview().isCropping()) { |
||||
if (previewStreamAspectRatio.toFloat() > previewSurfaceAspectRatio.toFloat()) { |
||||
// Stream is larger. The x coordinate must be increased: a touch on the left side
|
||||
// of the surface is not on the left size of stream (it's more to the right).
|
||||
float scale = previewStreamAspectRatio.toFloat() / previewSurfaceAspectRatio.toFloat(); |
||||
referencePoint.x += previewSurfaceSize.getWidth() * (scale - 1F) / 2F; |
||||
referenceWidth = Math.round(previewSurfaceSize.getWidth() * scale); |
||||
} else { |
||||
// 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).
|
||||
float scale = previewSurfaceAspectRatio.toFloat() / previewStreamAspectRatio.toFloat(); |
||||
referencePoint.y += previewSurfaceSize.getHeight() * (scale - 1F) / 2F; |
||||
referenceHeight = Math.round(previewSurfaceSize.getHeight() * scale); |
||||
} |
||||
} |
||||
return new Size(referenceWidth, referenceHeight); |
||||
} |
||||
|
||||
@SuppressWarnings("ConstantConditions") |
||||
@NonNull |
||||
private Size applyPreviewScale(@NonNull Size referenceSize, @NonNull PointF referencePoint) { |
||||
// 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 = engine.getPreviewStreamSize(Reference.VIEW); |
||||
referencePoint.x *= (float) previewStreamSize.getWidth() / referenceSize.getWidth(); |
||||
referencePoint.y *= (float) previewStreamSize.getHeight() / referenceSize.getHeight(); |
||||
return previewStreamSize; |
||||
} |
||||
|
||||
@SuppressWarnings("SuspiciousNameCombination") |
||||
@NonNull |
||||
private Size applyPreviewToSensorRotation(@NonNull Size referenceSize, @NonNull PointF referencePoint) { |
||||
// Not elegant, but the sin/cos way was failing for some reason.
|
||||
int angle = engine.getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE); |
||||
boolean flip = angle % 180 != 0; |
||||
float tempX = referencePoint.x; |
||||
float tempY = referencePoint.y; |
||||
if (angle == 0) { |
||||
referencePoint.x = tempX; |
||||
referencePoint.y = tempY; |
||||
} else if (angle == 90) { |
||||
referencePoint.x = tempY; |
||||
referencePoint.y = referenceSize.getWidth() - tempX; |
||||
} else if (angle == 180) { |
||||
referencePoint.x = referenceSize.getWidth() - tempX; |
||||
referencePoint.y = referenceSize.getHeight() - tempY; |
||||
} else if (angle == 270) { |
||||
referencePoint.x = referenceSize.getHeight() - tempY; |
||||
referencePoint.y = tempX; |
||||
} else { |
||||
throw new IllegalStateException("Unexpected angle " + angle); |
||||
} |
||||
return flip ? referenceSize.flip() : referenceSize; |
||||
} |
||||
|
||||
@NonNull |
||||
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
|
||||
Rect cropRect = holder.getBuilder(this).get(CaptureRequest.SCALER_CROP_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(); |
||||
int cropRectHeight = cropRect == null ? referenceSize.getHeight() : cropRect.height(); |
||||
// The stream is always centered inside the crop region, and one of the dimensions
|
||||
// should always match. We just increase the other one.
|
||||
referencePoint.x += (cropRectWidth - referenceSize.getWidth()) / 2F; |
||||
referencePoint.y += (cropRectHeight - referenceSize.getHeight()) / 2F; |
||||
return new Size(cropRectWidth, cropRectHeight); |
||||
} |
||||
|
||||
@NonNull |
||||
private Size applyActiveArrayCoordinates(@NonNull Size referenceSize, @NonNull PointF referencePoint) { |
||||
// 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 = holder.getBuilder(this).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 = readCharacteristic(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE, |
||||
new Rect(0, 0, referenceSize.getWidth(), referenceSize.getHeight())); |
||||
return new Size(activeRect.width(), activeRect.height()); |
||||
} |
||||
|
||||
/** |
||||
* Creates a metering rectangle around the center point. |
||||
* The rectangle will have a size that's a factor of the visible width and height. |
||||
* The rectangle will also be constrained to be inside the given boundaries, |
||||
* so we don't exceed them in case the center point is exactly on one side for example. |
||||
* @return a new rectangle |
||||
*/ |
||||
@NonNull |
||||
private MeteringRectangle createMeteringRectangle( |
||||
@NonNull Size boundaries, |
||||
@NonNull PointF center, |
||||
@NonNull Size visibleSize, |
||||
float factor, |
||||
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( |
||||
(int) rectangleLeft, |
||||
(int) rectangleTop, |
||||
(int) rectangleWidth, |
||||
(int) rectangleHeight, |
||||
weight |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
import com.otaliastudios.cameraview.engine.action.ActionWrapper; |
||||
import com.otaliastudios.cameraview.engine.action.Actions; |
||||
import com.otaliastudios.cameraview.engine.action.BaseAction; |
||||
import com.otaliastudios.cameraview.engine.lock.ExposureLock; |
||||
import com.otaliastudios.cameraview.engine.lock.FocusLock; |
||||
import com.otaliastudios.cameraview.engine.lock.WhiteBalanceLock; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class MeterResetAction extends ActionWrapper { |
||||
|
||||
private final BaseAction action; |
||||
|
||||
public MeterResetAction(boolean resetAreas) { |
||||
this.action = Actions.together( |
||||
new ExposureReset(resetAreas), |
||||
new FocusReset(resetAreas), |
||||
new WhiteBalanceReset(resetAreas) |
||||
); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public BaseAction getAction() { |
||||
return action; |
||||
} |
||||
} |
@ -0,0 +1,86 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
import com.otaliastudios.cameraview.engine.metering.Parameter; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class WhiteBalanceMeter extends BaseMeter { |
||||
|
||||
private static final String TAG = WhiteBalanceMeter.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public WhiteBalanceMeter(@NonNull List<MeteringRectangle> areas, boolean skipIfPossible) { |
||||
super(areas, skipIfPossible); |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkIsSupported(@NonNull ActionHolder holder) { |
||||
boolean isNotLegacy = readCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL, -1) |
||||
!= CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY; |
||||
Integer awbMode = holder.getBuilder(this).get(CaptureRequest.CONTROL_AWB_MODE); |
||||
boolean result = isNotLegacy && awbMode != null && awbMode == CaptureRequest.CONTROL_AWB_MODE_AUTO; |
||||
LOG.i("checkIsSupported:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected boolean checkShouldSkip(@NonNull ActionHolder holder) { |
||||
Integer awbState = holder.getLastResult(this).get(CaptureResult.CONTROL_AWB_STATE); |
||||
boolean result = awbState != null && awbState == CaptureRequest.CONTROL_AWB_STATE_CONVERGED; |
||||
LOG.i("checkShouldSkip:", result); |
||||
return result; |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @NonNull List<MeteringRectangle> areas) { |
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB, 0); |
||||
if (!areas.isEmpty() && maxRegions > 0) { |
||||
int max = Math.min(maxRegions, areas.size()); |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AWB_REGIONS, |
||||
areas.subList(0, max).toArray(new MeteringRectangle[]{})); |
||||
holder.applyBuilder(this); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onCaptureCompleted(@NonNull ActionHolder holder, @NonNull CaptureRequest request, |
||||
@NonNull TotalCaptureResult result) { |
||||
super.onCaptureCompleted(holder, request, result); |
||||
Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE); |
||||
LOG.i("onCaptureCompleted:", "awbState:", awbState); |
||||
if (awbState == null) return; |
||||
|
||||
switch (awbState) { |
||||
case CaptureRequest.CONTROL_AWB_STATE_CONVERGED: { |
||||
setSuccessful(true); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AWB_STATE_LOCKED: { |
||||
// Nothing we can do if AWB was locked.
|
||||
setSuccessful(false); |
||||
setState(STATE_COMPLETED); |
||||
break; |
||||
} |
||||
case CaptureRequest.CONTROL_AWB_STATE_INACTIVE: |
||||
case CaptureRequest.CONTROL_AWB_STATE_SEARCHING: { |
||||
// Wait...
|
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
package com.otaliastudios.cameraview.engine.meter; |
||||
|
||||
import android.hardware.camera2.CameraCharacteristics; |
||||
import android.hardware.camera2.CaptureRequest; |
||||
import android.hardware.camera2.CaptureResult; |
||||
import android.hardware.camera2.TotalCaptureResult; |
||||
import android.hardware.camera2.params.MeteringRectangle; |
||||
import android.os.Build; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.annotation.RequiresApi; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.engine.action.ActionHolder; |
||||
|
||||
import java.util.List; |
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public class WhiteBalanceReset extends BaseReset { |
||||
|
||||
private static final String TAG = WhiteBalanceReset.class.getSimpleName(); |
||||
private static final CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
public WhiteBalanceReset(boolean resetArea) { |
||||
super(resetArea); |
||||
} |
||||
|
||||
@Override |
||||
protected void onStarted(@NonNull ActionHolder holder, @Nullable MeteringRectangle area) { |
||||
int maxRegions = readCharacteristic(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB, 0); |
||||
if (area != null && maxRegions > 0) { |
||||
holder.getBuilder(this).set(CaptureRequest.CONTROL_AWB_REGIONS, new MeteringRectangle[]{area}); |
||||
holder.applyBuilder(this); |
||||
} |
||||
setState(STATE_COMPLETED); |
||||
} |
||||
} |
Loading…
Reference in new issue