Full pinch-to-zoom support! + pinch-to-zoom callbacks to draw

pull/1/head
Mattia Iavarone 7 years ago
parent 900e71dfa3
commit 0e3fbea03b
  1. 37
      camerakit/src/main/api16/com/flurgle/camerakit/Camera1.java
  2. 13
      camerakit/src/main/api21/com/flurgle/camerakit/Camera2.java
  3. 4
      camerakit/src/main/base/com/flurgle/camerakit/CameraImpl.java
  4. 13
      camerakit/src/main/java/com/flurgle/camerakit/CameraListener.java
  5. 74
      camerakit/src/main/java/com/flurgle/camerakit/CameraView.java
  6. 3
      camerakit/src/main/java/com/flurgle/camerakit/FocusMarkerLayout.java
  7. 6
      camerakit/src/main/java/com/flurgle/camerakit/GridLinesLayout.java
  8. 94
      camerakit/src/main/java/com/flurgle/camerakit/PinchToZoomLayout.java
  9. 4
      camerakit/src/main/res/values/attrs.xml
  10. 19
      demo/src/main/java/com/flurgle/camerakit/demo/MainActivity.java
  11. 56
      demo/src/main/res/layout/activity_main.xml

@ -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<Camera.Area> meteringAreas2 = computeMeteringAreas(x, y);
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1);
synchronized (mLock) {
@ -716,7 +724,6 @@ class Camera1 extends CameraImpl {
private List<Camera.Area> 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);

@ -36,6 +36,12 @@ class Camera2 extends CameraImpl {
private ConstantMapper.MapperImpl mMapper = new ConstantMapper.Mapper2();
private final HashMap<String, ExtraProperties> 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) {
}

@ -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();

@ -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) {
}
}

@ -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);
}

@ -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
}

@ -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();
}

@ -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;
}
}

@ -32,9 +32,7 @@
<attr name="cameraZoomMode" format="enum">
<enum name="off" value="0" />
<!-- TODO: Set to unique value when feature added -->
<enum name="pinch" value="0" />
<enum name="pinch" value="1" />
</attr>
<attr name="cameraGrid" format="enum">

@ -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;

@ -9,6 +9,8 @@
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@ -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 @@
</FrameLayout>
<!-- Pinch to zoom -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="2.5dp"
android:layout_height="match_parent"
android:background="@color/colorPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="3dp"
android:text="ZOOM MODE"
android:textColor="@android:color/black"
android:textSize="14sp"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/zoomModeRadioGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:checkedButton="@+id/zoomModeOff"
android:orientation="horizontal">
<RadioButton
android:id="@+id/zoomModeOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Off" />
<RadioButton
android:id="@+id/zoomModePinch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="Pinch to zoom" />
</RadioGroup>
</LinearLayout>
</FrameLayout>
<!-- Width -->
<FrameLayout
android:layout_width="match_parent"

Loading…
Cancel
Save