Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Commit

Permalink
Add support for manual tap to focus
Browse files Browse the repository at this point in the history
  • Loading branch information
koujm committed Apr 8, 2017
1 parent cd7405c commit 22e175d
Show file tree
Hide file tree
Showing 10 changed files with 754 additions and 5 deletions.
153 changes: 151 additions & 2 deletions library/src/main/api14/com/google/android/cameraview/Camera1.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,17 @@
package com.google.android.cameraview;

import android.annotation.SuppressLint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
import android.support.v4.util.SparseArrayCompat;
import android.view.SurfaceHolder;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
Expand All @@ -49,6 +53,8 @@ class Camera1 extends CameraViewImpl {

private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);

private final AtomicBoolean isAutoFocusInProgress = new AtomicBoolean(false);

Camera mCamera;

private Camera.Parameters mCameraParameters;
Expand All @@ -71,11 +77,30 @@ class Camera1 extends CameraViewImpl {

private int mDisplayOrientation;

private CameraCoordinateTransformer mCoordinateTransformer;

private Rect mPreviewRect = new Rect(0, 0, 0, 0);

private final Handler mCameraHandler;

private final Runnable mReturnToContinuousAFRunnable = new Runnable() {
@Override
public void run() {
if (setAutoFocusInternal(mAutoFocus)) {
mCamera.setParameters(mCameraParameters);
mCamera.cancelAutoFocus();
}
}
};

Camera1(Callback callback, PreviewImpl preview) {
super(callback, preview);
mCameraHandler = new Handler();
preview.setCallback(new PreviewImpl.Callback() {
@Override
public void onSurfaceChanged() {
mPreviewRect.set(0, 0, mPreview.getWidth(), mPreview.getHeight());
resetCoordinateTransformer();
if (mCamera != null) {
setUpPreview();
adjustCameraParameters();
Expand Down Expand Up @@ -103,6 +128,7 @@ void stop() {
}
mShowingPreview = false;
releaseCamera();
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
}

// Suppresses Camera#setPreviewTexture
Expand Down Expand Up @@ -141,6 +167,7 @@ void setFacing(int facing) {
stop();
start();
}
resetCoordinateTransformer();
}

@Override
Expand Down Expand Up @@ -223,11 +250,12 @@ void takePicture() {
throw new IllegalStateException(
"Camera is not ready. Call start() before takePicture().");
}
if (getAutoFocus()) {
if (getAutoFocus() || isAutoFocusInProgress.get()) {
mCamera.cancelAutoFocus();
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
isAutoFocusInProgress.set(false);
takePictureInternal();
}
});
Expand All @@ -238,7 +266,14 @@ public void onAutoFocus(boolean success, Camera camera) {

void takePictureInternal() {
if (!isPictureCaptureInProgress.getAndSet(true)) {
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
mCamera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
if (setAutoFocusInternal(mAutoFocus)) {
mCamera.setParameters(mCameraParameters);
}
}
}, null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
isPictureCaptureInProgress.set(false);
Expand All @@ -247,6 +282,7 @@ public void onPictureTaken(byte[] data, Camera camera) {
camera.startPreview();
}
});
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
}
}

Expand All @@ -256,6 +292,7 @@ void setDisplayOrientation(int displayOrientation) {
return;
}
mDisplayOrientation = displayOrientation;
resetCoordinateTransformer();
if (isCameraOpened()) {
int cameraRotation = calcCameraRotation(displayOrientation);
mCameraParameters.setRotation(cameraRotation);
Expand All @@ -271,6 +308,49 @@ void setDisplayOrientation(int displayOrientation) {
}
}

@Override
boolean hasManualFocus() {
return isCameraOpened() && getFacing() == Constants.FACING_BACK
&& (isFocusAreaSupported() || isMeteringAreaSupported());
}

@Override
void setFocusAt(int x, int y) {
if (isPictureCaptureInProgress.get()) {
return;
}
mCallback.onFocusAt(x, y);
if (isAutoFocusInProgress.getAndSet(false)) {
mCamera.cancelAutoFocus();
}
if (!isAutoFocusInProgress.getAndSet(true) && setFocusAndMeterInternal(x, y)) {
mCamera.setParameters(mCameraParameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
isAutoFocusInProgress.set(false);
resumeContinuousAFAfterDelay(Constants.FOCUS_HOLD_MILLIS);
}
});
}
}

boolean isFocusAreaSupported() {
if (Build.VERSION.SDK_INT >= 14) {
List<String> supportedFocusModes = mCameraParameters.getSupportedFocusModes();
return (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)
&& mCameraParameters.getMaxNumFocusAreas() > 0);
}
return false;
}

boolean isMeteringAreaSupported() {
if (Build.VERSION.SDK_INT >= 14) {
return mCameraParameters.getMaxNumMeteringAreas() > 0;
}
return false;
}

/**
* This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
*/
Expand Down Expand Up @@ -406,6 +486,10 @@ private boolean setAutoFocusInternal(boolean autoFocus) {
} else {
mCameraParameters.setFocusMode(modes.get(0));
}
if (Build.VERSION.SDK_INT >= 14) {
mCameraParameters.setFocusAreas(null);
mCameraParameters.setMeteringAreas(null);
}
return true;
} else {
return false;
Expand Down Expand Up @@ -437,4 +521,69 @@ private boolean setFlashInternal(int flash) {
}
}

