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..8bf95be9 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,90 @@ public class CameraViewTest extends BaseTest {
assertTrue(mockController.mExposureCorrectionChanged);
}
+ @Test
+ public void testGestureAction_filterControl1() {
+ mockController.setMockEngineState(true);
+ mockController.setMockCameraOptions(mock(CameraOptions.class));
+ 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);
+ mockController.setMockCameraOptions(mock(CameraOptions.class));
+ 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/filter/BaseFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
index e85d2a9d..9016a8ab 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
@@ -96,7 +96,8 @@ public abstract class BaseFilter implements Filter {
private int vertexTranformMatrixLocation = -1;
private int vertexPositionLocation = -1;
private int vertexTextureCoordinateLocation = -1;
- private Size outputSize;
+ private int programHandle = -1;
+ private Size size;
@SuppressWarnings("WeakerAccess")
protected String vertexPositionName = DEFAULT_VERTEX_POSITION_NAME;
@@ -127,6 +128,7 @@ public abstract class BaseFilter implements Filter {
@Override
public void onCreate(int programHandle) {
+ this.programHandle = programHandle;
vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName);
GlUtils.checkLocation(vertexPositionLocation, vertexPositionName);
vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName);
@@ -139,6 +141,7 @@ public abstract class BaseFilter implements Filter {
@Override
public void onDestroy() {
+ programHandle = -1;
vertexPositionLocation = -1;
vertexTextureCoordinateLocation = -1;
vertexModelViewProjectionMatrixLocation = -1;
@@ -153,14 +156,18 @@ public abstract class BaseFilter implements Filter {
@Override
public void setSize(int width, int height) {
- outputSize = new Size(width, height);
+ size = new Size(width, height);
}
@Override
public void draw(float[] transformMatrix) {
- onPreDraw(transformMatrix);
- onDraw();
- onPostDraw();
+ if (programHandle == -1) {
+ LOG.w("Filter.draw() called after destroying the filter. This can happen rarely because of threading.");
+ } else {
+ onPreDraw(transformMatrix);
+ onDraw();
+ onPostDraw();
+ }
}
protected void onPreDraw(float[] transformMatrix) {
@@ -203,7 +210,7 @@ public abstract class BaseFilter implements Filter {
@Override
public final BaseFilter copy() {
BaseFilter copy = onCopy();
- copy.setSize(outputSize.getWidth(), outputSize.getHeight());
+ copy.setSize(size.getWidth(), size.getHeight());
if (this instanceof OneParameterFilter) {
((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1());
}
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
index 5700c542..385ed99b 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
@@ -55,7 +55,8 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
*/
@SuppressWarnings("WeakerAccess")
public void setFirstColor(@ColorInt int color) {
- mFirstColor = color;
+ // Remove any alpha.
+ mFirstColor = Color.rgb(Color.red(color), Color.green(color), Color.blue(color));
}
/**
@@ -66,7 +67,8 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
*/
@SuppressWarnings("WeakerAccess")
public void setSecondColor(@ColorInt int color) {
- mSecondColor = color;
+ // Remove any alpha.
+ mSecondColor = Color.rgb(Color.red(color), Color.green(color), Color.blue(color));
}
/**
@@ -96,23 +98,27 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
@Override
public void setParameter1(float value) {
// no easy way to transform 0...1 into a color.
- setFirstColor((int) (value * Integer.MAX_VALUE));
+ setFirstColor((int) (value * 0xFFFFFF));
}
@Override
public float getParameter1() {
- return (float) getFirstColor() / Integer.MAX_VALUE;
+ int color = getFirstColor();
+ color = Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
+ return (float) color / 0xFFFFFF;
}
@Override
public void setParameter2(float value) {
// no easy way to transform 0...1 into a color.
- setSecondColor((int) (value * Integer.MAX_VALUE));
+ setSecondColor((int) (value * 0xFFFFFF));
}
@Override
public float getParameter2() {
- return (float) getSecondColor() / Integer.MAX_VALUE;
+ int color = getSecondColor();
+ color = Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
+ return (float) color / 0xFFFFFF;
}
@NonNull
diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
index e5f31769..3958c20f 100644
--- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
+++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
@@ -42,7 +42,8 @@ public class TintFilter extends BaseFilter implements OneParameterFilter {
*/
@SuppressWarnings("WeakerAccess")
public void setTint(@ColorInt int color) {
- this.tint = color;
+ // Remove any alpha.
+ this.tint = Color.rgb(Color.red(color), Color.green(color), Color.blue(color));
}
/**
@@ -60,12 +61,14 @@ public class TintFilter extends BaseFilter implements OneParameterFilter {
@Override
public void setParameter1(float value) {
// no easy way to transform 0...1 into a color.
- setTint((int) (value * Integer.MAX_VALUE));
+ setTint((int) (value * 0xFFFFFF));
}
@Override
public float getParameter1() {
- return (float) getTint() / Integer.MAX_VALUE;
+ int color = getTint();
+ color = Color.argb(0, Color.red(color), Color.green(color), Color.blue(color));
+ return (float) color / 0xFFFFFF;
}
@NonNull
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/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
index 672765a2..8b97aa4a 100644
--- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
+++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
@@ -331,7 +331,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
} else {
mCurrentFilter = 0;
}
- camera.setFilter(mAllFilters[mCurrentFilter].newInstance());
+ Filters filter = mAllFilters[mCurrentFilter];
+ camera.setFilter(filter.newInstance());
+ message(filter.toString(), false);
}
@Override
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