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

Commit d87e33c

Browse files
committed
Add support for manual tap to focus
1 parent 3eaeac0 commit d87e33c

File tree

10 files changed

+772
-7
lines changed

10 files changed

+772
-7
lines changed

library/src/main/api14/com/google/android/cameraview/Camera1.java

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
package com.google.android.cameraview;
1818

1919
import android.annotation.SuppressLint;
20+
import android.graphics.Rect;
21+
import android.graphics.RectF;
2022
import android.graphics.SurfaceTexture;
2123
import android.hardware.Camera;
2224
import android.os.Build;
25+
import android.os.Handler;
2326
import android.support.v4.util.SparseArrayCompat;
2427
import android.view.SurfaceHolder;
2528

2629
import java.io.IOException;
30+
import java.util.Collections;
2731
import java.util.List;
2832
import java.util.Set;
2933
import java.util.SortedSet;
@@ -49,6 +53,8 @@ class Camera1 extends CameraViewImpl {
4953

5054
private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);
5155

56+
private final AtomicBoolean isAutoFocusInProgress = new AtomicBoolean(false);
57+
5258
Camera mCamera;
5359

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

7278
private int mDisplayOrientation;
7379

80+
private CameraCoordinateTransformer mCoordinateTransformer;
81+
82+
private Rect mPreviewRect = new Rect(0, 0, 0, 0);
83+
84+
private final Handler mCameraHandler;
85+
86+
private final Runnable mReturnToContinuousAFRunnable = new Runnable() {
87+
@Override
88+
public void run() {
89+
if (setAutoFocusInternal(mAutoFocus)) {
90+
mCamera.setParameters(mCameraParameters);
91+
mCamera.cancelAutoFocus();
92+
}
93+
}
94+
};
95+
7496
Camera1(Callback callback, PreviewImpl preview) {
7597
super(callback, preview);
98+
mCameraHandler = new Handler();
7699
preview.setCallback(new PreviewImpl.Callback() {
77100
@Override
78101
public void onSurfaceChanged() {
102+
mPreviewRect.set(0, 0, mPreview.getWidth(), mPreview.getHeight());
103+
resetCoordinateTransformer();
79104
if (mCamera != null) {
80105
setUpPreview();
81106
adjustCameraParameters();
@@ -103,6 +128,7 @@ void stop() {
103128
}
104129
mShowingPreview = false;
105130
releaseCamera();
131+
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
106132
}
107133

108134
// Suppresses Camera#setPreviewTexture
@@ -141,6 +167,7 @@ void setFacing(int facing) {
141167
stop();
142168
start();
143169
}
170+
resetCoordinateTransformer();
144171
}
145172

146173
@Override
@@ -223,11 +250,12 @@ void takePicture() {
223250
throw new IllegalStateException(
224251
"Camera is not ready. Call start() before takePicture().");
225252
}
226-
if (getAutoFocus()) {
253+
if (getAutoFocus() || isAutoFocusInProgress.get()) {
227254
mCamera.cancelAutoFocus();
228255
mCamera.autoFocus(new Camera.AutoFocusCallback() {
229256
@Override
230257
public void onAutoFocus(boolean success, Camera camera) {
258+
isAutoFocusInProgress.set(false);
231259
takePictureInternal();
232260
}
233261
});
@@ -238,15 +266,25 @@ public void onAutoFocus(boolean success, Camera camera) {
238266

239267
void takePictureInternal() {
240268
if (!isPictureCaptureInProgress.getAndSet(true)) {
241-
mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
269+
mCamera.takePicture(new Camera.ShutterCallback() {
270+
@Override
271+
public void onShutter() {
272+
if (setAutoFocusInternal(mAutoFocus)) {
273+
mCamera.setParameters(mCameraParameters);
274+
}
275+
}
276+
}, null, null, new Camera.PictureCallback() {
242277
@Override
243278
public void onPictureTaken(byte[] data, Camera camera) {
244279
isPictureCaptureInProgress.set(false);
245280
mCallback.onPictureTaken(data);
246-
camera.cancelAutoFocus();
247-
camera.startPreview();
281+
if (mShowingPreview) {
282+
camera.cancelAutoFocus();
283+
camera.startPreview();
284+
}
248285
}
249286
});
287+
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
250288
}
251289
}
252290

@@ -256,6 +294,7 @@ void setDisplayOrientation(int displayOrientation) {
256294
return;
257295
}
258296
mDisplayOrientation = displayOrientation;
297+
resetCoordinateTransformer();
259298
if (isCameraOpened()) {
260299
mCameraParameters.setRotation(calcCameraRotation(displayOrientation));
261300
mCamera.setParameters(mCameraParameters);
@@ -270,6 +309,49 @@ void setDisplayOrientation(int displayOrientation) {
270309
}
271310
}
272311

312+
@Override
313+
boolean hasManualFocus() {
314+
return isCameraOpened() && getFacing() == Constants.FACING_BACK
315+
&& (isFocusAreaSupported() || isMeteringAreaSupported());
316+
}
317+
318+
@Override
319+
void setFocusAt(int x, int y) {
320+
if (isPictureCaptureInProgress.get()) {
321+
return;
322+
}
323+
mCallback.onFocusAt(x, y);
324+
if (isAutoFocusInProgress.getAndSet(false)) {
325+
mCamera.cancelAutoFocus();
326+
}
327+
if (!isAutoFocusInProgress.getAndSet(true) && setFocusAndMeterInternal(x, y)) {
328+
mCamera.setParameters(mCameraParameters);
329+
mCamera.autoFocus(new Camera.AutoFocusCallback() {
330+
@Override
331+
public void onAutoFocus(boolean success, Camera camera) {
332+
isAutoFocusInProgress.set(false);
333+
resumeContinuousAFAfterDelay(Constants.FOCUS_HOLD_MILLIS);
334+
}
335+
});
336+
}
337+
}
338+
339+
boolean isFocusAreaSupported() {
340+
if (Build.VERSION.SDK_INT >= 14) {
341+
List<String> supportedFocusModes = mCameraParameters.getSupportedFocusModes();
342+
return (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)
343+
&& mCameraParameters.getMaxNumFocusAreas() > 0);
344+
}
345+
return false;
346+
}
347+
348+
boolean isMeteringAreaSupported() {
349+
if (Build.VERSION.SDK_INT >= 14) {
350+
return mCameraParameters.getMaxNumMeteringAreas() > 0;
351+
}
352+
return false;
353+
}
354+
273355
/**
274356
* This rewrites {@link #mCameraId} and {@link #mCameraInfo}.
275357
*/
@@ -446,6 +528,14 @@ private boolean setAutoFocusInternal(boolean autoFocus) {
446528
} else {
447529
mCameraParameters.setFocusMode(modes.get(0));
448530
}
531+
if (Build.VERSION.SDK_INT >= 14 && hasManualFocus()) {
532+
if (isFocusAreaSupported()) {
533+
mCameraParameters.setFocusAreas(null);
534+
}
535+
if (isMeteringAreaSupported()) {
536+
mCameraParameters.setMeteringAreas(null);
537+
}
538+
}
449539
return true;
450540
} else {
451541
return false;
@@ -477,4 +567,69 @@ private boolean setFlashInternal(int flash) {
477567
}
478568
}
479569

570+
/**
571+
* @return {@code true} if {@link #mCameraParameters} was modified.
572+
*/
573+
private boolean setFocusAndMeterInternal(int x, int y) {
574+
if (Build.VERSION.SDK_INT >= 14 && hasManualFocus() && mCoordinateTransformer != null) {
575+
if (isFocusAreaSupported()) {
576+
List<Camera.Area> focusArea = Collections.singletonList(
577+
new Camera.Area(computeCameraRectFromPreviewCoordinates(x, y,
578+
getAFRegionSizePx()), 1));
579+
mCameraParameters.setFocusAreas(focusArea);
580+
}
581+
if (isMeteringAreaSupported()) {
582+
List<Camera.Area> meteringArea = Collections.singletonList(
583+
new Camera.Area(computeCameraRectFromPreviewCoordinates(x, y,
584+
getAERegionSizePx()), 1));
585+
mCameraParameters.setMeteringAreas(meteringArea);
586+
}
587+
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
588+
return true;
589+
} else {
590+
return false;
591+
}
592+
}
593+
594+
/**
595+
* @return {@code width} of auto focus region in pixels.
596+
*/
597+
private int getAFRegionSizePx() {
598+
return (int) (Math.min(mPreview.getWidth(), mPreview.getHeight())
599+
* Constants.AF_REGION_BOX);
600+
}
601+
602+
/**
603+
* @return {@code width} of metering region in pixels.
604+
*/
605+
private int getAERegionSizePx() {
606+
return (int) (Math.min(mPreview.getWidth(), mPreview.getHeight())
607+
* Constants.AE_REGION_BOX);
608+
}
609+
610+
private Rect computeCameraRectFromPreviewCoordinates(int x, int y, int size) {
611+
int left = CameraUtil.clamp(x - size / 2, mPreviewRect.left,
612+
mPreviewRect.right - size);
613+
int top = CameraUtil.clamp(y - size / 2, mPreviewRect.top,
614+
mPreviewRect.bottom - size);
615+
RectF rectF = new RectF(left, top, left + size, top + size);
616+
return CameraUtil.rectFToRect(mCoordinateTransformer.toCameraSpace(rectF));
617+
}
618+
619+
private void resetCoordinateTransformer() {
620+
if (mPreview.getWidth() > 0 && mPreview.getHeight() > 0) {
621+
mCoordinateTransformer = new CameraCoordinateTransformer(
622+
mFacing == Constants.FACING_FRONT,
623+
calcCameraRotation(mDisplayOrientation),
624+
CameraUtil.rectToRectF(mPreviewRect));
625+
}
626+
}
627+
628+
/**
629+
* Resume AF_MODE_CONTINUOUS_PICTURE after FOCUS_HOLD_MILLIS.
630+
*/
631+
private void resumeContinuousAFAfterDelay(int millis) {
632+
mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
633+
mCameraHandler.postDelayed(mReturnToContinuousAFRunnable, millis);
634+
}
480635
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright (C) 2016 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.cameraview;
18+
19+
import android.graphics.Matrix;
20+
import android.graphics.RectF;
21+
22+
/**
23+
* Transform coordinates to and from preview coordinate space and camera driver
24+
* coordinate space.
25+
*/
26+
public class CameraCoordinateTransformer {
27+
28+
// http://developer.android.com/guide/topics/media/camera.html#metering-focus-areas
29+
private static final RectF CAMERA_DRIVER_RECT = new RectF(-1000, -1000, 1000, 1000);
30+
private final Matrix mCameraToPreviewTransform;
31+
private final Matrix mPreviewToCameraTransform;
32+
33+
/**
34+
* Convert rectangles to / from camera coordinate and preview coordinate space.
35+
*
36+
* @param mirrorX if the preview is mirrored along the X axis.
37+
* @param displayOrientation orientation in degrees.
38+
* @param previewRect the preview rectangle size and position.
39+
*/
40+
public CameraCoordinateTransformer(boolean mirrorX, int displayOrientation,
41+
RectF previewRect) {
42+
if (!hasNonZeroArea(previewRect)) {
43+
throw new IllegalArgumentException("previewRect");
44+
}
45+
mCameraToPreviewTransform = cameraToPreviewTransform(mirrorX, displayOrientation,
46+
previewRect);
47+
mPreviewToCameraTransform = inverse(mCameraToPreviewTransform);
48+
}
49+
50+
/**
51+
* Transform a rectangle in camera space into a new rectangle in preview
52+
* view space.
53+
*
54+
* @param source the rectangle in camera space
55+
* @return the rectangle in preview view space.
56+
*/
57+
public RectF toPreviewSpace(RectF source) {
58+
RectF result = new RectF();
59+
mCameraToPreviewTransform.mapRect(result, source);
60+
return result;
61+
}
62+
63+
/**
64+
* Transform a rectangle in preview view space into a new rectangle in
65+
* camera view space.
66+
*
67+
* @param source the rectangle in preview view space
68+
* @return the rectangle in camera view space.
69+
*/
70+
public RectF toCameraSpace(RectF source) {
71+
RectF result = new RectF();
72+
mPreviewToCameraTransform.mapRect(result, source);
73+
return result;
74+
}
75+
76+
private Matrix cameraToPreviewTransform(boolean mirrorX, int displayOrientation,
77+
RectF previewRect) {
78+
Matrix transform = new Matrix();
79+
// Need mirror for front camera.
80+
transform.setScale(mirrorX ? -1 : 1, 1);
81+
// Apply a rotate transform.
82+
// This is the value for android.hardware.Camera.setDisplayOrientation.
83+
transform.postRotate(displayOrientation);
84+
// Map camera driver coordinates to preview rect coordinates
85+
Matrix fill = new Matrix();
86+
fill.setRectToRect(CAMERA_DRIVER_RECT,
87+
previewRect,
88+
Matrix.ScaleToFit.FILL);
89+
// Concat the previous transform on top of the fill behavior.
90+
transform.setConcat(fill, transform);
91+
return transform;
92+
}
93+
94+
private Matrix inverse(Matrix source) {
95+
Matrix newMatrix = new Matrix();
96+
source.invert(newMatrix);
97+
return newMatrix;
98+
}
99+
100+
private boolean hasNonZeroArea(RectF rect) {
101+
return rect.width() != 0 && rect.height() != 0;
102+
}
103+
}

0 commit comments

Comments
 (0)