Skip to content

[camerax] Implements setFocusPoint, setExposurePoint, setExposureOffset #6059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b6729ac
Start implementation of setting points + offset
camsim99 Feb 2, 2024
14d1024
Finish adding intial impls
camsim99 Feb 5, 2024
975806b
Fix crashes
camsim99 Feb 5, 2024
a7181b2
Cleanup pt1
camsim99 Feb 6, 2024
ea5f15f
Cleanup pt2
camsim99 Feb 6, 2024
5fe2976
Self review
camsim99 Feb 6, 2024
e41a825
Add tests
camsim99 Feb 7, 2024
9736287
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 7, 2024
38e88a8
Format
camsim99 Feb 7, 2024
2843626
Update readme + bump version
camsim99 Feb 7, 2024
35ebf13
Improve docs
camsim99 Feb 7, 2024
c2f75ec
Change metering point factory
camsim99 Feb 8, 2024
b8526e1
Self review
camsim99 Feb 8, 2024
928b4cd
Nits + lints
camsim99 Feb 8, 2024
3527d9e
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 9, 2024
1d4d37e
Update video cap test
camsim99 Feb 9, 2024
35ab369
Fix nit
camsim99 Feb 12, 2024
0de865a
Update packages/camera/camera_android_camerax/lib/src/android_camera_…
camsim99 Feb 14, 2024
bb13a35
Address review
camsim99 Feb 14, 2024
1a601a4
Merge remote-tracking branch 'refs/remotes/origin/camx_fe' into camx_fe
camsim99 Feb 14, 2024
2c3c9ab
Update packages/camera/camera_android_camerax/test/android_camera_cam…
camsim99 Feb 20, 2024
6652171
Use absolute paths
camsim99 Feb 20, 2024
a08eed6
Merge remote-tracking branch 'refs/remotes/origin/camx_fe' into camx_fe
camsim99 Feb 20, 2024
c35fa15
Address nit + change getExposureOffsetStepSize to return 0
camsim99 Feb 21, 2024
fac3ff6
Merge remote-tracking branch 'upstream/main' into camx_fe
camsim99 Feb 21, 2024
077ee37
Change to -1
camsim99 Feb 21, 2024
ff3fc39
Update wording
camsim99 Feb 21, 2024
fc60e77
Undo platform interface changes
camsim99 Feb 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.0+34

* Implements `setFocusPoint`, `setExposurePoint`, and `setExposureOffset`.

## 0.5.0+33

* Fixes typo in `README.md`.
Expand Down
9 changes: 4 additions & 5 deletions packages/camera/camera_android_camerax/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,19 @@ dependencies:

## Missing features and limitations


### 240p resolution configuration for video recording

240p resolution configuration for video recording is unsupported by CameraX,
and thus, the plugin will fall back to 480p if configured with a
`ResolutionPreset`.

### Exposure mode, point, & offset configuration \[[Issue #120468][120468]\]
### Exposure mode configuration \[[Issue #120468][120468]\]

`setExposureMode`, `setExposurePoint`, & `setExposureOffset` are unimplemented.
`setExposureMode`is unimplemented.

### Focus mode & point configuration \[[Issue #120467][120467]\]
### Focus mode configuration \[[Issue #120467][120467]\]

`setFocusMode` & `setFocusPoint` are unimplemented.
`setFocusMode` is unimplemented.

### Setting maximum duration and stream options for video capture

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public final class CameraAndroidCameraxPlugin implements FlutterPlugin, Activity
@VisibleForTesting @Nullable public ImageCaptureHostApiImpl imageCaptureHostApiImpl;
@VisibleForTesting @Nullable public CameraControlHostApiImpl cameraControlHostApiImpl;
@VisibleForTesting @Nullable public SystemServicesHostApiImpl systemServicesHostApiImpl;
@VisibleForTesting @Nullable public MeteringPointHostApiImpl meteringPointHostApiImpl;

