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

Commit 6a9ccb4

Browse files
committed
Refactored and tested zoom on Android
1 parent 3c1495c commit 6a9ccb4

File tree

7 files changed

+208
-50
lines changed

7 files changed

+208
-50
lines changed

packages/camera/camera/android/build.gradle

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ buildscript {
99
}
1010

1111
dependencies {
12-
classpath 'com.android.tools.build:gradle:3.3.0'
12+
classpath 'com.android.tools.build:gradle:3.5.0'
1313
}
1414
}
1515

@@ -40,16 +40,17 @@ android {
4040
sourceCompatibility = '1.8'
4141
targetCompatibility = '1.8'
4242
}
43-
dependencies {
44-
implementation 'androidx.annotation:annotation:1.0.0'
45-
implementation 'androidx.core:core:1.0.0'
46-
}
43+
4744
testOptions {
45+
unitTests.includeAndroidResources = true
4846
unitTests.returnDefaultValues = true
4947
}
5048
}
5149

5250
dependencies {
51+
compileOnly 'androidx.annotation:annotation:1.1.0'
5352
testImplementation 'junit:junit:4.12'
5453
testImplementation 'org.mockito:mockito-core:3.5.13'
54+
testImplementation 'androidx.test:core:1.3.0'
55+
testImplementation 'org.robolectric:robolectric:4.3'
5556
}

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import android.hardware.camera2.CameraMetadata;
1818
import android.hardware.camera2.CaptureFailure;
1919
import android.hardware.camera2.CaptureRequest;
20-
import android.hardware.camera2.params.StreamConfigurationMap;
2120
import android.media.CamcorderProfile;
2221
import android.media.Image;
2322
import android.media.ImageReader;
@@ -52,21 +51,21 @@ public class Camera {
5251
private final Size captureSize;
5352
private final Size previewSize;
5453
private final boolean enableAudio;
54+
private final DartMessenger dartMessenger;
55+
private final CamcorderProfile recordingProfile;
56+
private final Context applicationContext;
57+
private final CameraZoom cameraZoom;
5558

5659
private CameraDevice cameraDevice;
5760
private CameraCaptureSession cameraCaptureSession;
5861
private ImageReader pictureImageReader;
5962
private ImageReader imageStreamReader;
60-
private DartMessenger dartMessenger;
6163
private CaptureRequest.Builder captureRequestBuilder;
6264
private MediaRecorder mediaRecorder;
6365
private boolean recordingVideo;
6466
private File videoRecordingFile;
65-
private CamcorderProfile recordingProfile;
6667
private int currentOrientation = ORIENTATION_UNKNOWN;
67-
private Context applicationContext;
68-
private CameraCharacteristics cameraCharacteristics;
69-
private Rect activeArraySize;
68+
7069

7170
// Mirrors camera.dart
7271
public enum ResolutionPreset {
@@ -108,12 +107,9 @@ public void onOrientationChanged(int i) {
108107
};
109108
orientationEventListener.enable();
110109

111-
cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraName);
112-
StreamConfigurationMap streamConfigurationMap =
113-
cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
114-
//noinspection ConstantConditions
110+
CameraCharacteristics cameraCharacteristics = cameraManager
111+
.getCameraCharacteristics(cameraName);
115112
sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
116-
//noinspection ConstantConditions
117113
isFrontFacing =
118114
cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
119115
== CameraMetadata.LENS_FACING_FRONT;
@@ -122,10 +118,9 @@ public void onOrientationChanged(int i) {
122118
CameraUtils.getBestAvailableCamcorderProfileForResolutionPreset(cameraName, preset);
123119
captureSize = new Size(recordingProfile.videoFrameWidth, recordingProfile.videoFrameHeight);
124120
previewSize = computeBestPreviewSize(cameraName, preset);
125-
126-
// Store the sensor size to be able to perform zoom and focus calculations.
127-
activeArraySize =
128-
cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
121+
cameraZoom = new CameraZoom(
122+
cameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE),
123+
cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM));
129124
}
130125

131126
private void prepareMediaRecorder(String outputFilePath) throws IOException {
@@ -218,10 +213,6 @@ private void writeToFile(ByteBuffer buffer, File file) throws IOException {
218213
}
219214
}
220215

