From 0e3fbea03b6fe26c1f4138a64bda64f43d2ea195 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Sun, 6 Aug 2017 02:33:50 +0200 Subject: [PATCH] Full pinch-to-zoom support! + pinch-to-zoom callbacks to draw --- .../api16/com/flurgle/camerakit/Camera1.java | 37 +++++--- .../api21/com/flurgle/camerakit/Camera2.java | 13 +-- .../com/flurgle/camerakit/CameraImpl.java | 4 +- .../com/flurgle/camerakit/CameraListener.java | 13 +++ .../com/flurgle/camerakit/CameraView.java | 74 +++++++++++++-- .../flurgle/camerakit/FocusMarkerLayout.java | 3 +- .../flurgle/camerakit/GridLinesLayout.java | 6 +- .../flurgle/camerakit/PinchToZoomLayout.java | 94 +++++++++++++++++++ camerakit/src/main/res/values/attrs.xml | 4 +- .../flurgle/camerakit/demo/MainActivity.java | 19 ++++ demo/src/main/res/layout/activity_main.xml | 56 +++++++++++ 11 files changed, 284 insertions(+), 39 deletions(-) create mode 100644 camerakit/src/main/java/com/flurgle/camerakit/PinchToZoomLayout.java diff --git a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java index 91f7e907..8fecf547 100644 --- a/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java +++ b/camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java @@ -47,7 +47,6 @@ class Camera1 extends CameraImpl { private double mLatitude; private double mLongitude; - private boolean mFocusOnTap; private Handler mFocusHandler = new Handler(); private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper1(); @@ -308,7 +307,6 @@ class Camera1 extends CameraImpl { if (mergeFocus(params, old)) mCamera.setParameters(params); } } - mFocusOnTap = mFocus == FOCUS_TAP || mFocus == FOCUS_TAP_WITH_MARKER; } @@ -341,15 +339,12 @@ class Camera1 extends CameraImpl { } - @Override - void setZoomMode(@ZoomMode int zoom) { - } - @Override void setVideoQuality(int videoQuality) { if (mIsCapturingVideo) { throw new IllegalStateException("Can't change video quality while recording a video."); } + mVideoQuality = videoQuality; if (isCameraOpened() && mSessionType == CameraKit.Constants.SESSION_TYPE_VIDEO) { // Change capture size to a size that fits the video aspect ratio. @@ -657,18 +652,31 @@ class Camera1 extends CameraImpl { } } + // ----------------- + // Zoom stuff. + + + @Override + boolean setZoom(float zoom) { + if (!isCameraOpened()) return false; + synchronized (mLock) { + Camera.Parameters params = mCamera.getParameters(); + if (!params.isZoomSupported()) return false; + float max = params.getMaxZoom(); + params.setZoom((int) (zoom * max)); + mCamera.setParameters(params); + return true; + } + } + // ----------------- // Tap to focus stuff. @Override - void onTouchEvent(MotionEvent event) { - if (!mFocusOnTap) return; - if (mCamera == null) return; - if (event.getAction() != MotionEvent.ACTION_UP) return; - final float x = event.getX(); - final float y = event.getY(); + void startFocus(final float x, final float y) { + if (!isCameraOpened()) return; List meteringAreas2 = computeMeteringAreas(x, y); List meteringAreas1 = meteringAreas2.subList(0, 1); synchronized (mLock) { @@ -716,7 +724,6 @@ class Camera1 extends CameraImpl { private List computeMeteringAreas(double viewClickX, double viewClickY) { - // Event came in view coordinates. We must rotate to sensor coordinates. // First, rescale to the -1000 ... 1000 range. int displayToSensor = -computeSensorToDisplayOffset(); @@ -730,8 +737,8 @@ class Camera1 extends CameraImpl { double theta = ((double) displayToSensor) * Math.PI / 180; double sensorClickX = viewClickX * Math.cos(theta) - viewClickY * Math.sin(theta); double sensorClickY = viewClickX * Math.sin(theta) + viewClickY * Math.cos(theta); - Log.e(TAG, "viewClickX:"+viewClickX+", viewClickY:"+viewClickY); - Log.e(TAG, "sensorClickX:"+sensorClickX+", sensorClickY:"+sensorClickY); + // Log.e(TAG, "viewClickX:"+viewClickX+", viewClickY:"+viewClickY); + // Log.e(TAG, "sensorClickX:"+sensorClickX+", sensorClickY:"+sensorClickY); // Compute the rect bounds. Rect rect1 = computeMeteringArea(sensorClickX, sensorClickY, 150d); diff --git a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java index 829c4eef..f2c184be 100644 --- a/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java +++ b/camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java @@ -36,6 +36,12 @@ class Camera2 extends CameraImpl { private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper2(); private final HashMap mExtraPropertiesMap = new HashMap<>(); + + @Override + boolean setZoom(float zoom) { + return false; + } + @Override public void onSurfaceChanged() { @@ -156,11 +162,6 @@ class Camera2 extends CameraImpl { } - @Override - void setZoomMode(@ZoomMode int zoom) { - - } - @Override void setLocation(double latitude, double longitude) { @@ -259,7 +260,7 @@ class Camera2 extends CameraImpl { } @Override - void onTouchEvent(MotionEvent event) { + void startFocus(float x, float y) { } diff --git a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java index afba5222..be14d3a3 100644 --- a/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java +++ b/camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java @@ -30,10 +30,10 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback { abstract void onDisplayOffset(int displayOrientation); abstract void onDeviceOrientation(int deviceOrientation); + abstract boolean setZoom(float zoom); abstract void setFacing(@Facing int facing); abstract void setFlash(@Flash int flash); abstract void setFocus(@Focus int focus); - abstract void setZoomMode(@ZoomMode int zoom); abstract void setVideoQuality(@VideoQuality int videoQuality); abstract void setWhiteBalance(@WhiteBalance int whiteBalance); abstract void setSessionType(@SessionType int sessionType); @@ -49,7 +49,7 @@ abstract class CameraImpl implements PreviewImpl.SurfaceCallback { abstract boolean shouldFlipSizes(); // Wheter the Sizes should be flipped to match the view orientation. abstract boolean isCameraOpened(); - abstract void onTouchEvent(MotionEvent event); + abstract void startFocus(float x, float y); @Nullable abstract ExtraProperties getExtraProperties(); diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java index 19141e33..9fddcb07 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java @@ -1,5 +1,6 @@ package com.flurgle.camerakit; +import android.graphics.PointF; import android.graphics.YuvImage; import java.io.File; @@ -49,4 +50,16 @@ public abstract class CameraListener { } + + /** + * Noitifies that a finger pinch-to-zoom gesture just caused the camera zoom + * to be changed. This can be used to draw, for example, a seek bar. + * + * @param zoom the new zoom value, 0...1 + * @param fingers finger positions that caused the event + */ + public void onZoomChanged(float zoom, PointF[] fingers) { + + } + } \ No newline at end of file diff --git a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java index a194c150..e7b08368 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/CameraView.java @@ -11,6 +11,7 @@ import android.content.ContextWrapper; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.TypedArray; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.YuvImage; import android.os.Handler; @@ -83,7 +84,6 @@ public class CameraView extends FrameLayout implements LifecycleObserver { getWorkerHandler().post(runnable); } - @ZoomMode private int mZoom; private int mJpegQuality; private boolean mCropOutput; private int mDisplayOffset; @@ -95,6 +95,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { private Lifecycle mLifecycle; private FocusMarkerLayout mFocusMarkerLayout; private GridLinesLayout mGridLinesLayout; + private PinchToZoomLayout mPinchToZoomLayout; private boolean mIsStarted; private boolean mKeepScreenOn; @@ -118,7 +119,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { int whiteBalance = a.getInteger(R.styleable.CameraView_cameraWhiteBalance, CameraKit.Defaults.DEFAULT_WHITE_BALANCE); int videoQuality = a.getInteger(R.styleable.CameraView_cameraVideoQuality, CameraKit.Defaults.DEFAULT_VIDEO_QUALITY); int grid = a.getInteger(R.styleable.CameraView_cameraGrid, CameraKit.Defaults.DEFAULT_GRID); - mZoom = a.getInteger(R.styleable.CameraView_cameraZoomMode, CameraKit.Defaults.DEFAULT_ZOOM); + int zoom = a.getInteger(R.styleable.CameraView_cameraZoomMode, CameraKit.Defaults.DEFAULT_ZOOM); mJpegQuality = a.getInteger(R.styleable.CameraView_cameraJpegQuality, CameraKit.Defaults.DEFAULT_JPEG_QUALITY); mCropOutput = a.getBoolean(R.styleable.CameraView_cameraCropOutput, CameraKit.Defaults.DEFAULT_CROP_OUTPUT); a.recycle(); @@ -128,15 +129,17 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraImpl = new Camera1(mCameraCallbacks, mPreviewImpl); mFocusMarkerLayout = new FocusMarkerLayout(context); mGridLinesLayout = new GridLinesLayout(context); + mPinchToZoomLayout = new PinchToZoomLayout(context); addView(mFocusMarkerLayout); addView(mGridLinesLayout); + addView(mPinchToZoomLayout); mIsStarted = false; setFacing(facing); setFlash(flash); setFocus(focus); setSessionType(sessionType); - setZoomMode(mZoom); + setZoomMode(zoom); setVideoQuality(videoQuality); setWhiteBalance(whiteBalance); setGrid(grid); @@ -319,15 +322,23 @@ public class CameraView extends FrameLayout implements LifecycleObserver { return true; // Steal our own events. } + @Override public boolean onTouchEvent(MotionEvent event) { // And dispatch to everyone. - mFocusMarkerLayout.onTouchEvent(event); // For drawing focus marker. - mCameraImpl.onTouchEvent(event); // For focus behavior. - mGridLinesLayout.onTouchEvent(event); // For grid drawing. + if (mPinchToZoomLayout.onTouchEvent(event)) { + // For pinch-to-zoom. + float zoom = mPinchToZoomLayout.getZoom(); + boolean did = setZoom(zoom); + if (did) mCameraCallbacks.dispatchOnZoomChanged(zoom, mPinchToZoomLayout.getPoints()); + } else if (mFocusMarkerLayout.onTouchEvent(event)) { + // For drawing focus marker. + startFocus(event.getX(), event.getY()); // For focus behavior. + } return true; } + /** * Returns whether the camera has started showing its preview. * @return whether the camera has started @@ -477,6 +488,26 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * Sets a zoom value. This is not guaranteed to be supported by the current device. + * Look at the returned boolean to check. + * Zoom value should be >= 0 and <= 1, where 1 will be the maximum available zoom. + * + * @param zoom value in [0,1] + */ + public boolean setZoom(float zoom) { + if (zoom < 0 || zoom > 1) { + throw new IllegalArgumentException("Zoom value should be >= 0 and <= 1"); + } + if (mCameraImpl.setZoom(zoom)) { + // Notify PinchToZoomLayout, just in case the call came from outside. + mPinchToZoomLayout.onExternalZoom(zoom); + return true; + } + return false; + } + + /** * Controls the grids to be drawn over the current layout. * @@ -643,6 +674,20 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + /** + * Starts an autofocus process at the given coordinates, with respect + * to the view width and height. + * + * @param x should be >= 0 and <= getWidth() + * @param y should be >= 0 and <= getHeight() + */ + public void startFocus(float x, float y) { + if (x < 0 || x > getWidth()) throw new IllegalArgumentException("x should be >= 0 and <= getWidth()"); + if (y < 0 || y > getHeight()) throw new IllegalArgumentException("y should be >= 0 and <= getHeight()"); + mCameraImpl.startFocus(x, y); + } + + /** * Sets the current focus behavior. * @@ -738,8 +783,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { * @param zoom the zoom mode */ public void setZoomMode(@ZoomMode int zoom) { - this.mZoom = zoom; - mCameraImpl.setZoomMode(mZoom); + mPinchToZoomLayout.setZoomMode(zoom); } @@ -749,7 +793,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { */ @ZoomMode public int getZoomMode() { - return mZoom; + return mPinchToZoomLayout.getZoomMode(); } @@ -1159,6 +1203,18 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } + public void dispatchOnZoomChanged(final float zoom, final PointF[] fingers) { + uiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onZoomChanged(zoom, fingers); + } + } + }); + } + + private void addListener(@NonNull CameraListener cameraListener) { mListeners.add(cameraListener); } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/FocusMarkerLayout.java b/camerakit/src/main/java/com/flurgle/camerakit/FocusMarkerLayout.java index 1261078e..78706ec0 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/FocusMarkerLayout.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/FocusMarkerLayout.java @@ -12,7 +12,7 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; -public class FocusMarkerLayout extends FrameLayout { +class FocusMarkerLayout extends FrameLayout { private FrameLayout mFocusMarkerContainer; private ImageView mFill; @@ -35,6 +35,7 @@ public class FocusMarkerLayout extends FrameLayout { int action = event.getAction(); if (action == MotionEvent.ACTION_UP && isEnabled()) { focus(event.getX(), event.getY()); + return true; } return false; // We didn't consume, pass } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java b/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java index 8760f255..54f21e29 100644 --- a/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java +++ b/camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java @@ -12,7 +12,7 @@ import android.util.TypedValue; import android.view.View; // TODO animate lines! -public class GridLinesLayout extends View { +class GridLinesLayout extends View { @Grid private int gridMode; @@ -41,11 +41,11 @@ public class GridLinesLayout extends View { } @Grid - int getGridMode() { + public int getGridMode() { return gridMode; } - void setGridMode(@Grid int gridMode) { + public void setGridMode(@Grid int gridMode) { this.gridMode = gridMode; invalidate(); } diff --git a/camerakit/src/main/java/com/flurgle/camerakit/PinchToZoomLayout.java b/camerakit/src/main/java/com/flurgle/camerakit/PinchToZoomLayout.java new file mode 100644 index 00000000..9117eb54 --- /dev/null +++ b/camerakit/src/main/java/com/flurgle/camerakit/PinchToZoomLayout.java @@ -0,0 +1,94 @@ +package com.flurgle.camerakit; + +import android.content.Context; +import android.graphics.PointF; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; + +class PinchToZoomLayout extends View { + + + private ScaleGestureDetector detector; + private boolean notify; + @ZoomMode private int mZoomMode; + private float mZoom = 0; + private PointF[] mPoints = new PointF[]{ + new PointF(0, 0), + new PointF(0, 0) + }; + + public PinchToZoomLayout(@NonNull Context context) { + this(context, null); + } + + public PinchToZoomLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + detector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public boolean onScale(ScaleGestureDetector detector) { + notify = true; + mZoom += ((detector.getScaleFactor() - 1) * 2); + if (mZoom < 0) mZoom = 0; + if (mZoom > 1) mZoom = 1; + return true; + } + }); + + if (Build.VERSION.SDK_INT >= 19) { + detector.setQuickScaleEnabled(false); + } + } + + @ZoomMode + public int getZoomMode() { + return mZoomMode; + } + + public void setZoomMode(@ZoomMode int zoomMode) { + mZoomMode = zoomMode; + } + + public float getZoom() { + return mZoom; + } + + public void onExternalZoom(float zoom) { + mZoom = zoom; + } + + public PointF[] getPoints() { + return mPoints; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mZoomMode != CameraKit.Constants.ZOOM_PINCH) return false; + + // Reset the notify flag on a new gesture. + // This is to ensure that the notify flag stays on until the + // previous gesture ends. + if (event.getAction() == MotionEvent.ACTION_DOWN) { + notify = false; + } + + // Let's see if we detect something. This will call onScale(). + detector.onTouchEvent(event); + + // Keep notifying CameraView as long as the gesture goes. + if (notify) { + mPoints[0].x = event.getX(0); + mPoints[0].y = event.getY(0); + if (event.getPointerCount() > 1) { + mPoints[1].x = event.getX(1); + mPoints[1].y = event.getY(1); + } + return true; + } + return false; + } +} diff --git a/camerakit/src/main/res/values/attrs.xml b/camerakit/src/main/res/values/attrs.xml index e13ad4f7..6d120e05 100644 --- a/camerakit/src/main/res/values/attrs.xml +++ b/camerakit/src/main/res/values/attrs.xml @@ -32,9 +32,7 @@ - - - + diff --git a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java index 11008199..d532ee70 100644 --- a/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java +++ b/demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java @@ -40,6 +40,10 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan @BindView(R.id.cropModeRadioGroup) RadioGroup cropModeRadioGroup; + // Pinch to zoom: + @BindView(R.id.zoomModeRadioGroup) + RadioGroup zoomModeRadioGroup; + // Video Quality: @BindView(R.id.videoQualityRadioGroup) RadioGroup videoQualityRadioGroup; @@ -96,6 +100,7 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan heightModeRadioGroup.setOnCheckedChangeListener(heightModeChangedListener); videoQualityRadioGroup.setOnCheckedChangeListener(videoQualityChangedListener); gridModeRadioGroup.setOnCheckedChangeListener(gridModeChangedListener); + zoomModeRadioGroup.setOnCheckedChangeListener(zoomModeChangedListener); } private void message(String content, boolean important) { @@ -270,6 +275,20 @@ public class MainActivity extends AppCompatActivity implements View.OnLayoutChan } }; + + RadioGroup.OnCheckedChangeListener zoomModeChangedListener = new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + int zoom = CameraKit.Constants.ZOOM_OFF; + switch (checkedId) { + case R.id.zoomModeOff: zoom = CameraKit.Constants.ZOOM_OFF; break; + case R.id.zoomModePinch: zoom = CameraKit.Constants.ZOOM_PINCH; break; + } + camera.setZoomMode(zoom); + message("Zoom mode changed! Note that pinch-to-zoom won't work well in ScrollViews like here", true); + } + }; + @OnClick(R.id.widthUpdate) void widthUpdateClicked() { if (mCapturingPicture) return; diff --git a/demo/src/main/res/layout/activity_main.xml b/demo/src/main/res/layout/activity_main.xml index 40c5b126..dada4f89 100644 --- a/demo/src/main/res/layout/activity_main.xml +++ b/demo/src/main/res/layout/activity_main.xml @@ -9,6 +9,8 @@ android:focusable="true" android:focusableInTouchMode="true"> @@ -23,6 +25,7 @@ android:layout_height="400dp" android:layout_gravity="center_horizontal" android:keepScreenOn="true" + app:cameraZoomMode="off" app:cameraGrid="off" app:cameraCropOutput="false" app:cameraFacing="back" @@ -193,6 +196,59 @@ + + + + + + + + + + + + + + + + + + + + +