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

Commit 7a37b86

Browse files
mvanbeusekomacoutts
andcommitted
Added Android exposure related features
Co-authored-by: Andrew Coutts <andrewjohncoutts@gmail.com>
1 parent a02cc68 commit 7a37b86

File tree

15 files changed

+1141
-1
lines changed

15 files changed

+1141
-1
lines changed

packages/camera/camera/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ android {
4949
dependencies {
5050
compileOnly 'androidx.annotation:annotation:1.1.0'
5151
testImplementation 'junit:junit:4.12'
52-
testImplementation 'org.mockito:mockito-core:3.5.13'
52+
testImplementation 'org.mockito:mockito-inline:3.5.13'
5353
testImplementation 'androidx.test:core:1.3.0'
5454
testImplementation 'org.robolectric:robolectric:4.3'
5555
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features;
6+
7+
/** Represents a point on an x/y axis. */
8+
public class Point {
9+
public final Double x;
10+
public final Double y;
11+
12+
public Point(Double x, Double y) {
13+
this.x = x;
14+
this.y = y;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.exposurelock;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import io.flutter.plugins.camera.CameraProperties;
9+
import io.flutter.plugins.camera.features.CameraFeature;
10+
11+
/**
12+
* Exposure lock controls whether or not exposure mode is currenty locked or automatically metering.
13+
*/
14+
public class ExposureLockFeature extends CameraFeature<ExposureMode> {
15+
private ExposureMode currentSetting = ExposureMode.auto;
16+
17+
public ExposureLockFeature(CameraProperties cameraProperties) {
18+
super(cameraProperties);
19+
}
20+
21+
@Override
22+
public String getDebugName() {
23+
return "ExposureLockFeature";
24+
}
25+
26+
@Override
27+
public ExposureMode getValue() {
28+
return currentSetting;
29+
}
30+
31+
@Override
32+
public void setValue(ExposureMode value) {
33+
this.currentSetting = value;
34+
}
35+
36+
// Available on all devices.
37+
@Override
38+
public boolean checkIsSupported() {
39+
return true;
40+
}
41+
42+
@Override
43+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
44+
if (!checkIsSupported()) {
45+
return;
46+
}
47+
48+
switch (currentSetting) {
49+
case locked:
50+
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
51+
break;
52+
case auto:
53+
default:
54+
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
55+
break;
56+
}
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.exposurelock;
6+
7+
// Mirrors exposure_mode.dart
8+
public enum ExposureMode {
9+
auto("auto"),
10+
locked("locked");
11+
12+
private final String strValue;
13+
14+
ExposureMode(String strValue) {
15+
this.strValue = strValue;
16+
}
17+
18+
public static ExposureMode getValueForString(String modeStr) {
19+
for (ExposureMode value : values()) {
20+
if (value.strValue.equals(modeStr)) return value;
21+
}
22+
return null;
23+
}
24+
25+
@Override
26+
public String toString() {
27+
return strValue;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.exposureoffset;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import android.util.Range;
9+
import io.flutter.plugins.camera.CameraProperties;
10+
import io.flutter.plugins.camera.features.CameraFeature;
11+
12+
/** Exposure offset makes the image brighter or darker. */
13+
public class ExposureOffsetFeature extends CameraFeature<ExposureOffsetValue> {
14+
private ExposureOffsetValue currentSetting;
15+
private final double min;
16+
private final double max;
17+
18+
public ExposureOffsetFeature(CameraProperties cameraProperties) {
19+
super(cameraProperties);
20+
21+
this.min = getMinExposureOffset();
22+
this.max = getMaxExposureOffset();
23+
24+
// Initial offset of 0
25+
this.currentSetting = new ExposureOffsetValue(this.min, this.max, 0);
26+
}
27+
28+
@Override
29+
public String getDebugName() {
30+
return "ExposureOffsetFeature";
31+
}
32+
33+
@Override
34+
public ExposureOffsetValue getValue() {
35+
return currentSetting;
36+
}
37+
38+
@Override
39+
public void setValue(ExposureOffsetValue value) {
40+
double stepSize = getExposureOffsetStepSize();
41+
this.currentSetting = new ExposureOffsetValue(min, max, (value.value / stepSize));
42+
}
43+
44+
// Available on all devices.
45+
@Override
46+
public boolean checkIsSupported() {
47+
return true;
48+
}
49+
50+
@Override
51+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
52+
if (!checkIsSupported()) {
53+
return;
54+
}
55+
56+
requestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, (int) currentSetting.value);
57+
}
58+
59+
/**
60+
* Return the minimum exposure offset double value.
61+
*
62+
* @return
63+
*/
64+
private double getMinExposureOffset() {
65+
Range<Integer> range = cameraProperties.getControlAutoExposureCompensationRange();
66+
double minStepped = range == null ? 0 : range.getLower();
67+
double stepSize = getExposureOffsetStepSize();
68+
return minStepped * stepSize;
69+
}
70+
71+
/**
72+
* Return the max exposure offset double value.
73+
*
74+
* @return
75+
*/
76+
private double getMaxExposureOffset() {
77+
Range<Integer> range = cameraProperties.getControlAutoExposureCompensationRange();
78+
double maxStepped = range == null ? 0 : range.getUpper();
79+
double stepSize = getExposureOffsetStepSize();
80+
return maxStepped * stepSize;
81+
}
82+
83+
/**
84+
* Returns the exposure offset step size. This is the smallest amount which the exposure offset
85+
* can be changed.
86+
*
87+
* <p>Example: if this has a value of 0.5, then an aeExposureCompensation setting of -2 means that
88+
* the actual AE offset is -1.
89+
*
90+
* @return
91+
*/
92+
public double getExposureOffsetStepSize() {
93+
return cameraProperties.getControlAutoExposureCompensationStep();
94+
}
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.exposureoffset;
6+
7+
/**
8+
* This represents the exposure offset value. It holds the minimum and maximum values, as well as
9+
* the current setting value.
10+
*/
11+
public class ExposureOffsetValue {
12+
public final double min;
13+
public final double max;
14+
public final double value;
15+
16+
public ExposureOffsetValue(double min, double max, double value) {
17+
this.min = min;
18+
this.max = max;
19+
this.value = value;
20+
}
21+
22+
public ExposureOffsetValue(double value) {
23+
this(0, 0, value);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.exposurepoint;
6+
7+
import android.hardware.camera2.CaptureRequest;
8+
import android.hardware.camera2.params.MeteringRectangle;
9+
import android.util.Log;
10+
import androidx.annotation.NonNull;
11+
import io.flutter.plugins.camera.CameraProperties;
12+
import io.flutter.plugins.camera.features.CameraFeature;
13+
import io.flutter.plugins.camera.features.Point;
14+
import io.flutter.plugins.camera.features.regionboundaries.CameraRegions;
15+
import java.util.concurrent.Callable;
16+
17+
/** Exposure point controls where in the frame exposure metering will come from. */
18+
public class ExposurePointFeature extends CameraFeature<Point> {
19+
// Used later to always get the correct camera regions instance.
20+
private final Callable<CameraRegions> getCameraRegions;
21+
private Point currentSetting = new Point(0.0, 0.0);
22+
23+
public ExposurePointFeature(
24+
CameraProperties cameraProperties, Callable<CameraRegions> getCameraRegions) {
25+
super(cameraProperties);
26+
this.getCameraRegions = getCameraRegions;
27+
}
28+
29+
@Override
30+
public String getDebugName() {
31+
return "ExposurePointFeature";
32+
}
33+
34+
@Override
35+
public Point getValue() {
36+
return currentSetting;
37+
}
38+
39+
@Override
40+
public void setValue(@NonNull Point value) {
41+
this.currentSetting = value;
42+
43+
try {
44+
if (value.x == null || value.y == null) {
45+
getCameraRegions.call().resetAutoExposureMeteringRectangle();
46+
} else {
47+
getCameraRegions.call().setAutoExposureMeteringRectangleFromPoint(value.x, value.y);
48+
}
49+
} catch (Exception e) {
50+
e.printStackTrace();
51+
}
52+
}
53+
54+
// Whether or not this camera can set the exposure point.
55+
@Override
56+
public boolean checkIsSupported() {
57+
Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoExposure();
58+
return supportedRegions != null && supportedRegions > 0;
59+
}
60+
61+
@Override
62+
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
63+
if (!checkIsSupported()) {
64+
return;
65+
}
66+
67+
MeteringRectangle aeRect;
68+
try {
69+
aeRect = getCameraRegions.call().getAEMeteringRectangle();
70+
requestBuilder.set(
71+
CaptureRequest.CONTROL_AE_REGIONS,
72+
aeRect == null ? null : new MeteringRectangle[] {aeRect});
73+
} catch (Exception e) {
74+
Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e);
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugins.camera.features.regionboundaries;
6+
7+
import android.hardware.camera2.params.MeteringRectangle;
8+
import android.util.Size;
9+
10+
public final class CameraRegions {
11+
private final Size maxBoundaries;
12+
13+
private MeteringRectangle aeMeteringRectangle;
14+
private MeteringRectangle afMeteringRectangle;
15+
16+
public CameraRegions(Size maxBoundaries) {
17+
assert (maxBoundaries == null || maxBoundaries.getWidth() > 0);
18+
assert (maxBoundaries == null || maxBoundaries.getHeight() > 0);
19+
this.maxBoundaries = maxBoundaries;
20+
}
21+
22+
public MeteringRectangle getAEMeteringRectangle() {
23+
return aeMeteringRectangle;
24+
}
25+
26+
public MeteringRectangle getAFMeteringRectangle() {
27+
return afMeteringRectangle;
28+
}
29+
30+
public Size getMaxBoundaries() {
31+
return this.maxBoundaries;
32+
}
33+
34+
public void resetAutoExposureMeteringRectangle() {
35+
this.aeMeteringRectangle = null;
36+
}
37+
38+
public void setAutoExposureMeteringRectangleFromPoint(double x, double y) {
39+
this.aeMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
40+
}
41+
42+
public void resetAutoFocusMeteringRectangle() {
43+
this.afMeteringRectangle = null;
44+
}
45+
46+
public void setAutoFocusMeteringRectangleFromPoint(double x, double y) {
47+
this.afMeteringRectangle = getMeteringRectangleForPoint(maxBoundaries, x, y);
48+
}
49+
50+
public MeteringRectangle getMeteringRectangleForPoint(Size maxBoundaries, double x, double y) {
51+
assert (x >= 0 && x <= 1);
52+
assert (y >= 0 && y <= 1);
53+
if (maxBoundaries == null)
54+
throw new IllegalStateException(
55+
"Functionality for managing metering rectangles is unavailable as this CameraRegions instance was initialized with null boundaries.");
56+
57+
// Interpolate the target coordinate
58+
int targetX = (int) Math.round(x * ((double) (maxBoundaries.getWidth() - 1)));
59+
int targetY = (int) Math.round(y * ((double) (maxBoundaries.getHeight() - 1)));
60+
// Determine the dimensions of the metering triangle (10th of the viewport)
61+
int targetWidth = (int) Math.round(((double) maxBoundaries.getWidth()) / 10d);
62+
int targetHeight = (int) Math.round(((double) maxBoundaries.getHeight()) / 10d);
63+
// Adjust target coordinate to represent top-left corner of metering rectangle
64+
targetX -= targetWidth / 2;
65+
targetY -= targetHeight / 2;
66+
// Adjust target coordinate as to not fall out of bounds
67+
if (targetX < 0) targetX = 0;
68+
if (targetY < 0) targetY = 0;
69+
int maxTargetX = maxBoundaries.getWidth() - 1 - targetWidth;
70+
int maxTargetY = maxBoundaries.getHeight() - 1 - targetHeight;
71+
if (targetX > maxTargetX) targetX = maxTargetX;
72+
if (targetY > maxTargetY) targetY = maxTargetY;
73+
74+
// Build the metering rectangle
75+
return new MeteringRectangle(targetX, targetY, targetWidth, targetHeight, 1);
76+
}
77+
}

0 commit comments

Comments
 (0)