Skip to content

Commit

Permalink
[camera] Allow logical cameras to use all physical cameras via zoom o…
Browse files Browse the repository at this point in the history
…n android 11+ (flutter#6150)

* Adding support for the android 11+ Camera2 CONTROL_ZOOM_RATIO_RANGE camera preference. This preference is essential for supporting Logical cameras on the rear which after android 11 wrap multiple cameras (ultrawide, normal, superzoom, etc) into a single rear facing camera.

This updates change how zoom functions as well as how min and max zoom is set to be compliant with those updates as long as the user device is on android 11+.

* Adding updates to the changelog and pubspec to reflect the zoom and camera updates.

* comment cleanup for min and max zoom ratio functions

* Pull request fixes

* Update packages/camera/camera_android/CHANGELOG.md

Co-authored-by: Camille Simon <[email protected]>

* Update packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/features/zoomlevel/ZoomUtils.java

Co-authored-by: Camille Simon <[email protected]>

* Updating comments from PR

* Fixing variable name formatting, and comment structures

* Fixing variable name formatting, and comment structures

* Autogormatter updates

Co-authored-by: Camille Simon <[email protected]>
Co-authored-by: stuartmorgan <[email protected]>
  • Loading branch information
3 people authored Jan 17, 2023
1 parent 56ab33f commit e85e0f2
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 208 deletions.
5 changes: 5 additions & 0 deletions packages/camera/camera_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.10.2+2

* Fixes zoom computation for virtual cameras hiding physical cameras in Android 11+.
* Removes the unused CameraZoom class from the codebase.

## 0.10.2+1

* Updates code for stricter lint checks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,34 @@ public interface CameraProperties {
* android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key.
*
* @return Float Maximum ratio between both active area width and crop region width, and active
* area height and crop region height
* area height and crop region height.
*/
Float getScalerAvailableMaxDigitalZoom();

/**
* Returns the minimum ratio between the default camera zoom setting and all of the available
* zoom.
*
* <p>By default maps to the @see
* android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's lower value.
*
* @return Float Minimum ratio between the default zoom ratio and the minimum possible zoom.
*/
@RequiresApi(api = VERSION_CODES.R)
Float getScalerMinZoomRatio();

/**
* Returns the maximum ratio between the default camera zoom setting and all of the available
* zoom.
*
* <p>By default maps to the @see
* android.hardware.camera2.CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE key's upper value.
*
* @return Float Maximum ratio between the default zoom ratio and the maximum possible zoom.
*/
@RequiresApi(api = VERSION_CODES.R)
Float getScalerMaxZoomRatio();

/**
* Returns the area of the image sensor which corresponds to active pixels after any geometric
* distortion correction has been applied.
Expand Down Expand Up @@ -315,6 +339,18 @@ public Float getScalerAvailableMaxDigitalZoom() {
return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
}

@RequiresApi(api = VERSION_CODES.R)
@Override
public Float getScalerMaxZoomRatio() {
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getUpper();
}

@RequiresApi(api = VERSION_CODES.R)
@Override
public Float getScalerMinZoomRatio() {
return cameraCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE).getLower();
}

@Override
public Rect getSensorInfoActiveArraySize() {
return cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@

import android.graphics.Rect;
import android.hardware.camera2.CaptureRequest;
import android.os.Build;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.features.CameraFeature;

/** Controls the zoom configuration on the {@link android.hardware.camera2} API. */
public class ZoomLevelFeature extends CameraFeature<Float> {
private static final float MINIMUM_ZOOM_LEVEL = 1.0f;
private static final Float DEFAULT_ZOOM_LEVEL = 1.0f;
private final boolean hasSupport;
private final Rect sensorArraySize;
private Float currentSetting = MINIMUM_ZOOM_LEVEL;
private Float maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
private Float currentSetting = DEFAULT_ZOOM_LEVEL;
private Float minimumZoomLevel = currentSetting;
private Float maximumZoomLevel;

/**
* Creates a new instance of the {@link ZoomLevelFeature}.
Expand All @@ -28,18 +30,24 @@ public ZoomLevelFeature(CameraProperties cameraProperties) {
sensorArraySize = cameraProperties.getSensorInfoActiveArraySize();

if (sensorArraySize == null) {
maximumZoomLevel = MINIMUM_ZOOM_LEVEL;
maximumZoomLevel = minimumZoomLevel;
hasSupport = false;
return;
}
// On Android 11+ CONTROL_ZOOM_RATIO_RANGE should be use to get the zoom ratio directly as minimum zoom does not have to be 1.0f.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
minimumZoomLevel = cameraProperties.getScalerMinZoomRatio();
maximumZoomLevel = cameraProperties.getScalerMaxZoomRatio();
} else {
minimumZoomLevel = DEFAULT_ZOOM_LEVEL;
Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom();
maximumZoomLevel =
((maxDigitalZoom == null) || (maxDigitalZoom < minimumZoomLevel))
? minimumZoomLevel
: maxDigitalZoom;
}

Float maxDigitalZoom = cameraProperties.getScalerAvailableMaxDigitalZoom();
maximumZoomLevel =
((maxDigitalZoom == null) || (maxDigitalZoom < MINIMUM_ZOOM_LEVEL))
? MINIMUM_ZOOM_LEVEL
: maxDigitalZoom;

hasSupport = (Float.compare(maximumZoomLevel, MINIMUM_ZOOM_LEVEL) > 0);
hasSupport = (Float.compare(maximumZoomLevel, minimumZoomLevel) > 0);
}

@Override
Expand Down Expand Up @@ -67,11 +75,19 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
if (!checkIsSupported()) {
return;
}

final Rect computedZoom =
ZoomUtils.computeZoom(
currentSetting, sensorArraySize, MINIMUM_ZOOM_LEVEL, maximumZoomLevel);
requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
// On Android 11+ CONTROL_ZOOM_RATIO can be set to a zoom ratio and the camera feed will compute
// how to zoom on its own accounting for multiple logical cameras.
// Prior the image cropping window must be calculated and set manually.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
requestBuilder.set(
CaptureRequest.CONTROL_ZOOM_RATIO,
ZoomUtils.computeZoomRatio(currentSetting, minimumZoomLevel, maximumZoomLevel));
} else {
final Rect computedZoom =
ZoomUtils.computeZoomRect(
currentSetting, sensorArraySize, minimumZoomLevel, maximumZoomLevel);
requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
}
}

/**
Expand All @@ -80,7 +96,7 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
* @return The minimum zoom level.
*/
public float getMinimumZoomLevel() {
return MINIMUM_ZOOM_LEVEL;
return minimumZoomLevel;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,18 @@ final class ZoomUtils {
* Computes an image sensor area based on the supplied zoom settings.
*
* <p>The returned image sensor area can be applied to the {@link android.hardware.camera2} API in
* order to control zoom levels.
* order to control zoom levels. This method of zoom should only be used for Android versions <=
* 11 as past that, the newer {@link #computeZoomRatio()} functional can be used.
*
* @param zoom The desired zoom level.
* @param sensorArraySize The current area of the image sensor.
* @param minimumZoomLevel The minimum supported zoom level.
* @param maximumZoomLevel The maximim supported zoom level.
* @return An image sensor area based on the supplied zoom settings
*/
static Rect computeZoom(
static Rect computeZoomRect(
float zoom, @NonNull Rect sensorArraySize, float minimumZoomLevel, float maximumZoomLevel) {
final float newZoom = MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel);
final float newZoom = computeZoomRatio(zoom, minimumZoomLevel, maximumZoomLevel);

final int centerX = sensorArraySize.width() / 2;
final int centerY = sensorArraySize.height() / 2;
Expand All @@ -37,4 +38,8 @@ static Rect computeZoom(

return new Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY);
}

static Float computeZoomRatio(float zoom, float minimumZoomLevel, float maximumZoomLevel) {
return MathUtils.clamp(zoom, minimumZoomLevel, maximumZoomLevel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,30 @@ public void getScalerAvailableMaxDigitalZoomTest() {
assertEquals(actualDigitalZoom, expectedDigitalZoom);
}

@Test
public void getScalerGetScalerMinZoomRatioTest() {
Range zoomRange = mock(Range.class);
when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE))
.thenReturn(zoomRange);

Float minZoom = cameraProperties.getScalerMinZoomRatio();

verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
assertEquals(zoomRange.getLower(), minZoom);
}

@Test
public void getScalerGetScalerMaxZoomRatioTest() {
Range zoomRange = mock(Range.class);
when(mockCharacteristics.get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE))
.thenReturn(zoomRange);

Float maxZoom = cameraProperties.getScalerMaxZoomRatio();

verify(mockCharacteristics, times(1)).get(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
assertEquals(zoomRange.getUpper(), maxZoom);
}

@Test
public void getSensorInfoActiveArraySizeTest() {
Rect expectedArraySize = mock(Rect.class);
Expand Down

This file was deleted.

Loading

0 comments on commit e85e0f2

Please sign in to comment.