Real-time filters gestures (#537)

* Add FILTER_CONTROL_1 and FILTER_CONTROL_2 to control filters with gestures

* Improve TintFilter and DuotoneFilter

* Display current filter in DemoApp

* Fix potential bug

* Rename outputSize

* Fix tests
pull/541/head
Mattia Iavarone 5 years ago committed by GitHub
parent bf41489279
commit 95b1b2cdc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      README.md
  2. 2
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraOptions1Test.java
  3. 85
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  4. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java
  5. 26
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  6. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
  7. 18
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
  8. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
  9. 6
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/Gesture.java
  10. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/gesture/GestureAction.java
  11. 6
      cameraview/src/main/res/values/attrs.xml
  12. 4
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  13. 4
      demo/src/main/res/layout/activity_camera.xml
  14. 30
      docs/_posts/2018-12-20-gestures.md
  15. 12
      docs/_posts/2019-08-06-filters.md

@ -82,9 +82,9 @@ motivation boost to push the library forward.
app:cameraAudioBitRate="@integer/audio_bit_rate" app:cameraAudioBitRate="@integer/audio_bit_rate"
app:cameraGestureTap="none|autoFocus|takePicture" app:cameraGestureTap="none|autoFocus|takePicture"
app:cameraGestureLongTap="none|autoFocus|takePicture" app:cameraGestureLongTap="none|autoFocus|takePicture"
app:cameraGesturePinch="none|zoom|exposureCorrection" app:cameraGesturePinch="none|zoom|exposureCorrection|filterControl1|filterControl2"
app:cameraGestureScrollHorizontal="none|zoom|exposureCorrection" app:cameraGestureScrollHorizontal="none|zoom|exposureCorrection|filterControl1|filterControl2"
app:cameraGestureScrollVertical="none|zoom|exposureCorrection" app:cameraGestureScrollVertical="none|zoom|exposureCorrection|filterControl1|filterControl2"
app:cameraEngine="camera1|camera2" app:cameraEngine="camera1|camera2"
app:cameraPreview="glSurface|surface|texture" app:cameraPreview="glSurface|surface|texture"
app:cameraFacing="back|front" app:cameraFacing="back|front"

@ -221,6 +221,8 @@ public class CameraOptions1Test extends BaseTest {
assertTrue(o.supports(GestureAction.TAKE_PICTURE)); assertTrue(o.supports(GestureAction.TAKE_PICTURE));
assertTrue(o.supports(GestureAction.NONE)); assertTrue(o.supports(GestureAction.NONE));
assertTrue(o.supports(GestureAction.ZOOM)); 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)); assertFalse(o.supports(GestureAction.EXPOSURE_CORRECTION));
} }

@ -25,6 +25,7 @@ import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.Filters; import com.otaliastudios.cameraview.filter.Filters;
import com.otaliastudios.cameraview.filter.NoFilter; import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.cameraview.filters.DuotoneFilter;
import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.gesture.Gesture;
@ -355,6 +356,90 @@ public class CameraViewTest extends BaseTest {
assertTrue(mockController.mExposureCorrectionChanged); 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 //endregion
//region testMeasure //region testMeasure

@ -273,6 +273,8 @@ public class CameraOptions {
case AUTO_FOCUS: case AUTO_FOCUS:
return isAutoFocusSupported(); return isAutoFocusSupported();
case TAKE_PICTURE: case TAKE_PICTURE:
case FILTER_CONTROL_1:
case FILTER_CONTROL_2:
case NONE: case NONE:
return true; return true;
case ZOOM: case ZOOM:

@ -51,6 +51,8 @@ import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.FilterParser; import com.otaliastudios.cameraview.filter.FilterParser;
import com.otaliastudios.cameraview.filter.Filters; import com.otaliastudios.cameraview.filter.Filters;
import com.otaliastudios.cameraview.filter.NoFilter; 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.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.gesture.Gesture;
@ -627,6 +629,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCameraEngine.setExposureCorrection(newValue, bounds, points, true); mCameraEngine.setExposureCorrection(newValue, bounds, points, true);
} }
break; 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;
} }
} }