@VisibleForTesting
public @Nullable DeviceOrientationManagerHostApiImpl deviceOrientationManagerHostApiImpl;
Expand Down Expand Up @@ -119,6 +120,12 @@ public void setUp(
cameraControlHostApiImpl =
new CameraControlHostApiImpl(binaryMessenger, instanceManager, context);
GeneratedCameraXLibrary.CameraControlHostApi.setup(binaryMessenger, cameraControlHostApiImpl);
GeneratedCameraXLibrary.FocusMeteringActionHostApi.setup(
binaryMessenger, new FocusMeteringActionHostApiImpl(instanceManager));
GeneratedCameraXLibrary.FocusMeteringResultHostApi.setup(
binaryMessenger, new FocusMeteringResultHostApiImpl(instanceManager));
meteringPointHostApiImpl = new MeteringPointHostApiImpl(instanceManager);
GeneratedCameraXLibrary.MeteringPointHostApi.setup(binaryMessenger, meteringPointHostApiImpl);
}

@Override
Expand Down Expand Up @@ -238,5 +245,8 @@ public void updateActivity(@Nullable Activity activity) {
if (deviceOrientationManagerHostApiImpl != null) {
deviceOrientationManagerHostApiImpl.setActivity(activity);
}
if (meteringPointHostApiImpl != null) {
meteringPointHostApiImpl.setActivity(activity);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public void onSuccess(Void voidResult) {
}

public void onFailure(Throwable t) {
if (t instanceof CameraControl.OperationCanceledException) {
// Operation was canceled due to camera being closed or a new request was submitted, which
// is not actionable and should not block a new value from potentially being submitted.
result.success(null);
return;
}
result.error(t);
}
},
Expand All @@ -94,6 +100,9 @@ public void onFailure(Throwable t) {
*
* <p>Will trigger an auto focus action and enable auto focus/auto exposure/auto white balance
* metering regions.
*
* <p>Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was
* canceled.
*/
public void startFocusAndMetering(
@NonNull CameraControl cameraControl,
Expand All @@ -117,6 +126,12 @@ public void onSuccess(FocusMeteringResult focusMeteringResult) {
}

public void onFailure(Throwable t) {
if (t instanceof CameraControl.OperationCanceledException) {
// Operation was canceled due to camera being closed or a new request was submitted, which
// is not actionable and should not block a new value from potentially being submitted.
result.success(null);
return;
}
result.error(t);
}
},
Expand Down Expand Up @@ -152,6 +167,9 @@ public void onFailure(Throwable t) {
* <p>The exposure compensation value set on the camera must be within the range of {@code
* ExposureState#getExposureCompensationRange()} for the current {@code ExposureState} for the
* call to succeed.
*
* <p>Will send a {@link GeneratedCameraXLibrary.Result} with a null result if operation was
* canceled.
*/
public void setExposureCompensationIndex(
@NonNull CameraControl cameraControl, @NonNull Long index, @NonNull Result<Long> result) {
Expand All @@ -166,6 +184,12 @@ public void onSuccess(Integer integerResult) {
}

public void onFailure(Throwable t) {
if (t instanceof CameraControl.OperationCanceledException) {
// Operation was canceled due to camera being closed or a new request was submitted, which
// is not actionable and should not block a new value from potentially being submitted.
result.success(null);
return;
}
result.error(t);
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ private CameraStateType(final int index) {
* <p>If you need to add another type to support a type S to use a LiveData<S> in this plugin,
* ensure the following is done on the Dart side:
*
* <p>* In `../lib/src/live_data.dart`, add new cases for S in
* <p>* In `camera_android_camerax/lib/src/live_data.dart`, add new cases for S in
* `_LiveDataHostApiImpl#getValueFromInstances` to get the current value of type S from a
* LiveData<S> instance and in `LiveDataFlutterApiImpl#create` to create the expected type of
* LiveData<S> when requested.
*
* <p>On the native side, ensure the following is done:
*
* <p>* Update `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for
* <p>* Make sure `LiveDataHostApiImpl#getValue` is updated to properly return identifiers for
* instances of type S. * Update `ObserverFlutterApiWrapper#onChanged` to properly handle
* receiving calls with instances of type S if a LiveData<S> instance is observed.
*/
Expand Down Expand Up @@ -146,6 +146,24 @@ private VideoResolutionFallbackRule(final int index) {
}
}

/**
* The types of capture request options this plugin currently supports.
*
* <p>If you need to add another option to support, ensure the following is done on the Dart side:
*
* <p>* In `camera_android_camerax/lib/src/capture_request_options.dart`, add new cases for this
* option in `_CaptureRequestOptionsHostApiImpl#createFromInstances` to create the expected Map
* entry of option key index and value to send to the native side.
*
* <p>On the native side, ensure the following is done:
*
* <p>* Update `CaptureRequestOptionsHostApiImpl#create` to set the correct `CaptureRequest` key
* with a valid value type for this option.
*
* <p>See https://developer.android.com/reference/android/hardware/camera2/CaptureRequest for the
* sorts of capture request options that can be supported via CameraX's interoperability with
* Camera2.
*/
public enum CaptureRequestKeySupportedType {
CONTROL_AE_LOCK(0);

Expand Down Expand Up @@ -3899,7 +3917,11 @@ public void create(@NonNull Long identifierArg, @NonNull Reply<Void> callback) {
public interface MeteringPointHostApi {

void create(
@NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size);
@NonNull Long identifier,
@NonNull Double x,
@NonNull Double y,
@Nullable Double size,
@NonNull Long cameraInfoId);

@NonNull
Double getDefaultPointSize();
Expand Down Expand Up @@ -3927,12 +3949,14 @@ static void setup(
Double xArg = (Double) args.get(1);
Double yArg = (Double) args.get(2);
Double sizeArg = (Double) args.get(3);
Number cameraInfoIdArg = (Number) args.get(4);
try {
api.create(
(identifierArg == null) ? null : identifierArg.longValue(),
xArg,
yArg,
sizeArg);
sizeArg,
(cameraInfoIdArg == null) ? null : cameraInfoIdArg.longValue());
wrapped.add(0, null);
} catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@

package io.flutter.plugins.camerax;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.Display;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.DisplayOrientedMeteringPointFactory;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
import io.flutter.plugins.camerax.GeneratedCameraXLibrary.MeteringPointHostApi;
import java.util.Objects;

/**
* Host API implementation for {@link MeteringPoint}.
Expand All @@ -25,29 +32,51 @@ public class MeteringPointHostApiImpl implements MeteringPointHostApi {
/** Proxy for constructor and static methods of {@link MeteringPoint}. */
@VisibleForTesting
public static class MeteringPointProxy {
Activity activity;

/**
* Creates a surface oriented {@link MeteringPoint} with the specified x, y, and size.
*
* <p>A {@link SurfaceOrientedMeteringPointFactory} is used to construct the {@link
* MeteringPoint} because underlying the camera preview that this plugin uses is a Flutter
* texture that is backed by a {@link Surface} created by the Flutter Android embedding.
* <p>A {@link DisplayOrientedMeteringPointFactory} is used to construct the {@link
* MeteringPoint} because this factory handles the transformation of specified coordinates based
* on camera information and the device orientation automatically.
*/
@NonNull
public MeteringPoint create(@NonNull Double x, @NonNull Double y, @Nullable Double size) {
SurfaceOrientedMeteringPointFactory factory = getSurfaceOrientedMeteringPointFactory(1f, 1f);
public MeteringPoint create(
@NonNull Double x,
@NonNull Double y,
@Nullable Double size,
@NonNull CameraInfo cameraInfo) {
Display display = null;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
display = activity.getDisplay();
} else {
display = getDefaultDisplayForAndroidVersionBelowR(activity);
}

DisplayOrientedMeteringPointFactory factory =
getDisplayOrientedMeteringPointFactory(display, cameraInfo, 1f, 1f);

if (size == null) {
return factory.createPoint(x.floatValue(), y.floatValue());
} else {
return factory.createPoint(x.floatValue(), y.floatValue(), size.floatValue());
}
}

@NonNull
@SuppressWarnings("deprecation")
private Display getDefaultDisplayForAndroidVersionBelowR(@NonNull Activity activity) {
return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
}

@VisibleForTesting
@NonNull
public SurfaceOrientedMeteringPointFactory getSurfaceOrientedMeteringPointFactory(
float width, float height) {
return new SurfaceOrientedMeteringPointFactory(width, height);
public DisplayOrientedMeteringPointFactory getDisplayOrientedMeteringPointFactory(
@NonNull Display display, @NonNull CameraInfo cameraInfo, float width, float height) {
return new DisplayOrientedMeteringPointFactory(display, cameraInfo, width, height);
}

/**
Expand Down Expand Up @@ -81,10 +110,23 @@ public MeteringPointHostApiImpl(@NonNull InstanceManager instanceManager) {
this.proxy = proxy;
}

public void setActivity(@NonNull Activity activity) {
this.proxy.activity = activity;
}

@Override
public void create(
@NonNull Long identifier, @NonNull Double x, @NonNull Double y, @Nullable Double size) {
MeteringPoint meteringPoint = proxy.create(x, y, size);
@NonNull Long identifier,
@NonNull Double x,
@NonNull Double y,
@Nullable Double size,
@NonNull Long cameraInfoId) {
MeteringPoint meteringPoint =
proxy.create(
x,
y,
size,
(CameraInfo) Objects.requireNonNull(instanceManager.getInstance(cameraInfoId)));
instanceManager.addDartCreatedInstance(meteringPoint, identifier);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
mock(SystemServicesHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
mock(MeteringPointHostApiImpl.class);
final ArgumentCaptor<PermissionsRegistry> permissionsRegistryCaptor =
ArgumentCaptor.forClass(PermissionsRegistry.class);

Expand All @@ -103,13 +105,15 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;

plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onAttachedToActivity(activityPluginBinding);

// Check Activity references are set.
verify(mockSystemServicesHostApiImpl).setActivity(mockActivity);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity);
verify(mockMeteringPointHostApiImpl).setActivity(mockActivity);

// Check permissions registry reference is set.
verify(mockSystemServicesHostApiImpl)
Expand All @@ -129,11 +133,14 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
mock(SystemServicesHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
mock(MeteringPointHostApiImpl.class);

plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;

plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onDetachedFromActivityForConfigChanges();
Expand All @@ -142,6 +149,7 @@ public void onAttachedToActivity_setsActivityAsNeededAndPermissionsRegistry() {
verify(mockLiveDataHostApiImpl).setLifecycleOwner(null);
verify(mockSystemServicesHostApiImpl).setActivity(null);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null);
verify(mockMeteringPointHostApiImpl).setActivity(null);
}

@Test
Expand Down Expand Up @@ -251,6 +259,8 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
mock(CameraControlHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
mock(MeteringPointHostApiImpl.class);
final ArgumentCaptor<PermissionsRegistry> permissionsRegistryCaptor =
ArgumentCaptor.forClass(PermissionsRegistry.class);

Expand All @@ -265,6 +275,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
plugin.imageAnalysisHostApiImpl = mockImageAnalysisHostApiImpl;
plugin.cameraControlHostApiImpl = mockCameraControlHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;
plugin.liveDataHostApiImpl = mock(LiveDataHostApiImpl.class);

plugin.onAttachedToEngine(flutterPluginBinding);
Expand All @@ -273,6 +284,7 @@ public void onReattachedToActivityForConfigChanges_setsActivityAndPermissionsReg
// Check Activity references are set.
verify(mockSystemServicesHostApiImpl).setActivity(mockActivity);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(mockActivity);
verify(mockMeteringPointHostApiImpl).setActivity(mockActivity);

// Check Activity as Context references are set.
verify(mockProcessCameraProviderHostApiImpl).setContext(mockActivity);
Expand Down Expand Up @@ -300,11 +312,14 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc
final LiveDataHostApiImpl mockLiveDataHostApiImpl = mock(LiveDataHostApiImpl.class);
final DeviceOrientationManagerHostApiImpl mockDeviceOrientationManagerHostApiImpl =
mock(DeviceOrientationManagerHostApiImpl.class);
final MeteringPointHostApiImpl mockMeteringPointHostApiImpl =
mock(MeteringPointHostApiImpl.class);

plugin.processCameraProviderHostApiImpl = mockProcessCameraProviderHostApiImpl;
plugin.liveDataHostApiImpl = mockLiveDataHostApiImpl;
plugin.systemServicesHostApiImpl = mockSystemServicesHostApiImpl;
plugin.deviceOrientationManagerHostApiImpl = mockDeviceOrientationManagerHostApiImpl;
plugin.meteringPointHostApiImpl = mockMeteringPointHostApiImpl;

plugin.onAttachedToEngine(flutterPluginBinding);
plugin.onDetachedFromActivityForConfigChanges();
Expand All @@ -313,6 +328,7 @@ public void onDetachedFromActivity_removesReferencesToActivityPluginBindingAndAc
verify(mockLiveDataHostApiImpl).setLifecycleOwner(null);
verify(mockSystemServicesHostApiImpl).setActivity(null);
verify(mockDeviceOrientationManagerHostApiImpl).setActivity(null);
verify(mockMeteringPointHostApiImpl).setActivity(null);
}

@Test
Expand Down
Loading