221-
SurfaceTextureEntry getFlutterTexture() {
222-
return flutterTexture;
223-
}
224-
225216
public void takePicture(@NonNull final Result result) {
226217
final File outputDir = applicationContext.getCacheDir();
227218
final File file;
@@ -515,16 +506,16 @@ public void close() {
515506
}
516507

517508
public float getMaxZoomLevel() {
518-
return cameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
509+
return cameraZoom.maxZoom;
519510
}
520511

521512
public float getMinZoomLevel() {
522-
return 1f;
513+
return CameraZoom.DEFAULT_ZOOM_FACTOR;
523514
}
524515

525516
public void setZoomLevel(@NonNull final Result result, float zoom) throws CameraAccessException {
526-
float maxZoom = this.getMaxZoomLevel();
527-
float minZoom = this.getMinZoomLevel();
517+
float maxZoom = cameraZoom.maxZoom;
518+
float minZoom = CameraZoom.DEFAULT_ZOOM_FACTOR;
528519

529520
if (zoom > maxZoom || zoom < minZoom) {
530521
String errorMessage =
@@ -538,33 +529,15 @@ public void setZoomLevel(@NonNull final Result result, float zoom) throws Camera
538529
}
539530

540531
//Zoom area is calculated relative to sensor area (activeRect)
541-
Rect zoomCropPreview = getZoomRect(zoom, activeArraySize.width(), activeArraySize.height());
542532
if (captureRequestBuilder != null) {
543-
captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomCropPreview);
533+
final Rect computedZoom = cameraZoom.computeZoom(zoom);
534+
captureRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, computedZoom);
544535
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
545536
}
546537

547538
result.success(null);
548539
}
549540

