diff --git a/README.md b/README.md index 26ef8f02..55801a9f 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ motivation boost to push the library forward. app:cameraAudioBitRate="@integer/audio_bit_rate" app:cameraGestureTap="none|autoFocus|takePicture" app:cameraGestureLongTap="none|autoFocus|takePicture" - app:cameraGesturePinch="none|zoom|exposureCorrection" - app:cameraGestureScrollHorizontal="none|zoom|exposureCorrection" - app:cameraGestureScrollVertical="none|zoom|exposureCorrection" + app:cameraGesturePinch="none|zoom|exposureCorrection|filterControl1|filterControl2" + app:cameraGestureScrollHorizontal="none|zoom|exposureCorrection|filterControl1|filterControl2" + app:cameraGestureScrollVertical="none|zoom|exposureCorrection|filterControl1|filterControl2" app:cameraEngine="camera1|camera2" app:cameraPreview="glSurface|surface|texture" app:cameraFacing="back|front" diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java index 8787b889..2e72bb6a 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java @@ -221,6 +221,8 @@ public class CameraOptions1Test extends BaseTest { assertTrue(o.supports(GestureAction.TAKE_PICTURE)); assertTrue(o.supports(GestureAction.NONE)); assertTrue(o.supports(GestureAction.ZOOM)); + assertTrue(o.supports(GestureAction.FILTER_CONTROL_1)); + assertTrue(o.supports(GestureAction.FILTER_CONTROL_2)); assertFalse(o.supports(GestureAction.EXPOSURE_CORRECTION)); } diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index 1c0d7ecd..590ad079 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -25,6 +25,7 @@ import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filters; import com.otaliastudios.cameraview.filter.NoFilter; +import com.otaliastudios.cameraview.filters.DuotoneFilter; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.gesture.Gesture; @@ -355,6 +356,88 @@ public class CameraViewTest extends BaseTest { assertTrue(mockController.mExposureCorrectionChanged); } + @Test + public void testGestureAction_filterControl1() { + mockController.setMockEngineState(true); + DuotoneFilter filter = new DuotoneFilter(); // supports two parameters + filter.setParameter1(0F); + filter = spy(filter); + cameraView.setExperimental(true); + cameraView.setFilter(filter); + mockController.mExposureCorrectionChanged = false; + MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); + final FactorHolder factor = new FactorHolder(); + uiSync(new Runnable() { + @Override + public void run() { + cameraView.mScrollGestureFinder = new ScrollGestureFinder(cameraView.mCameraCallbacks) { + @Override + protected boolean handleTouchEvent(@NonNull MotionEvent event) { + setGesture(Gesture.SCROLL_HORIZONTAL); + return true; + } + + @Override + protected float getFactor() { + return factor.value; + } + }; + cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.FILTER_CONTROL_1); + } + }); + + // If factor is 0, we return the same value. The filter should not be notified. + factor.value = 0f; + cameraView.dispatchTouchEvent(event); + verify(filter, never()).setParameter1(anyFloat()); + + // For larger factors, the value is scaled. The filter should be notified. + factor.value = 1f; + cameraView.dispatchTouchEvent(event); + verify(filter, times(1)).setParameter1(anyFloat()); + } + + @Test + public void testGestureAction_filterControl2() { + mockController.setMockEngineState(true); + DuotoneFilter filter = new DuotoneFilter(); // supports two parameters + filter.setParameter2(0F); + filter = spy(filter); + cameraView.setExperimental(true); + cameraView.setFilter(filter); + mockController.mExposureCorrectionChanged = false; + MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); + final FactorHolder factor = new FactorHolder(); + uiSync(new Runnable() { + @Override + public void run() { + cameraView.mScrollGestureFinder = new ScrollGestureFinder(cameraView.mCameraCallbacks) { + @Override + protected boolean handleTouchEvent(@NonNull MotionEvent event) { + setGesture(Gesture.SCROLL_HORIZONTAL); + return true; + } + + @Override + protected float getFactor() { + return factor.value; + } + }; + cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.FILTER_CONTROL_2); + } + }); + + // If factor is 0, we return the same value. The filter should not be notified. + factor.value = 0f; + cameraView.dispatchTouchEvent(event); + verify(filter, never()).setParameter2(anyFloat()); + + // For larger factors, the value is scaled. The filter should be notified. + factor.value = 1f; + cameraView.dispatchTouchEvent(event); + verify(filter, times(1)).setParameter2(anyFloat()); + } + //endregion //region testMeasure diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index 25f90bff..559d24dc 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -273,6 +273,8 @@ public class CameraOptions { case AUTO_FOCUS: return isAutoFocusSupported(); case TAKE_PICTURE: + case FILTER_CONTROL_1: + case FILTER_CONTROL_2: case NONE: return true; case ZOOM: diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index bf7fb76e..9508da0d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -51,6 +51,8 @@ import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.FilterParser; import com.otaliastudios.cameraview.filter.Filters; import com.otaliastudios.cameraview.filter.NoFilter; +import com.otaliastudios.cameraview.filter.OneParameterFilter; +import com.otaliastudios.cameraview.filter.TwoParameterFilter; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.gesture.Gesture; @@ -627,6 +629,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver { mCameraEngine.setExposureCorrection(newValue, bounds, points, true); } break; + + case FILTER_CONTROL_1: + if (!mExperimental) break; + if (getFilter() instanceof OneParameterFilter) { + OneParameterFilter filter = (OneParameterFilter) getFilter(); + oldValue = filter.getParameter1(); + newValue = source.computeValue(oldValue, 0, 1); + if (newValue != oldValue) { + filter.setParameter1(newValue); + } + } + break; + + case FILTER_CONTROL_2: + if (!mExperimental) break; + if (getFilter() instanceof TwoParameterFilter) { + TwoParameterFilter filter = (TwoParameterFilter) getFilter(); + oldValue = filter.getParameter2(); + newValue = source.computeValue(oldValue, 0, 1); + if (newValue != oldValue) { + filter.setParameter2(newValue); + } + } + break; } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java index 0599f60f..8f38f89f 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java @@ -22,6 +22,8 @@ public enum Gesture { * * - {@link GestureAction#ZOOM} * - {@link GestureAction#EXPOSURE_CORRECTION} + * - {@link GestureAction#FILTER_CONTROL_1} + * - {@link GestureAction#FILTER_CONTROL_2} * - {@link GestureAction#NONE} */ PINCH(GestureType.CONTINUOUS), @@ -52,6 +54,8 @@ public enum Gesture { * * - {@link GestureAction#ZOOM} * - {@link GestureAction#EXPOSURE_CORRECTION} + * - {@link GestureAction#FILTER_CONTROL_1} + * - {@link GestureAction#FILTER_CONTROL_2} * - {@link GestureAction#NONE} */ SCROLL_HORIZONTAL(GestureType.CONTINUOUS), @@ -62,6 +66,8 @@ public enum Gesture { * * - {@link GestureAction#ZOOM} * - {@link GestureAction#EXPOSURE_CORRECTION} + * - {@link GestureAction#FILTER_CONTROL_1} + * - {@link GestureAction#FILTER_CONTROL_2} * - {@link GestureAction#NONE} */ SCROLL_VERTICAL(GestureType.CONTINUOUS); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java index 61294b96..b4fa3659 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java @@ -60,8 +60,27 @@ public enum GestureAction { * - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_VERTICAL} */ - EXPOSURE_CORRECTION(4, GestureType.CONTINUOUS); + EXPOSURE_CORRECTION(4, GestureType.CONTINUOUS), + /** + * Controls the first parameter of a real-time {@link com.otaliastudios.cameraview.filter.Filter}, + * if it accepts one. This action can be mapped to continuous gestures: + * + * - {@link Gesture#PINCH} + * - {@link Gesture#SCROLL_HORIZONTAL} + * - {@link Gesture#SCROLL_VERTICAL} + */ + FILTER_CONTROL_1(5, GestureType.CONTINUOUS), + + /** + * Controls the second parameter of a real-time {@link com.otaliastudios.cameraview.filter.Filter}, + * if it accepts one. This action can be mapped to continuous gestures: + * + * - {@link Gesture#PINCH} + * - {@link Gesture#SCROLL_HORIZONTAL} + * - {@link Gesture#SCROLL_VERTICAL} + */ + FILTER_CONTROL_2(6, GestureType.CONTINUOUS); final static GestureAction DEFAULT_PINCH = NONE; final static GestureAction DEFAULT_TAP = NONE; diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index 83eb4dfc..59905eb1 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -44,18 +44,24 @@ + + + + + + diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index f02ec291..87bea9cf 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -26,8 +26,8 @@ app:cameraGestureTap="autoFocus" app:cameraGestureLongTap="none" app:cameraGesturePinch="zoom" - app:cameraGestureScrollHorizontal="exposureCorrection" - app:cameraGestureScrollVertical="none" + app:cameraGestureScrollHorizontal="filterControl1" + app:cameraGestureScrollVertical="exposureCorrection" app:cameraMode="picture" app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"> diff --git a/docs/_posts/2018-12-20-gestures.md b/docs/_posts/2018-12-20-gestures.md index 2f446903..6a510ba8 100644 --- a/docs/_posts/2018-12-20-gestures.md +++ b/docs/_posts/2018-12-20-gestures.md @@ -26,21 +26,35 @@ Simple as that. There are two things to be noted: |Gesture|Description|Can be mapped to| |-------------|-----------|----------------| -|`PINCH`|Pinch gesture, typically assigned to the zoom control.|`zoom` `exposureCorrection` `none`| -|`TAP`|Single tap gesture, typically assigned to the focus control.|`autoFocus` `takePicture` `none`| -|`LONG_TAP`|Long tap gesture.|`autoFocus` `takePicture` `none`| -|`SCROLL_HORIZONTAL`|Horizontal movement gesture.|`zoom` `exposureCorrection` `none`| -|`SCROLL_VERTICAL`|Vertical movement gesture.|`zoom` `exposureCorrection` `none`| +|`PINCH`|Pinch gesture, typically assigned to the zoom control.|`ZOOM` `EXPOSURE_CORRECTION` `FILTER_CONTROL_1` `FILTER_CONTROL_2` `NONE`| +|`TAP`|Single tap gesture, typically assigned to the focus control.|`AUTO_FOCUS` `TAKE_PICTURE` `NONE`| +|`LONG_TAP`|Long tap gesture.|`AUTO_FOCUS` `TAKE_PICTURE` `NONE`| +|`SCROLL_HORIZONTAL`|Horizontal movement gesture.|`ZOOM` `EXPOSURE_CORRECTION` `FILTER_CONTROL_1` `FILTER_CONTROL_2` `NONE`| +|`SCROLL_VERTICAL`|Vertical movement gesture.|`ZOOM` `EXPOSURE_CORRECTION` `FILTER_CONTROL_1` `FILTER_CONTROL_2` `NONE`| + +### Gesture Actions + +Looking at this from the other side: + +|Gesture action|Description|Can be mapped to| +|--------------|-----------|----------------| +|`NONE`|Disables this gesture.|`TAP` `LONG_TAP` `PINCH` `SCROLL_HORIZONTAL` `SCROLL_VERTICAL`| +|`AUTO_FOCUS`|Launches an [auto-focus operation](controls.html#auto-focus) on the finger position.|`TAP` `LONG_TAP`| +|`TAKE_PICTURE`|Takes a picture using [takePicture](capturing-media.html).|`TAP` `LONG_TAP`| +|`ZOOM`|[Zooms](controls.html#zoom) in or out.|`PINCH` `SCROLL_HORIZONTAL` `SCROLL_VERTICAL`| +|`EXPOSURE_CORRECTION`|Controls the [exposure correction](controls.html#exposure-correction).|`PINCH` `SCROLL_HORIZONTAL` `SCROLL_VERTICAL`| +|`FILTER_CONTROL_1`|Controls the first parameter (if any) of a [real-time filter](filters.html).|`PINCH` `SCROLL_HORIZONTAL` `SCROLL_VERTICAL`| +|`FILTER_CONTROL_2`|Controls the second parameter (if any) of a [real-time filter](filters.html).|`PINCH` `SCROLL_HORIZONTAL` `SCROLL_VERTICAL`| ### XML Attributes ```xml + app:cameraGestureScrollHorizontal="zoom|exposureCorrection|filterControl1|filterControl2|none" + app:cameraGestureScrollVertical="zoom|exposureCorrection|filterControl1|filterControl2|none"/> ``` ### Related APIs diff --git a/docs/_posts/2019-08-06-filters.md b/docs/_posts/2019-08-06-filters.md index 1d8c6a0c..099ad559 100644 --- a/docs/_posts/2019-08-06-filters.md +++ b/docs/_posts/2019-08-06-filters.md @@ -70,7 +70,9 @@ filter that was previously set and return back to normal. |`TintFilter`|`Filters.TINT`|`@string/cameraview_filter_tint`| |`VignetteFilter`|`Filters.VIGNETTE`|`@string/cameraview_filter_vignette`| -Most of these filters accept input parameters to tune them. For example, `DuotoneFilter` will +### Filters controls + +Most of the provided filters accept input parameters to tune them. For example, `DuotoneFilter` will accept two colors to apply the duotone effect. ```java @@ -81,6 +83,14 @@ duotoneFilter.setSecondColor(Color.GREEN); You can change these values by acting on the filter object, before or after passing it to `CameraView`. Whenever something is changed, the updated values will be visible immediately in the next frame. +You can also map the first or second filter control to a gesture (like horizontal or vertical scrolling), +as explained in [the gesture documentation](gestures.html): + +```java +camera.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.FILTER_CONTROL_1); +camera.mapGesture(Gesture.SCROLL_VERTICAL, GestureAction.FILTER_CONTROL_2); +``` + ### Advanced usage Advanced users with OpenGL experience can create their own filters by implementing the `Filter` interface