@ -96,7 +96,8 @@ public abstract class BaseFilter implements Filter {
private int vertexTranformMatrixLocation = -1; private int vertexTranformMatrixLocation = -1;
private int vertexPositionLocation = -1; private int vertexPositionLocation = -1;
private int vertexTextureCoordinateLocation = -1; private int vertexTextureCoordinateLocation = -1;
private Size outputSize; private int programHandle = -1;
private Size size;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected String vertexPositionName = DEFAULT_VERTEX_POSITION_NAME; protected String vertexPositionName = DEFAULT_VERTEX_POSITION_NAME;
@ -127,6 +128,7 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public void onCreate(int programHandle) { public void onCreate(int programHandle) {
this.programHandle = programHandle;
vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName); vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName);
GlUtils.checkLocation(vertexPositionLocation, vertexPositionName); GlUtils.checkLocation(vertexPositionLocation, vertexPositionName);
vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName); vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName);
@ -139,6 +141,7 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public void onDestroy() { public void onDestroy() {
programHandle = -1;
vertexPositionLocation = -1; vertexPositionLocation = -1;
vertexTextureCoordinateLocation = -1; vertexTextureCoordinateLocation = -1;
vertexModelViewProjectionMatrixLocation = -1; vertexModelViewProjectionMatrixLocation = -1;
@ -153,15 +156,19 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public void setSize(int width, int height) { public void setSize(int width, int height) {
outputSize = new Size(width, height); size = new Size(width, height);
} }
@Override @Override
public void draw(float[] transformMatrix) { public void draw(float[] transformMatrix) {
if (programHandle == -1) {
LOG.w("Filter.draw() called after destroying the filter. This can happen rarely because of threading.");
} else {
onPreDraw(transformMatrix); onPreDraw(transformMatrix);
onDraw(); onDraw();
onPostDraw(); onPostDraw();
} }
}
protected void onPreDraw(float[] transformMatrix) { protected void onPreDraw(float[] transformMatrix) {
// Copy the model / view / projection matrix over. // Copy the model / view / projection matrix over.
@ -203,7 +210,7 @@ public abstract class BaseFilter implements Filter {
@Override @Override
public final BaseFilter copy() { public final BaseFilter copy() {
BaseFilter copy = onCopy(); BaseFilter copy = onCopy();
copy.setSize(outputSize.getWidth(), outputSize.getHeight()); copy.setSize(size.getWidth(), size.getHeight());
if (this instanceof OneParameterFilter) { if (this instanceof OneParameterFilter) {
((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1()); ((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1());
} }

@ -55,7 +55,8 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
*/ */
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public void setFirstColor(@ColorInt int color) { 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") @SuppressWarnings("WeakerAccess")
public void setSecondColor(@ColorInt int color) { 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 @Override
public void setParameter1(float value) { public void setParameter1(float value) {
// no easy way to transform 0...1 into a color. // no easy way to transform 0...1 into a color.
setFirstColor((int) (value * Integer.MAX_VALUE)); setFirstColor((int) (value * 0xFFFFFF));
} }
@Override @Override
public float getParameter1() { 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 @Override
public void setParameter2(float value) { public void setParameter2(float value) {
// no easy way to transform 0...1 into a color. // no easy way to transform 0...1 into a color.
setSecondColor((int) (value * Integer.MAX_VALUE)); setSecondColor((int) (value * 0xFFFFFF));
} }
@Override @Override
public float getParameter2() { 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 @NonNull

@ -42,7 +42,8 @@ public class TintFilter extends BaseFilter implements OneParameterFilter {
*/ */
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public void setTint(@ColorInt int color) { 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 @Override
public void setParameter1(float value) { public void setParameter1(float value) {
// no easy way to transform 0...1 into a color. // no easy way to transform 0...1 into a color.
setTint((int) (value * Integer.MAX_VALUE)); setTint((int) (value * 0xFFFFFF));
} }
@Override @Override
public float getParameter1() { 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 @NonNull

@ -22,6 +22,8 @@ public enum Gesture {
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#FILTER_CONTROL_1}
* - {@link GestureAction#FILTER_CONTROL_2}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
PINCH(GestureType.CONTINUOUS), PINCH(GestureType.CONTINUOUS),
@ -52,6 +54,8 @@ public enum Gesture {
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#FILTER_CONTROL_1}
* - {@link GestureAction#FILTER_CONTROL_2}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
SCROLL_HORIZONTAL(GestureType.CONTINUOUS), SCROLL_HORIZONTAL(GestureType.CONTINUOUS),
@ -62,6 +66,8 @@ public enum Gesture {
* *
* - {@link GestureAction#ZOOM} * - {@link GestureAction#ZOOM}
* - {@link GestureAction#EXPOSURE_CORRECTION} * - {@link GestureAction#EXPOSURE_CORRECTION}
* - {@link GestureAction#FILTER_CONTROL_1}
* - {@link GestureAction#FILTER_CONTROL_2}
* - {@link GestureAction#NONE} * - {@link GestureAction#NONE}
*/ */
SCROLL_VERTICAL(GestureType.CONTINUOUS); SCROLL_VERTICAL(GestureType.CONTINUOUS);

@ -60,8 +60,27 @@ public enum GestureAction {
* - {@link Gesture#SCROLL_HORIZONTAL} * - {@link Gesture#SCROLL_HORIZONTAL}
* - {@link Gesture#SCROLL_VERTICAL} * - {@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_PINCH = NONE;
final static GestureAction DEFAULT_TAP = NONE; final static GestureAction DEFAULT_TAP = NONE;

@ -44,18 +44,24 @@
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="3" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="4" /> <enum name="exposureCorrection" value="4" />
<enum name="filterControl1" value="5" />
<enum name="filterControl2" value="6" />
</attr> </attr>
<attr name="cameraGestureScrollHorizontal" format="enum"> <attr name="cameraGestureScrollHorizontal" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="3" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="4" /> <enum name="exposureCorrection" value="4" />
<enum name="filterControl1" value="5" />
<enum name="filterControl2" value="6" />
</attr> </attr>
<attr name="cameraGestureScrollVertical" format="enum"> <attr name="cameraGestureScrollVertical" format="enum">
<enum name="none" value="0" /> <enum name="none" value="0" />
<enum name="zoom" value="3" /> <enum name="zoom" value="3" />
<enum name="exposureCorrection" value="4" /> <enum name="exposureCorrection" value="4" />
<enum name="filterControl1" value="5" />
<enum name="filterControl2" value="6" />
</attr> </attr>
<attr name="cameraEngine" format="enum"> <attr name="cameraEngine" format="enum">

@ -331,7 +331,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
} else { } else {
mCurrentFilter = 0; mCurrentFilter = 0;
} }
camera.setFilter(mAllFilters[mCurrentFilter].newInstance()); Filters filter = mAllFilters[mCurrentFilter];
camera.setFilter(filter.newInstance());
message(filter.toString(), false);
} }
@Override @Override

@ -26,8 +26,8 @@
app:cameraGestureTap="autoFocus" app:cameraGestureTap="autoFocus"
app:cameraGestureLongTap="none" app:cameraGestureLongTap="none"
app:cameraGesturePinch="zoom" app:cameraGesturePinch="zoom"
app:cameraGestureScrollHorizontal="exposureCorrection" app:cameraGestureScrollHorizontal="filterControl1"
app:cameraGestureScrollVertical="none" app:cameraGestureScrollVertical="exposureCorrection"
app:cameraMode="picture" app:cameraMode="picture"
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"> app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker">

@ -26,21 +26,35 @@ Simple as that. There are two things to be noted:
|Gesture|Description|Can be mapped to| |Gesture|Description|Can be mapped to|
|-------------|-----------|----------------| |-------------|-----------|----------------|
|`PINCH`|Pinch gesture, typically assigned to the zoom control.|`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.|`autoFocus` `takePicture` `none`| |`TAP`|Single tap gesture, typically assigned to the focus control.|`AUTO_FOCUS` `TAKE_PICTURE` `NONE`|
|`LONG_TAP`|Long tap gesture.|`autoFocus` `takePicture` `none`| |`LONG_TAP`|Long tap gesture.|`AUTO_FOCUS` `TAKE_PICTURE` `NONE`|
|`SCROLL_HORIZONTAL`|Horizontal movement gesture.|`zoom` `exposureCorrection` `none`| |`SCROLL_HORIZONTAL`|Horizontal movement gesture.|`ZOOM` `EXPOSURE_CORRECTION` `FILTER_CONTROL_1` `FILTER_CONTROL_2` `NONE`|
|`SCROLL_VERTICAL`|Vertical movement gesture.|`zoom` `exposureCorrection` `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 Attributes
```xml ```xml
<com.otaliastudios.cameraview.CameraView <com.otaliastudios.cameraview.CameraView
app:cameraGesturePinch="zoom|exposureCorrection|none" app:cameraGesturePinch="zoom|exposureCorrection|filterControl1|filterControl2|none"
app:cameraGestureTap="autoFocus|takePicture|none" app:cameraGestureTap="autoFocus|takePicture|none"
app:cameraGestureLongTap="autoFocus|takePicture|none" app:cameraGestureLongTap="autoFocus|takePicture|none"
app:cameraGestureScrollHorizontal="zoom|exposureCorrection|none" app:cameraGestureScrollHorizontal="zoom|exposureCorrection|filterControl1|filterControl2|none"
app:cameraGestureScrollVertical="zoom|exposureCorrection|none"/> app:cameraGestureScrollVertical="zoom|exposureCorrection|filterControl1|filterControl2|none"/>
``` ```
### Related APIs ### Related APIs

@ -70,7 +70,9 @@ filter that was previously set and return back to normal.
|`TintFilter`|`Filters.TINT`|`@string/cameraview_filter_tint`| |`TintFilter`|`Filters.TINT`|`@string/cameraview_filter_tint`|
|`VignetteFilter`|`Filters.VIGNETTE`|`@string/cameraview_filter_vignette`| |`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. accept two colors to apply the duotone effect.
```java ```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`. 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. 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 usage
Advanced users with OpenGL experience can create their own filters by implementing the `Filter` interface Advanced users with OpenGL experience can create their own filters by implementing the `Filter` interface

Loading…
Cancel
Save