550-
//Calculate zoom area according input image size
551-
private static Rect getZoomRect(float zoom, int imgWidth, int imgHeight) {
552-
int cropWidth = (int) (imgWidth / zoom) + 2 * 64;
553-
int cropHeight = (int) (imgHeight / zoom) + 2 * 64;
554-
// ensure crop w,h divisible by 4 (SZ requirement)
555-
cropWidth -= cropWidth & 3;
556-
cropHeight -= cropHeight & 3;
557-
// crop area for standard frame
558-
int cropWidthStd = cropWidth - 2 * 64;
559-
int cropHeightStd = cropHeight - 2 * 64;
560-
561-
return new Rect(
562-
(imgWidth - cropWidthStd) / 2,
563-
(imgHeight - cropHeightStd) / 2,
564-
(imgWidth + cropWidthStd) / 2,
565-
(imgHeight + cropHeightStd) / 2);
566-
}
567-
568541
public void dispose() {
569542
close();
570543
flutterTexture.release();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.flutter.plugins.camera;
2+
3+
import android.graphics.Rect;
4+
import androidx.annotation.NonNull;
5+
import androidx.annotation.Nullable;
6+
import androidx.core.math.MathUtils;
7+
8+
public final class CameraZoom
9+
{
10+
public static final float DEFAULT_ZOOM_FACTOR = 1.0f;
11+
12+
@NonNull
13+
private final Rect cropRegion = new Rect();
14+
@Nullable
15+
private final Rect sensorSize;
16+
17+
public final float maxZoom;
18+
public final boolean hasSupport;
19+
20+
public CameraZoom(@Nullable final Rect sensorArraySize, final Float maxZoom)
21+
{
22+
this.sensorSize = sensorArraySize;
23+
24+
if (this.sensorSize == null)
25+
{
26+
this.maxZoom = DEFAULT_ZOOM_FACTOR;
27+
this.hasSupport = false;
28+
return;
29+
}
30+
31+
this.maxZoom = ((maxZoom == null) || (maxZoom < DEFAULT_ZOOM_FACTOR))
32+
? DEFAULT_ZOOM_FACTOR
33+
: maxZoom;
34+
35+
this.hasSupport = (Float.compare(this.maxZoom, DEFAULT_ZOOM_FACTOR) > 0);
36+
}
37+
38+
public Rect computeZoom(final float zoom) {
39+
if (sensorSize == null || !this.hasSupport) {
40+
return null;
41+
}
42+
43+
final float newZoom = MathUtils.clamp(zoom, DEFAULT_ZOOM_FACTOR, this.maxZoom);
44+
45+
final int centerX = this.sensorSize.width() / 2;
46+
final int centerY = this.sensorSize.height() / 2;
47+
final int deltaX = (int)((0.5f * this.sensorSize.width()) / newZoom);
48+
final int deltaY = (int)((0.5f * this.sensorSize.height()) / newZoom);
49+
50+
this.cropRegion.set(centerX - deltaX,
51+
centerY - deltaY,
52+
centerX + deltaX,
53+
centerY + deltaY);
54+
55+
return cropRegion;
56+
}
57+
}

packages/camera/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
144144
}
145145
case "getMaxZoomLevel":
146146
{
147+
assert camera != null;
148+
147149
try {
148150
float maxZoomLevel = camera.getMaxZoomLevel();
149151
result.success(maxZoomLevel);
@@ -154,6 +156,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
154156
}
155157
case "getMinZoomLevel":
156158
{
159+
assert camera != null;
160+
157161
try {
158162
float minZoomLevel = camera.getMinZoomLevel();
159163
result.success(minZoomLevel);
@@ -164,6 +168,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result)
164168
}
165169
case "setZoomLevel":
166170
{
171+
assert camera != null;
172+
167173
Double zoom = call.argument("zoom");
168174

169175
if (zoom == null) {
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package io.flutter.plugins.camera;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotNull;
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertNull;
7+
import static org.junit.Assert.assertTrue;
8+
9+
import android.graphics.Rect;
10+
import org.junit.Test;
11+
import org.junit.runner.RunWith;
12+
import org.robolectric.RobolectricTestRunner;
13+
14+
@RunWith(RobolectricTestRunner.class)
15+
public class CameraZoomTest {
16+
17+
@Test
18+
public void ctor_when_parameters_are_valid() {
19+
final Rect sensorSize = new Rect(0,0,0,0);
20+
final Float maxZoom = 4.0f;
21+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
22+
23+
assertNotNull(cameraZoom);
24+
assertTrue(cameraZoom.hasSupport);
25+
assertEquals(4.0f, cameraZoom.maxZoom, 0);
26+
assertEquals(1.0f, CameraZoom.DEFAULT_ZOOM_FACTOR, 0);
27+
}
28+
29+
@Test
30+
public void ctor_when_sensor_size_is_null() {
31+
final Rect sensorSize = null;
32+
final Float maxZoom = 4.0f;
33+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
34+
35+
assertNotNull(cameraZoom);
36+
assertFalse(cameraZoom.hasSupport);
37+
assertEquals( cameraZoom.maxZoom, 1.0f, 0);
38+
}
39+
40+
@Test
41+
public void ctor_when_max_zoom_is_null() {
42+
final Rect sensorSize = new Rect(0,0,0,0);
43+
final Float maxZoom = null;
44+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
45+
46+
assertNotNull(cameraZoom);
47+
assertFalse(cameraZoom.hasSupport);
48+
assertEquals( cameraZoom.maxZoom, 1.0f, 0);
49+
}
50+
51+
@Test
52+
public void ctor_when_max_zoom_is_smaller_then_default_zoom_factor() {
53+
final Rect sensorSize = new Rect(0,0,0,0);
54+
final Float maxZoom = 0.5f;
55+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, maxZoom);
56+
57+
assertNotNull(cameraZoom);
58+
assertFalse(cameraZoom.hasSupport);
59+
assertEquals( cameraZoom.maxZoom, 1.0f, 0);
60+
}
61+
62+
@Test
63+
public void setZoom_when_no_support_should_not_set_scaler_crop_region() {
64+
final CameraZoom cameraZoom = new CameraZoom(null, null);
65+
final Rect computedZoom = cameraZoom.computeZoom(2f);
66+
67+
assertNull(computedZoom);
68+
}
69+
70+
@Test
71+
public void setZoom_when_sensor_size_equals_zero_should_return_crop_region_of_zero() {
72+
final Rect sensorSize = new Rect(0, 0, 0, 0);
73+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f);
74+
final Rect computedZoom = cameraZoom.computeZoom(18f);
75+
76+
assertNotNull(computedZoom);
77+
assertEquals(computedZoom.left, 0);
78+
assertEquals(computedZoom.top, 0);
79+
assertEquals(computedZoom.right, 0);
80+
assertEquals(computedZoom.bottom, 0);
81+
}
82+
83+
@Test
84+
public void setZoom_when_sensor_size_is_valid_should_return_crop_region() {
85+
final Rect sensorSize = new Rect(0, 0, 100, 100);
86+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, 20f);
87+
final Rect computedZoom = cameraZoom.computeZoom(18f);
88+
89+
assertNotNull(computedZoom);
90+
assertEquals(computedZoom.left, 48);
91+
assertEquals(computedZoom.top, 48);
92+
assertEquals(computedZoom.right, 52);
93+
assertEquals(computedZoom.bottom, 52);
94+
}
95+
96+
@Test
97+
public void setZoom_when_zoom_is_greater_then_max_zoom_clamp_to_max_zoom() {
98+
final Rect sensorSize = new Rect(0, 0, 100, 100);
99+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f);
100+
final Rect computedZoom = cameraZoom.computeZoom(25f);
101+
102+
assertNotNull(computedZoom);
103+
assertEquals(computedZoom.left, 45);
104+
assertEquals(computedZoom.top, 45);
105+
assertEquals(computedZoom.right, 55);
106+
assertEquals(computedZoom.bottom, 55);
107+
}
108+
109+
@Test
110+
public void setZoom_when_zoom_is_smaller_then_min_zoom_clamp_to_min_zoom() {
111+
final Rect sensorSize = new Rect(0, 0, 100, 100);
112+
final CameraZoom cameraZoom = new CameraZoom(sensorSize, 10f);
113+
final Rect computedZoom = cameraZoom.computeZoom(0.5f);
114+
115+
assertNotNull(computedZoom);
116+
assertEquals(computedZoom.left, 0);
117+
assertEquals(computedZoom.top, 0);
118+
assertEquals(computedZoom.right, 100);
119+
assertEquals(computedZoom.bottom, 100);
120+
}
121+
}

packages/camera/camera/example/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ buildscript {
55
}
66

77
dependencies {
8-
classpath 'com.android.tools.build:gradle:3.3.0'
8+
classpath 'com.android.tools.build:gradle:3.5.0'
99
}
1010
}
1111

packages/camera/camera/example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
33
zipStoreBase=GRADLE_USER_HOME
44
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip

0 commit comments

Comments
 (0)