Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Allow logical cameras to use all physical cameras via zoom on android 11+ #6150

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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