/**
* @return {@code true} if {@link #mCameraParameters} was modified.
*/
private boolean setFocusAndMeterInternal(int x, int y) {
if (Build.VERSION.SDK_INT >= 14 && hasManualFocus() && mCoordinateTransformer != null) {
if (isFocusAreaSupported()) {
List<Camera.Area> focusArea = Collections.singletonList(
new Camera.Area(computeCameraRectFromPreviewCoordinates(x, y,
getAFRegionSizePx()), 1));
mCameraParameters.setFocusAreas(focusArea);
}
if (isMeteringAreaSupported()) {
List<Camera.Area> meteringArea = Collections.singletonList(
new Camera.Area(computeCameraRectFromPreviewCoordinates(x, y,
getAERegionSizePx()), 1));
mCameraParameters.setMeteringAreas(meteringArea);
}
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
return true;
} else {
return false;
}
}

/**
* @return {@code width} of auto focus region in pixels.
*/
private int getAFRegionSizePx() {
return (int) (Math.min(mPreview.getWidth(), mPreview.getHeight())
* Constants.AF_REGION_BOX);
}

/**
* @return {@code width} of metering region in pixels.
*/
private int getAERegionSizePx() {
return (int) (Math.min(mPreview.getWidth(), mPreview.getHeight())
* Constants.AE_REGION_BOX);
}

private Rect computeCameraRectFromPreviewCoordinates(int x, int y, int size) {
int left = CameraUtil.clamp(x - size / 2, mPreviewRect.left,
mPreviewRect.right - size);
int top = CameraUtil.clamp(y - size / 2, mPreviewRect.top,
mPreviewRect.bottom - size);
RectF rectF = new RectF(left, top, left + size, top + size);
return CameraUtil.rectFToRect(mCoordinateTransformer.toCameraSpace(rectF));
}

private void resetCoordinateTransformer() {
if (mPreview.getWidth() > 0 && mPreview.getHeight() > 0) {
mCoordinateTransformer = new CameraCoordinateTransformer(
mFacing == Constants.FACING_FRONT,
calcCameraRotation(mDisplayOrientation),
CameraUtil.rectToRectF(mPreviewRect));
}
}

/**
* Resume AF_MODE_CONTINUOUS_PICTURE after FOCUS_HOLD_MILLIS.
*/
private void resumeContinuousAFAfterDelay(int millis) {
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
mCameraHandler.postDelayed(mReturnToContinuousAFRunnable, millis);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.cameraview;

import android.graphics.Matrix;
import android.graphics.RectF;

/**
* Transform coordinates to and from preview coordinate space and camera driver
* coordinate space.
*/
public class CameraCoordinateTransformer {

// http://developer.android.com/guide/topics/media/camera.html#metering-focus-areas
private static final RectF CAMERA_DRIVER_RECT = new RectF(-1000, -1000, 1000, 1000);
private final Matrix mCameraToPreviewTransform;
private final Matrix mPreviewToCameraTransform;

/**
* Convert rectangles to / from camera coordinate and preview coordinate space.
*
* @param mirrorX if the preview is mirrored along the X axis.
* @param displayOrientation orientation in degrees.
* @param previewRect the preview rectangle size and position.
*/
public CameraCoordinateTransformer(boolean mirrorX, int displayOrientation,
RectF previewRect) {
if (!hasNonZeroArea(previewRect)) {
throw new IllegalArgumentException("previewRect");
}
mCameraToPreviewTransform = cameraToPreviewTransform(mirrorX, displayOrientation,
previewRect);
mPreviewToCameraTransform = inverse(mCameraToPreviewTransform);
}

/**
* Transform a rectangle in camera space into a new rectangle in preview
* view space.
*
* @param source the rectangle in camera space
* @return the rectangle in preview view space.
*/
public RectF toPreviewSpace(RectF source) {
RectF result = new RectF();
mCameraToPreviewTransform.mapRect(result, source);
return result;
}

/**
* Transform a rectangle in preview view space into a new rectangle in
* camera view space.
*
* @param source the rectangle in preview view space
* @return the rectangle in camera view space.
*/
public RectF toCameraSpace(RectF source) {
RectF result = new RectF();
mPreviewToCameraTransform.mapRect(result, source);
return result;
}

private Matrix cameraToPreviewTransform(boolean mirrorX, int displayOrientation,
RectF previewRect) {
Matrix transform = new Matrix();
// Need mirror for front camera.
transform.setScale(mirrorX ? -1 : 1, 1);
// Apply a rotate transform.
// This is the value for android.hardware.Camera.setDisplayOrientation.
transform.postRotate(displayOrientation);
// Map camera driver coordinates to preview rect coordinates
Matrix fill = new Matrix();
fill.setRectToRect(CAMERA_DRIVER_RECT,
previewRect,
Matrix.ScaleToFit.FILL);
// Concat the previous transform on top of the fill behavior.
transform.setConcat(fill, transform);
return transform;
}

private Matrix inverse(Matrix source) {
Matrix newMatrix = new Matrix();
source.invert(newMatrix);
return newMatrix;
}

private boolean hasNonZeroArea(RectF rect) {
return rect.width() != 0 && rect.height() != 0;
}
}
Loading

0 comments on commit 22e175d

Please sign in to comment.