Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion packages/camera/camera/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ android {
dependencies {
compileOnly 'androidx.annotation:annotation:1.1.0'
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-inline:3.5.13'
testImplementation 'org.mockito:mockito-inline:3.11.1'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'org.robolectric:robolectric:4.3'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera;

import android.annotation.TargetApi;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.MeteringRectangle;
import android.os.Build;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Arrays;

/**
* Utility class offering functions to calculate values regarding the camera boundaries.
*
* <p>The functions are used to calculate focus and exposure settings.
*/
public final class CameraRegionUtils {

/**
* Obtains the boundaries for the currently active camera, that can be used for calculating
* MeteringRectangle instances required for setting focus or exposure settings.
*
* @param cameraProperties - Collection of the characteristics for the current camera device.
* @param requestBuilder - The request builder for the current capture request.
* @return The boundaries for the current camera device.
*/
public static Size getCameraBoundaries(
@NonNull CameraProperties cameraProperties, @NonNull CaptureRequest.Builder requestBuilder) {
// No distortion correction support
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& supportsDistortionCorrection(cameraProperties)) {
// Get the current distortion correction mode
Integer distortionCorrectionMode =
requestBuilder.get(CaptureRequest.DISTORTION_CORRECTION_MODE);

// Return the correct boundaries depending on the mode
android.graphics.Rect rect;
if (distortionCorrectionMode == null
|| distortionCorrectionMode == CaptureRequest.DISTORTION_CORRECTION_MODE_OFF) {
rect = cameraProperties.getSensorInfoPreCorrectionActiveArraySize();
} else {
rect = cameraProperties.getSensorInfoActiveArraySize();
}

// Return camera boundaries
return rect == null ? null : new Size(rect.width(), rect.height());
} else {
// Return camera boundaries
return cameraProperties.getSensorInfoPixelArraySize();
}
}

/**
* Converts a point into a {@link MeteringRectangle} with the supplied coordinates as the centre
* point.
*
* <p>Since the Camera API (due to cross-platform constraints) only accepts a point when
* configuring a specific focus or exposure area and Android requires a rectangle to configure
* these settings there is a need to convert the point into a rectangle. This method will create
* the required rectangle with an arbitrarily size that is a 10th of the current viewport and the
* coordinates as the centre point.
*
* @param boundaries - The camera boundaries to calculate the metering rectangle for.
* @param x x - 1 >= coordinate >= 0
* @param y y - 1 >= coordinate >= 0
* @return The dimensions of the metering rectangle based on the supplied coordinates and
* boundaries.
*/
public static MeteringRectangle convertPointToMeteringRectangle(
@NonNull Size boundaries, double x, double y) {
assert (boundaries.getWidth() > 0 && boundaries.getHeight() > 0);
assert (x >= 0 && x <= 1);
assert (y >= 0 && y <= 1);

// Interpolate the target coordinate
int targetX = (int) Math.round(x * ((double) (boundaries.getWidth() - 1)));
int targetY = (int) Math.round(y * ((double) (boundaries.getHeight() - 1)));
// Since the Camera API only allows Determine the dimensions of the metering rectangle (10th of
// the viewport)
int targetWidth = (int) Math.round(((double) boundaries.getWidth()) / 10d);
int targetHeight = (int) Math.round(((double) boundaries.getHeight()) / 10d);
// Adjust target coordinate to represent top-left corner of metering rectangle
targetX -= targetWidth / 2;
targetY -= targetHeight / 2;
// Adjust target coordinate as to not fall out of bounds
if (targetX < 0) targetX = 0;
if (targetY < 0) targetY = 0;
int maxTargetX = boundaries.getWidth() - 1 - targetWidth;
int maxTargetY = boundaries.getHeight() - 1 - targetHeight;
if (targetX > maxTargetX) targetX = maxTargetX;
if (targetY > maxTargetY) targetY = maxTargetY;

// Build the metering rectangle
return MeteringRectangleFactory.create(targetX, targetY, targetWidth, targetHeight, 1);
}

@TargetApi(Build.VERSION_CODES.P)
private static boolean supportsDistortionCorrection(CameraProperties cameraProperties) {
int[] availableDistortionCorrectionModes =
cameraProperties.getDistortionCorrectionAvailableModes();
if (availableDistortionCorrectionModes == null) {
availableDistortionCorrectionModes = new int[0];
}
long nonOffModesSupported =
Arrays.stream(availableDistortionCorrectionModes)
.filter((value) -> value != CaptureRequest.DISTORTION_CORRECTION_MODE_OFF)
.count();
return nonOffModesSupported > 0;
}

/** Factory class that assists in creating a {@link MeteringRectangle} instance. */
static class MeteringRectangleFactory {
/**
* Creates a new instance of the {@link MeteringRectangle} class.
*
* <p>This method is visible for testing purposes only and should never be used outside this *
* class.
*
* @param x coordinate >= 0
* @param y coordinate >= 0
* @param width width >= 0
* @param height height >= 0
* @param meteringWeight weight between {@value MeteringRectangle#METERING_WEIGHT_MIN} and
* {@value MeteringRectangle#METERING_WEIGHT_MAX} inclusively
* @return new instance of the {@link MeteringRectangle} class.
* @throws IllegalArgumentException if any of the parameters were negative
*/
@VisibleForTesting
public static MeteringRectangle create(
int x, int y, int width, int height, int meteringWeight) {
return new MeteringRectangle(x, y, width, height, meteringWeight);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,37 @@

import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.MeteringRectangle;
import android.util.Log;
import android.util.Size;
import androidx.annotation.NonNull;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.CameraRegionUtils;
import io.flutter.plugins.camera.features.CameraFeature;
import io.flutter.plugins.camera.features.Point;
import io.flutter.plugins.camera.types.CameraRegions;

/** Exposure point controls where in the frame exposure metering will come from. */
public class ExposurePointFeature extends CameraFeature<Point> {

private final CameraRegions cameraRegions;
private Point currentSetting = new Point(0.0, 0.0);
private Size cameraBoundaries;
private Point exposurePoint;
private MeteringRectangle exposureRectangle;

/**
* Creates a new instance of the {@link ExposurePointFeature}.
*
* @param cameraProperties Collection of the characteristics for the current camera device.
* @param cameraRegions Utility class to assist in calculating exposure boundaries.
*/
public ExposurePointFeature(CameraProperties cameraProperties, CameraRegions cameraRegions) {
public ExposurePointFeature(CameraProperties cameraProperties) {
super(cameraProperties);
this.cameraRegions = cameraRegions;
}

/**
* Sets the camera boundaries that are required for the exposure point feature to function.
*
* @param cameraBoundaries - The camera boundaries to set.
*/
public void setCameraBoundaries(@NonNull Size cameraBoundaries) {
this.cameraBoundaries = cameraBoundaries;
this.buildExposureRectangle();
}

@Override
Expand All @@ -37,18 +46,13 @@ public String getDebugName() {

@Override
public Point getValue() {
return currentSetting;
return exposurePoint;
}

@Override
public void setValue(@NonNull Point value) {
this.currentSetting = value;

if (value.x == null || value.y == null) {
cameraRegions.resetAutoExposureMeteringRectangle();
} else {
cameraRegions.setAutoExposureMeteringRectangleFromPoint(value.x, value.y);
}
public void setValue(Point value) {
this.exposurePoint = value == null || value.x == null || value.y == null ? null : value;
this.buildExposureRectangle();
}

// Whether or not this camera can set the exposure point.
Expand All @@ -63,16 +67,22 @@ public void updateBuilder(CaptureRequest.Builder requestBuilder) {
if (!checkIsSupported()) {
return;
}

MeteringRectangle aeRect = null;
try {
aeRect = cameraRegions.getAEMeteringRectangle();
} catch (Exception e) {
Log.w("Camera", "Unable to retrieve the Auto Exposure metering rectangle.", e);
}

requestBuilder.set(
CaptureRequest.CONTROL_AE_REGIONS,
aeRect == null ? null : new MeteringRectangle[] {aeRect});
exposureRectangle == null ? null : new MeteringRectangle[] {exposureRectangle});
}

private void buildExposureRectangle() {
if (this.cameraBoundaries == null) {
throw new AssertionError(
"The cameraBoundaries should be set (using `ExposurePointFeature.setCameraBoundaries(Size)`) before updating the exposure point.");
}
if (this.exposurePoint == null) {
this.exposureRectangle = null;
} else {
this.exposureRectangle =
CameraRegionUtils.convertPointToMeteringRectangle(
this.cameraBoundaries, this.exposurePoint.x, this.exposurePoint.y);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.camera.features.focuspoint;

import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.MeteringRectangle;
import android.util.Size;
import androidx.annotation.NonNull;
import io.flutter.plugins.camera.CameraProperties;
import io.flutter.plugins.camera.CameraRegionUtils;
import io.flutter.plugins.camera.features.CameraFeature;
import io.flutter.plugins.camera.features.Point;

/** Focus point controls where in the frame focus will come from. */
public class FocusPointFeature extends CameraFeature<Point> {

private Size cameraBoundaries;
private Point focusPoint;
private MeteringRectangle focusRectangle;

/**
* Creates a new instance of the {@link FocusPointFeature}.
*
* @param cameraProperties Collection of the characteristics for the current camera device.
*/
public FocusPointFeature(CameraProperties cameraProperties) {
super(cameraProperties);
}

/**
* Sets the camera boundaries that are required for the focus point feature to function.
*
* @param cameraBoundaries - The camera boundaries to set.
*/
public void setCameraBoundaries(@NonNull Size cameraBoundaries) {
this.cameraBoundaries = cameraBoundaries;
this.buildFocusRectangle();
}

@Override
public String getDebugName() {
return "FocusPointFeature";
}

@Override
public Point getValue() {
return focusPoint;
}

@Override
public void setValue(Point value) {
this.focusPoint = value == null || value.x == null || value.y == null ? null : value;
this.buildFocusRectangle();
}

// Whether or not this camera can set the focus point.
@Override
public boolean checkIsSupported() {
Integer supportedRegions = cameraProperties.getControlMaxRegionsAutoFocus();
return supportedRegions != null && supportedRegions > 0;
}

@Override
public void updateBuilder(CaptureRequest.Builder requestBuilder) {
if (!checkIsSupported()) {
return;
}
requestBuilder.set(
CaptureRequest.CONTROL_AF_REGIONS,
focusRectangle == null ? null : new MeteringRectangle[] {focusRectangle});
}

private void buildFocusRectangle() {
if (this.cameraBoundaries == null) {
throw new AssertionError(
"The cameraBoundaries should be set (using `FocusPointFeature.setCameraBoundaries(Size)`) before updating the focus point.");
}
if (this.focusPoint == null) {
this.focusRectangle = null;
} else {
this.focusRectangle =
CameraRegionUtils.convertPointToMeteringRectangle(
this.cameraBoundaries, this.focusPoint.x, this.focusPoint.y);
}
}
}
Loading