Improve realtime filters (#535)

* Simplify Filters class

* Simplify filter switching in demo app

* Create FilterCameraPreview, improve GlCameraPreview

* Add comments

* Cleanup EglViewport

* Rename setPreviewingViewSize

* Create Filter interface and BaseFilter abstract class

* Move GL drawing code into BaseFilter

* Avoid releasing location pointers

* Add more docs and Filter.copy()

* Split two packages

* Remove filters package from code coverage computation

* Document all filters, implement onCopy, suppress warnings

* Add javadocs in Filters class

* Move NoFilter, add string resources

* XML support, require experimental flag

* Update first 6 filters with onPreDraw

* Update DuotoneFilter with onPreDraw

* Update FillLightFilter with onPreDraw

* Update Gamma, Grain, Grayscale, Hue, InvertColors, Lomoish with onPreDraw

* Update Posterize, Saturation, Sepia with onPreDraw

* Update all filters with onPreDraw

* Add OneParameterFilter and TwoParameterFilter

* Implement OneParameterFilter and TwoParameterFilter in all filters

* Improve comments

* Remove commented out code in demo

* Add FilterParser test

* Add GlCameraPreview and CameraView tests

* Add documentation

* Fix tests
pull/541/head
Mattia Iavarone 6 years ago committed by GitHub
parent facd26f11d
commit bf41489279
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 3
      cameraview/build.gradle
  3. 31
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  4. 16
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  5. 52
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/FilterParserTest.java
  6. 6
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/CameraPreviewTest.java
  7. 21
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/GlCameraPreviewTest.java
  8. 15
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/MockCameraPreview.java
  9. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreviewTest.java
  10. 4
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/preview/TextureCameraPreviewTest.java
  11. 70
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  12. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/controls/Preview.java
  13. 226
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java
  14. 91
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filter.java
  15. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/FilterParser.java
  16. 121
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/Filters.java
  17. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/NoFilter.java
  18. 30
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/OneParameterFilter.java
  19. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/filter/TwoParameterFilter.java
  20. 164
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java
  21. 37
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java
  22. 96
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java
  23. 97
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java
  24. 72
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java
  25. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java
  26. 169
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java
  27. 171
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
  28. 130
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java
  29. 128
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java
  30. 124
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java
  31. 95
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java
  32. 186
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java
  33. 29
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrayscaleFilter.java
  34. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java
  35. 130
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java
  36. 40
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java
  37. 143
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java
  38. 165
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java
  39. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java
  40. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java
  41. 174
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java
  42. 63
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java
  43. 149
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java
  44. 115
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java
  45. 119
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
  46. 201
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java
  47. 35
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java
  48. 7
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java
  49. 171
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  50. 13
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  51. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/FilterCameraPreview.java
  52. 48
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  53. 4
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererFrameCallback.java
  54. 29
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  55. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java
  56. 2
      cameraview/src/main/res/values/attrs.xml
  57. 23
      cameraview/src/main/res/values/strings.xml
  58. 89
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  59. 2
      docs/_posts/2018-12-20-debugging.md
  60. 2
      docs/_posts/2018-12-20-error-handling.md
  61. 2
      docs/_posts/2018-12-20-more-features.md
  62. 2
      docs/_posts/2018-12-20-previews.md
  63. 2
      docs/_posts/2018-12-20-runtime-permissions.md
  64. 102
      docs/_posts/2019-08-06-filters.md
  65. 1
      docs/index.md

@ -21,6 +21,7 @@ api 'com.otaliastudios:cameraview:2.0.0'
- Fast & reliable
- Gestures support [[docs]](https://natario1.github.io/CameraView/docs/gestures.html)
- Real-time filters [[docs]](https://natario1.github.io/CameraView/docs/filters.html)
- Camera1 or Camera2 powered engine [[docs]](https://natario1.github.io/CameraView/docs/previews.html)
- Frame processing support [[docs]](https://natario1.github.io/CameraView/docs/frame-processing.html)
- Watermarks & animated overlays [[docs]](https://natario1.github.io/CameraView/docs/watermarks-and-overlays.html)
@ -101,6 +102,7 @@ motivation boost to push the library forward.
app:cameraAutoFocusResetDelay="@integer/autofocus_delay"
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"
app:cameraUseDeviceOrientation="true|false"
app:cameraFilter="@string/real_time_filter"
app:cameraExperimental="false|true">
<!-- Watermark! -->

@ -245,6 +245,9 @@ task mergedCoverageReport(type: JacocoReport) {
// TODO these below could be testable ALSO outside of the integration tests
classFilter.add('**/com/otaliastudios/cameraview/video/encoding/**.*')
}
// We don't test OpenGL filters.
classFilter.add('**/com/otaliastudios/cameraview/filters/**.*')
classDirectories = fileTree(dir: classDir, excludes: classFilter);
reports.html.enabled = true

@ -22,6 +22,9 @@ import com.otaliastudios.cameraview.controls.Facing;
import com.otaliastudios.cameraview.controls.Flash;
import com.otaliastudios.cameraview.controls.Preview;
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.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.gesture.Gesture;
@ -65,7 +68,7 @@ public class CameraViewTest extends BaseTest {
private CameraView cameraView;
private MockCameraEngine mockController;
private CameraPreview mockPreview;
private MockCameraPreview mockPreview;
private boolean hasPermissions;
@Before
@ -825,4 +828,30 @@ public class CameraViewTest extends BaseTest {
//endregion
// TODO: test permissions
//region Filter
@Test(expected = RuntimeException.class)
public void testSetFilter_notExperimental() {
cameraView.setExperimental(false);
cameraView.setFilter(Filters.AUTO_FIX.newInstance());
}
@Test
public void testSetFilter_notExperimental_noFilter() {
cameraView.setExperimental(false);
cameraView.setFilter(Filters.NONE.newInstance());
// no exception thrown
}
@Test
public void testSetFilter() {
cameraView.setExperimental(true);
Filter filter = Filters.AUTO_FIX.newInstance();
cameraView.setFilter(filter);
verify(mockPreview, times(1)).setFilter(filter);
assertEquals(filter, cameraView.getFilter());
//noinspection ResultOfMethodCallIgnored
verify(mockPreview, times(1)).getCurrentFilter();
}
}

@ -65,7 +65,6 @@ public abstract class CameraIntegrationTest extends BaseTest {
private final static CameraLogger LOG = CameraLogger.create(CameraIntegrationTest.class.getSimpleName());
private final static long DELAY = 8000;
private final static long VIDEO_DELAY = 16000;
@Rule
public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
@ -183,7 +182,20 @@ public abstract class CameraIntegrationTest extends BaseTest {
}));
// Wait for onVideoTaken and check.
VideoResult result = video.await(VIDEO_DELAY);
VideoResult result = video.await(DELAY);
// It seems that when running all the tests together, the device can go in some
// power saving mode which makes the CPU extremely slow. This is especially problematic
// with video snapshots where we do lots of processing. The videoEnd callback can return
// long after the actual stop() call, so if we're still processing, let's wait more.
if (expectSuccess && camera.isTakingVideo()) {
while (camera.isTakingVideo()) {
video.listen();
result = video.await(DELAY);
}
}
// Now we should be OK.
if (expectSuccess) {
assertEquals("Should call onVideoRecordingEnd", 0, onVideoRecordingEnd.getCount());
assertNotNull("Should end video", result);

@ -0,0 +1,52 @@
package com.otaliastudios.cameraview.filter;
import android.content.res.TypedArray;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.otaliastudios.cameraview.BaseTest;
import com.otaliastudios.cameraview.R;
import org.junit.Test;
import org.junit.runner.RunWith;
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class FilterParserTest extends BaseTest {
@Test
public void testFallback() {
TypedArray array = mock(TypedArray.class);
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(false);
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(null);
FilterParser parser = new FilterParser(array);
assertNotNull(parser.getFilter());
assertTrue(parser.getFilter() instanceof NoFilter);
}
@Test
public void testConstructor() {
TypedArray array = mock(TypedArray.class);
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(true);
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(MyFilter.class.getName());
FilterParser parser = new FilterParser(array);
assertNotNull(parser.getFilter());
assertTrue(parser.getFilter() instanceof MyFilter);
}
public static class MyFilter extends BaseFilter {
@NonNull
@Override
public String getFragmentShader() {
return createDefaultFragmentShader();
}
}
}

@ -23,16 +23,16 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public abstract class CameraPreviewTest extends BaseTest {
public abstract class CameraPreviewTest<T extends CameraPreview> extends BaseTest {
private final static long DELAY = 4000;
protected abstract CameraPreview createPreview(Context context, ViewGroup parent);
protected abstract T createPreview(Context context, ViewGroup parent);
@Rule
public ActivityTestRule<TestActivity> rule = new ActivityTestRule<>(TestActivity.class);
protected CameraPreview preview;
protected T preview;
@SuppressWarnings("WeakerAccess")
protected Size surfaceSize;
private CameraPreview.SurfaceCallback callback;

@ -7,24 +7,37 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.Filters;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class GlCameraPreviewTest extends CameraPreviewTest {
public class GlCameraPreviewTest extends CameraPreviewTest<GlCameraPreview> {
@Override
protected CameraPreview createPreview(Context context, ViewGroup parent) {
protected GlCameraPreview createPreview(Context context, ViewGroup parent) {
return new GlCameraPreview(context, parent);
}
@Override
protected float getCropScaleY() {
return 1F / ((GlCameraPreview) preview).mCropScaleY;
return 1F / preview.mCropScaleY;
}
@Override
protected float getCropScaleX() {
return 1F / ((GlCameraPreview) preview).mCropScaleX;
return 1F / preview.mCropScaleX;
}
@Test
public void testSetFilter() {
Filter filter = Filters.BLACK_AND_WHITE.newInstance();
preview.setFilter(filter);
assertEquals(filter, preview.getCurrentFilter());
}
}

@ -7,15 +7,17 @@ import androidx.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.preview.CameraPreview;
public class MockCameraPreview extends CameraPreview<View, Void> {
public class MockCameraPreview extends FilterCameraPreview<View, Void> {
public MockCameraPreview(Context context, ViewGroup parent) {
super(context, parent);
}
private View rootView;
private Filter filter;
@Override
public boolean supportsCropping() {
@ -47,4 +49,15 @@ public class MockCameraPreview extends CameraPreview<View, Void> {
public View getRootView() {
return rootView;
}
@Override
public void setFilter(@NonNull Filter filter) {
this.filter = filter;
}
@NonNull
@Override
public Filter getCurrentFilter() {
return filter;
}
}

@ -11,10 +11,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SurfaceCameraPreviewTest extends CameraPreviewTest {
public class SurfaceCameraPreviewTest extends CameraPreviewTest<SurfaceCameraPreview> {
@Override
protected CameraPreview createPreview(Context context, ViewGroup parent) {
protected SurfaceCameraPreview createPreview(Context context, ViewGroup parent) {
return new SurfaceCameraPreview(context, parent);
}

@ -11,10 +11,10 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TextureCameraPreviewTest extends CameraPreviewTest {
public class TextureCameraPreviewTest extends CameraPreviewTest<TextureCameraPreview> {
@Override
protected CameraPreview createPreview(Context context, ViewGroup parent) {
protected TextureCameraPreview createPreview(Context context, ViewGroup parent) {
return new TextureCameraPreview(context, parent);
}

@ -47,8 +47,10 @@ import com.otaliastudios.cameraview.engine.Camera1Engine;
import com.otaliastudios.cameraview.engine.Camera2Engine;
import com.otaliastudios.cameraview.engine.CameraEngine;
import com.otaliastudios.cameraview.engine.offset.Reference;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filters.Filters;
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.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
import com.otaliastudios.cameraview.gesture.Gesture;
@ -68,6 +70,7 @@ import com.otaliastudios.cameraview.markers.MarkerLayout;
import com.otaliastudios.cameraview.markers.MarkerParser;
import com.otaliastudios.cameraview.overlay.OverlayLayout;
import com.otaliastudios.cameraview.preview.CameraPreview;
import com.otaliastudios.cameraview.preview.FilterCameraPreview;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.SurfaceCameraPreview;
import com.otaliastudios.cameraview.preview.TextureCameraPreview;
@ -109,6 +112,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
private HashMap<Gesture, GestureAction> mGestureMap = new HashMap<>(4);
private Preview mPreview;
private Engine mEngine;
private Filter mPendingFilter;
// Components
@VisibleForTesting CameraCallbacks mCameraCallbacks;
@ -177,6 +181,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
SizeSelectorParser sizeSelectors = new SizeSelectorParser(a);
GestureParser gestures = new GestureParser(a);
MarkerParser markers = new MarkerParser(a);
FilterParser filters = new FilterParser(a);
a.recycle();
@ -234,6 +239,9 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// Apply markers
setAutoFocusMarker(markers.getAutoFocusMarker());
// Apply filters
setFilter(filters.getFilter());
if (!isInEditMode()) {
mOrientationHelper = new OrientationHelper(context, mCameraCallbacks);
}
@ -261,6 +269,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
mCameraPreview = instantiatePreview(mPreview, getContext(), this);
LOG.w("doInstantiateEngine:", "instantiated. preview:", mCameraPreview.getClass().getSimpleName());
mCameraEngine.setPreview(mCameraPreview);
if (mPendingFilter != null) {
setFilter(mPendingFilter);
mPendingFilter = null;
}
}
@ -2132,18 +2144,58 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
//endregion
//region Effects
//region Filters
public void setFilter(@NonNull Filters filter) {
setFilter(filter.newInstance());
/**
* Applies a real-time filter to the camera preview, if it supports it.
* The only preview type that does so is currently {@link Preview#GL_SURFACE}.
*
* The filter will be applied to any picture snapshot taken with
* {@link #takePictureSnapshot()} and any video snapshot taken with
* {@link #takeVideoSnapshot(File)}.
*
* Use {@link NoFilter} to clear the existing filter,
* and take a look at the {@link Filters} class for commonly used filters.
*
* This method will throw an exception if the current preview does not support real-time filters.
* Make sure you use {@link Preview#GL_SURFACE} (the default).
*
* @see Filters
* @param filter a new filter
*/
public void setFilter(@NonNull Filter filter) {
if (mCameraPreview == null) {
mPendingFilter = filter;
} else if (!(filter instanceof NoFilter) && !mExperimental) {
throw new RuntimeException("Filters are an experimental features and need the experimental flag set.");
} else if (mCameraPreview instanceof FilterCameraPreview) {
((FilterCameraPreview) mCameraPreview).setFilter(filter);
} else {
throw new RuntimeException("Filters are only supported by the GL_SURFACE preview. Current:" + mPreview);
}
}
public void setFilter(@NonNull Filter filter) {
if (mCameraPreview instanceof GlCameraPreview) {
((GlCameraPreview) mCameraPreview).setShaderEffect(filter);
/**
* Returns the current real-time filter applied to the camera preview.
*
* This method will throw an exception if the current preview does not support real-time filters.
* Make sure you use {@link Preview#GL_SURFACE} (the default).
*
* @see #setFilter(Filter)
* @return the current filter
*/
@NonNull
public Filter getFilter() {
if (!mExperimental) {
throw new RuntimeException("Filters are an experimental features and need the experimental flag set.");
} else if (mCameraPreview == null) {
return mPendingFilter;
} else if (mCameraPreview instanceof FilterCameraPreview) {
return ((FilterCameraPreview) mCameraPreview).getCurrentFilter();
} else {
LOG.w("setFilter", "setFilter is supported only for GLSurfaceView");
throw new RuntimeException("Filters are only supported by the GL_SURFACE preview. Current:" + mPreview);
}
}
//endregion

@ -29,7 +29,8 @@ public enum Preview implements Control {
/**
* Preview engine based on {@link android.opengl.GLSurfaceView}.
* This is the best engine available. Supports video snapshots,
* and picture snapshots while taking videos.
* supports picture snapshots while taking videos, supports
* watermarks and overlays, supports real-time filters.
*/
GL_SURFACE(2);

@ -0,0 +1,226 @@
package com.otaliastudios.cameraview.filter;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.GlUtils;
import com.otaliastudios.cameraview.size.Size;
import java.nio.FloatBuffer;
/**
* A base implementation of {@link Filter} that just leaves the fragment shader to subclasses.
* See {@link NoFilter} for a non-abstract implementation.
*
* This class offers a default vertex shader implementation which in most cases is not required
* to be changed. Most effects can be rendered by simply changing the fragment shader, thus
* by overriding {@link #getFragmentShader()}.
*
* All {@link BaseFilter}s should have a no-op public constructor.
* This class will try to automatically implement {@link #copy()} thanks to this.
* If your filter implements public parameters, please implement {@link OneParameterFilter}
* and {@link TwoParameterFilter} to handle them and have them passed automatically to copies.
*
* NOTE - This class expects variable to have a certain name:
* - {@link #vertexPositionName}
* - {@link #vertexTransformMatrixName}
* - {@link #vertexModelViewProjectionMatrixName}
* - {@link #vertexTextureCoordinateName}
* - {@link #fragmentTextureCoordinateName}
* You can either change these variables, for example in your constructor, or change your
* vertex and fragment shader code to use them.
*
* NOTE - the {@link android.graphics.SurfaceTexture} restrictions apply:
* We only support the {@link android.opengl.GLES11Ext#GL_TEXTURE_EXTERNAL_OES} texture target
* and it must be specified in the fragment shader as a samplerExternalOES texture.
* You also have to explicitly require the extension: see {@link #createDefaultFragmentShader(String)}.
*
*/
public abstract class BaseFilter implements Filter {
private final static String TAG = BaseFilter.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
private final static String DEFAULT_VERTEX_POSITION_NAME = "aPosition";
private final static String DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME = "aTextureCoord";
private final static String DEFAULT_VERTEX_MVP_MATRIX_NAME = "uMVPMatrix";
private final static String DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME = "uTexMatrix";
private final static String DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME = "vTextureCoord";
@NonNull
private static String createDefaultVertexShader(@NonNull String vertexPositionName,
@NonNull String vertexTextureCoordinateName,
@NonNull String vertexModelViewProjectionMatrixName,
@NonNull String vertexTransformMatrixName,
@NonNull String fragmentTextureCoordinateName) {
return "uniform mat4 "+vertexModelViewProjectionMatrixName+";\n" +
"uniform mat4 "+vertexTransformMatrixName+";\n" +
"attribute vec4 "+vertexPositionName+";\n" +
"attribute vec4 "+vertexTextureCoordinateName+";\n" +
"varying vec2 "+fragmentTextureCoordinateName+";\n" +
"void main() {\n" +
" gl_Position = "+vertexModelViewProjectionMatrixName+" * "+vertexPositionName+";\n" +
" vTextureCoord = ("+vertexTransformMatrixName+" * "+vertexTextureCoordinateName+").xy;\n" +
"}\n";
}
@NonNull
private static String createDefaultFragmentShader(@NonNull String fragmentTextureCoordinateName) {
return "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 "+fragmentTextureCoordinateName+";\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(sTexture, "+fragmentTextureCoordinateName+");\n"
+ "}\n";
}
// When the model/view/projection matrix is identity, this will exactly cover the viewport.
private FloatBuffer vertexPosition = GlUtils.floatBuffer(new float[]{
-1.0f, -1.0f, // 0 bottom left
1.0f, -1.0f, // 1 bottom right
-1.0f, 1.0f, // 2 top left
1.0f, 1.0f, // 3 top right
});
private FloatBuffer textureCoordinates = GlUtils.floatBuffer(new float[]{
0.0f, 0.0f, // 0 bottom left
1.0f, 0.0f, // 1 bottom right
0.0f, 1.0f, // 2 top left
1.0f, 1.0f // 3 top right
});
private int vertexModelViewProjectionMatrixLocation = -1;
private int vertexTranformMatrixLocation = -1;
private int vertexPositionLocation = -1;
private int vertexTextureCoordinateLocation = -1;
private Size outputSize;
@SuppressWarnings("WeakerAccess")
protected String vertexPositionName = DEFAULT_VERTEX_POSITION_NAME;
@SuppressWarnings("WeakerAccess")
protected String vertexTextureCoordinateName = DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME;
@SuppressWarnings("WeakerAccess")
protected String vertexModelViewProjectionMatrixName = DEFAULT_VERTEX_MVP_MATRIX_NAME;
@SuppressWarnings("WeakerAccess")
protected String vertexTransformMatrixName = DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME;
@SuppressWarnings({"unused", "WeakerAccess"})
protected String fragmentTextureCoordinateName = DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME;
@SuppressWarnings("WeakerAccess")
@NonNull
protected String createDefaultVertexShader() {
return createDefaultVertexShader(vertexPositionName,
vertexTextureCoordinateName,
vertexModelViewProjectionMatrixName,
vertexTransformMatrixName,
fragmentTextureCoordinateName);
}
@SuppressWarnings("WeakerAccess")
@NonNull
protected String createDefaultFragmentShader() {
return createDefaultFragmentShader(fragmentTextureCoordinateName);
}
@Override
public void onCreate(int programHandle) {
vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName);
GlUtils.checkLocation(vertexPositionLocation, vertexPositionName);
vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName);
GlUtils.checkLocation(vertexTextureCoordinateLocation, vertexTextureCoordinateName);
vertexModelViewProjectionMatrixLocation = GLES20.glGetUniformLocation(programHandle, vertexModelViewProjectionMatrixName);
GlUtils.checkLocation(vertexModelViewProjectionMatrixLocation, vertexModelViewProjectionMatrixName);
vertexTranformMatrixLocation = GLES20.glGetUniformLocation(programHandle, vertexTransformMatrixName);
GlUtils.checkLocation(vertexTranformMatrixLocation, vertexTransformMatrixName);
}
@Override
public void onDestroy() {
vertexPositionLocation = -1;
vertexTextureCoordinateLocation = -1;
vertexModelViewProjectionMatrixLocation = -1;
vertexTranformMatrixLocation = -1;
}
@NonNull
@Override
public String getVertexShader() {
return createDefaultVertexShader();
}
@Override
public void setSize(int width, int height) {
outputSize = new Size(width, height);
}
@Override
public void draw(float[] transformMatrix) {
onPreDraw(transformMatrix);
onDraw();
onPostDraw();
}
protected void onPreDraw(float[] transformMatrix) {
// Copy the model / view / projection matrix over.
GLES20.glUniformMatrix4fv(vertexModelViewProjectionMatrixLocation, 1, false, GlUtils.IDENTITY_MATRIX, 0);
GlUtils.checkError("glUniformMatrix4fv");
// Copy the texture transformation matrix over.
GLES20.glUniformMatrix4fv(vertexTranformMatrixLocation, 1, false, transformMatrix, 0);
GlUtils.checkError("glUniformMatrix4fv");
// Enable the "aPosition" vertex attribute.
// Connect vertexBuffer to "aPosition".
GLES20.glEnableVertexAttribArray(vertexPositionLocation);
GlUtils.checkError("glEnableVertexAttribArray: " + vertexPositionLocation);
GLES20.glVertexAttribPointer(vertexPositionLocation, 2, GLES20.GL_FLOAT, false, 8, vertexPosition);
GlUtils.checkError("glVertexAttribPointer");
// Enable the "aTextureCoord" vertex attribute.
// Connect texBuffer to "aTextureCoord".
GLES20.glEnableVertexAttribArray(vertexTextureCoordinateLocation);
GlUtils.checkError("glEnableVertexAttribArray");
GLES20.glVertexAttribPointer(vertexTextureCoordinateLocation, 2, GLES20.GL_FLOAT, false, 8, textureCoordinates);
GlUtils.checkError("glVertexAttribPointer");
}
@SuppressWarnings("WeakerAccess")
protected void onDraw() {
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GlUtils.checkError("glDrawArrays");
}
@SuppressWarnings("WeakerAccess")
protected void onPostDraw() {
GLES20.glDisableVertexAttribArray(vertexPositionLocation);
GLES20.glDisableVertexAttribArray(vertexTextureCoordinateLocation);
}
@NonNull
@Override
public final BaseFilter copy() {
BaseFilter copy = onCopy();
copy.setSize(outputSize.getWidth(), outputSize.getHeight());
if (this instanceof OneParameterFilter) {
((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1());
}
if (this instanceof TwoParameterFilter) {
((TwoParameterFilter) copy).setParameter2(((TwoParameterFilter) this).getParameter2());
}
return copy;
}
@SuppressWarnings("WeakerAccess")
protected BaseFilter onCopy() {
try {
return getClass().newInstance();
} catch (IllegalAccessException e) {
throw new RuntimeException("Filters should have a public no-op constructor.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Filters should have a public no-op constructor.", e);
}
}
}

@ -0,0 +1,91 @@
package com.otaliastudios.cameraview.filter;
import com.otaliastudios.cameraview.CameraView;
import androidx.annotation.NonNull;
import java.io.File;
/**
* A Filter is a real time filter that operates onto the camera preview, plus any
* snapshot media taken with {@link CameraView#takePictureSnapshot()} and
* {@link CameraView#takeVideoSnapshot(File)}.
*
* You can apply filters to the camera engine using {@link CameraView#setFilter(Filter)}.
* The default filter is called {@link NoFilter} and can be used to restore the normal preview.
* A lof of other filters are collected in the {@link Filters} class.
*
* Advanced users can create custom filters using GLES.
* It is recommended to extend {@link BaseFilter} instead of this class.
*
* All {@link Filter}s should have a no-op public constructor.
* This ensures that you can pass the filter class to XML attribute {@code app:cameraFilter},
* and also helps {@link BaseFilter} automatically make a copy of the filter.
*
* Parameterized filters can implement {@link OneParameterFilter} and {@link TwoParameterFilter}
* to receive parameters in the 0F-1F range. This helps in making filter copies and also let us
* map the filter parameter to gestures.
*/
public interface Filter {
/**
* Returns a String containing the vertex shader.
* Together with {@link #getFragmentShader()}, this will be used to
* create the OpenGL program.
*
* @return vertex shader
*/
@NonNull
String getVertexShader();
/**
* Returns a String containing the fragment shader.
* Together with {@link #getVertexShader()}, this will be used to
* create the OpenGL program.
*
* @return fragment shader
*/
@NonNull
String getFragmentShader();
/**
* The filter program was just created. We pass in a handle to the OpenGL
* program that was created, so you can fetch pointers.
*
* @param programHandle handle
*/
void onCreate(int programHandle);
/**
* The filter program is about to be destroyed.
*
*/
void onDestroy();
/**
* Called to render the actual texture. The given transformation matrix
* should be applied.
*
* @param transformMatrix matrix
*/
void draw(float[] transformMatrix);
/**
* Called anytime the output size changes.
*
* @param width width
* @param height height
*/
void setSize(int width, int height);
/**
* Clones this filter creating a new instance of it.
* If it has any important parameters, these should be passed
* to the new instance.
*
* @return a clone
*/
@NonNull
Filter copy();
}

@ -0,0 +1,32 @@
package com.otaliastudios.cameraview.filter;
import android.content.res.TypedArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.otaliastudios.cameraview.R;
/**
* Parses filters from XML attributes.
*/
public class FilterParser {
private Filter filter = null;
public FilterParser(@NonNull TypedArray array) {
String filterName = array.getString(R.styleable.CameraView_cameraFilter);
try {
//noinspection ConstantConditions
Class<?> filterClass = Class.forName(filterName);
filter = (Filter) filterClass.newInstance();
} catch (Exception ignore) {
filter = new NoFilter();
}
}
@NonNull
public Filter getFilter() {
return filter;
}
}

@ -0,0 +1,121 @@
package com.otaliastudios.cameraview.filter;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filters.AutoFixFilter;
import com.otaliastudios.cameraview.filters.BlackAndWhiteFilter;
import com.otaliastudios.cameraview.filters.BrightnessFilter;
import com.otaliastudios.cameraview.filters.ContrastFilter;
import com.otaliastudios.cameraview.filters.CrossProcessFilter;
import com.otaliastudios.cameraview.filters.DocumentaryFilter;
import com.otaliastudios.cameraview.filters.DuotoneFilter;
import com.otaliastudios.cameraview.filters.FillLightFilter;
import com.otaliastudios.cameraview.filters.GammaFilter;
import com.otaliastudios.cameraview.filters.GrainFilter;
import com.otaliastudios.cameraview.filters.GrayscaleFilter;
import com.otaliastudios.cameraview.filters.HueFilter;
import com.otaliastudios.cameraview.filters.InvertColorsFilter;
import com.otaliastudios.cameraview.filters.LomoishFilter;
import com.otaliastudios.cameraview.filters.PosterizeFilter;
import com.otaliastudios.cameraview.filters.SaturationFilter;
import com.otaliastudios.cameraview.filters.SepiaFilter;
import com.otaliastudios.cameraview.filters.SharpnessFilter;
import com.otaliastudios.cameraview.filters.TemperatureFilter;
import com.otaliastudios.cameraview.filters.TintFilter;
import com.otaliastudios.cameraview.filters.VignetteFilter;
/**
* Contains commonly used {@link Filter}s.
*
* You can use {@link #newInstance()} to create a new instance and
* pass it to {@link com.otaliastudios.cameraview.CameraView#setFilter(Filter)}.
*/
public enum Filters {
/** @see NoFilter */
NONE(NoFilter.class),
/** @see AutoFixFilter */
AUTO_FIX(AutoFixFilter.class),
/** @see BlackAndWhiteFilter */
BLACK_AND_WHITE(BlackAndWhiteFilter.class),
/** @see BrightnessFilter */
BRIGHTNESS(BrightnessFilter.class),
/** @see ContrastFilter */
CONTRAST(ContrastFilter.class),
/** @see CrossProcessFilter */
CROSS_PROCESS(CrossProcessFilter.class),
/** @see DocumentaryFilter */
DOCUMENTARY(DocumentaryFilter.class),
/** @see DuotoneFilter */
DUOTONE(DuotoneFilter.class),
/** @see FillLightFilter */
FILL_LIGHT(FillLightFilter.class),
/** @see GammaFilter */
GAMMA(GammaFilter.class),
/** @see GrainFilter */
GRAIN(GrainFilter.class),
/** @see GrayscaleFilter */
GRAYSCALE(GrayscaleFilter.class),
/** @see HueFilter */
HUE(HueFilter.class),
/** @see InvertColorsFilter */
INVERT_COLORS(InvertColorsFilter.class),
/** @see LomoishFilter */
LOMOISH(LomoishFilter.class),
/** @see PosterizeFilter */
POSTERIZE(PosterizeFilter.class),
/** @see SaturationFilter */
SATURATION(SaturationFilter.class),
/** @see SepiaFilter */
SEPIA(SepiaFilter.class),
/** @see SharpnessFilter */
SHARPNESS(SharpnessFilter.class),
/** @see TemperatureFilter */
TEMPERATURE(TemperatureFilter.class),
/** @see TintFilter */
TINT(TintFilter.class),
/** @see VignetteFilter */
VIGNETTE(VignetteFilter.class);
private Class<? extends Filter> filterClass;
Filters(@NonNull Class<? extends Filter> filterClass) {
this.filterClass = filterClass;
}
/**
* Returns a new instance of the given filter.
* @return a new instance
*/
@NonNull
public Filter newInstance() {
try {
return filterClass.newInstance();
} catch (IllegalAccessException e) {
return new NoFilter();
} catch (InstantiationException e) {
return new NoFilter();
}
}
}

@ -0,0 +1,14 @@
package com.otaliastudios.cameraview.filter;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
public class NoFilter extends BaseFilter {
@NonNull
@Override
public String getFragmentShader() {
return createDefaultFragmentShader();
}
}

@ -0,0 +1,30 @@
package com.otaliastudios.cameraview.filter;
/**
* A special {@link Filter} that accepts a float parameter.
*
* The parameters will always be between 0F and 1F, so subclasses should
* map this range to their internal range if needed.
*
* A standardized range is useful for different applications. For example:
* - Filter parameters can be easily mapped to gestures since the range is fixed
* - {@link BaseFilter} can use this setters and getters to make a filter copy
*/
public interface OneParameterFilter extends Filter {
/**
* Sets the parameter.
* The value should always be between 0 and 1.
*
* @param value parameter
*/
void setParameter1(float value);
/**
* Returns the parameter.
* The returned value should always be between 0 and 1.
*
* @return parameter
*/
float getParameter1();
}

@ -0,0 +1,31 @@
package com.otaliastudios.cameraview.filter;
/**
* A special {@link Filter} that accepts two floats parameters.
* This is done by extending {@link OneParameterFilter}.
*
* The parameters will always be between 0F and 1F, so subclasses should
* map this range to their internal range if needed.
*
* A standardized range is useful for different applications. For example:
* - Filter parameters can be easily mapped to gestures since the range is fixed
* - {@link BaseFilter} can use this setters and getters to make a filter copy
*/
public interface TwoParameterFilter extends OneParameterFilter {
/**
* Sets the second parameter.
* The value should always be between 0 and 1.
*
* @param value parameter
*/
void setParameter2(float value);
/**
* Returns the second parameter.
* The returned value should always be between 0 and 1.
*
* @return parameter
*/
float getParameter2();
}

@ -1,96 +1,124 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Attempts to auto-fix the preview based on histogram equalization.
* Attempts to auto-fix the frames based on histogram equalization.
*/
public class AutoFixFilter extends Filter {
public class AutoFixFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES tex_sampler_0;\n"
+ "uniform samplerExternalOES tex_sampler_1;\n"
+ "uniform samplerExternalOES tex_sampler_2;\n"
+ "uniform float scale;\n"
+ "float shift_scale;\n"
+ "float hist_offset;\n"
+ "float hist_scale;\n"
+ "float density_offset;\n"
+ "float density_scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " shift_scale = " + (1.0f / 256f) + ";\n"
+ " hist_offset = " + (0.5f / 766f) + ";\n"
+ " hist_scale = " + (765f / 766f) + ";\n"
+ " density_offset = " + (0.5f / 1024f) + ";\n"
+ " density_scale = " + (1023f / 1024f) + ";\n"
+ " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n"
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n"
+ " float energy = dot(color.rgb, weights);\n"
+ " float mask_value = energy - 0.5;\n"
+ " float alpha;\n"
+ " if (mask_value > 0.0) {\n"
+ " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n"
+ " } else { \n"
+ " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n"
+ " }\n"
+ " float index = energy * hist_scale + hist_offset;\n"
+ " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n"
+ " float value = temp.g + temp.r * shift_scale;\n"
+ " index = value * density_scale + density_offset;\n"
+ " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n"
+ " value = temp.g + temp.r * shift_scale;\n"
+ " float dst_energy = energy * alpha + value * (1.0 - alpha);\n"
+ " float max_energy = energy / max(color.r, max(color.g, color.b));\n"
+ " if (dst_energy > max_energy) {\n"
+ " dst_energy = max_energy;\n"
+ " }\n"
+ " if (energy == 0.0) {\n"
+ " gl_FragColor = color;\n"
+ " } else {\n"
+ " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n"
+ " }\n"
+ "}\n";
private float scale = 1.0f;
private int scaleLocation = -1;
public AutoFixFilter() { }
/**
* Initialize Effect
* A parameter between 0 and 1. Zero means no adjustment, while 1 indicates
* the maximum amount of adjustment.
*
* @param scale scale
*/
public AutoFixFilter() {
public void setScale(float scale) {
if (scale < 0.0f) scale = 0.0f;
if (scale > 1.0f) scale = 1.0f;
this.scale = scale;
}
/**
* Returns the current scale.
*
* @see #setScale(float)
* @return current scale
*/
public float getScale() {
return scale;
}
/**
* @param scale Float, between 0 and 1. Zero means no adjustment, while 1
* indicates the maximum amount of adjustment.
*/
public void setScale(float scale) {
if (scale < 0.0f)
scale = 0.0f;
else if (scale > 1.0f)
scale = 1.0f;
@Override
public void setParameter1(float value) {
setScale(value);
}
this.scale = scale;
@Override
public float getParameter1() {
return getScale();
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES tex_sampler_0;\n"
+ "uniform samplerExternalOES tex_sampler_1;\n"
+ "uniform samplerExternalOES tex_sampler_2;\n"
+ " float scale;\n" + " float shift_scale;\n"
+ " float hist_offset;\n" + " float hist_scale;\n"
+ " float density_offset;\n" + " float density_scale;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ " shift_scale = "
+ (1.0f / 256f)
+ ";\n"
+ " hist_offset = "
+ (0.5f / 766f)
+ ";\n"
+ " hist_scale = "
+ (765f / 766f)
+ ";\n"
+ " density_offset = "
+ (0.5f / 1024f)
+ ";\n"
+ " density_scale = "
+ (1023f / 1024f)
+ ";\n"
+ " scale = "
+ scale
+ ";\n"
+ " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n"
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n"
+ " float energy = dot(color.rgb, weights);\n"
+ " float mask_value = energy - 0.5;\n"
+ " float alpha;\n"
+ " if (mask_value > 0.0) {\n"
+ " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n"
+ " } else { \n"
+ " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n"
+ " }\n"
+ " float index = energy * hist_scale + hist_offset;\n"
+ " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n"
+ " float value = temp.g + temp.r * shift_scale;\n"
+ " index = value * density_scale + density_offset;\n"
+ " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n"
+ " value = temp.g + temp.r * shift_scale;\n"
+ " float dst_energy = energy * alpha + value * (1.0 - alpha);\n"
+ " float max_energy = energy / max(color.r, max(color.g, color.b));\n"
+ " if (dst_energy > max_energy) {\n"
+ " dst_energy = max_energy;\n"
+ " }\n"
+ " if (energy == 0.0) {\n"
+ " gl_FragColor = color;\n"
+ " } else {\n"
+ " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n"
+ " }\n" + "}\n";
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale");
}
return shader;
@Override
public void onDestroy() {
super.onDestroy();
scaleLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f");
}
}

@ -2,34 +2,29 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Converts the preview into black and white colors
* Converts the frames into black and white colors.
*/
public class BlackAndWhiteFilter extends Filter {
public class BlackAndWhiteFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float colorR = (color.r + color.g + color.b) / 3.0;\n"
+ " float colorG = (color.r + color.g + color.b) / 3.0;\n"
+ " float colorB = (color.r + color.g + color.b) / 3.0;\n"
+ " gl_FragColor = vec4(colorR, colorG, colorB, color.a);\n"
+ "}\n";
/**
* Initialize effect
*/
public BlackAndWhiteFilter() {
}
public BlackAndWhiteFilter() { }
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float colorR = (color.r + color.g + color.b) / 3.0;\n"
+ " float colorG = (color.r + color.g + color.b) / 3.0;\n"
+ " float colorB = (color.r + color.g + color.b) / 3.0;\n"
+ " gl_FragColor = vec4(colorR, colorG, colorB, color.a);\n"
+ "}\n";
return shader;
return FRAGMENT_SHADER;
}
}

@ -1,56 +1,94 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Adjusts the brightness of the preview.
* Adjusts the brightness of the frames.
*/
public class BrightnessFilter extends Filter {
private float brightnessValue = 2.0f;
public class BrightnessFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float brightness;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " gl_FragColor = brightness * color;\n"
+ "}\n";
private float brightness = 2.0f; // 1.0F...2.0F
private int brightnessLocation = -1;
public BrightnessFilter() { }
/**
* Initialize Effect
* Sets the brightness adjustment.
* 1.0: normal brightness.
* 2.0: high brightness.
*
* @param brightness brightness.
*/
public BrightnessFilter() {
@SuppressWarnings({"WeakerAccess", "unused"})
public void setBrightness(float brightness) {
if (brightness < 1.0f) brightness = 1.0f;
if (brightness > 2.0f) brightness = 2.0f;
this.brightness = brightness;
}
/**
* setBrightnessValue
* Returns the current brightness.
*
* @param brightnessvalue Range should be between 0.0- 1.0 with 0.0 being normal.
* @see #setBrightness(float)
* @return brightness
*/
public void setBrightnessValue(float brightnessvalue) {
if (brightnessvalue < 0.0f)
brightnessvalue = 0.0f;
else if (brightnessvalue > 1.0f)
brightnessvalue = 1.0f;
@SuppressWarnings({"unused", "WeakerAccess"})
public float getBrightness() {
return brightness;
}
//since the shader excepts a range of 1.0 - 2.0
// will add the 1.0 to every value
this.brightnessValue = 1.0f + brightnessvalue;
@Override
public void setParameter1(float value) {
// parameter is 0...1, brightness is 1...2.
setBrightness(value + 1);
}
public float getBrightnessValue() {
//since the shader excepts a range of 1.0 - 2.0
//to keep it between 0.0f - 1.0f range, will subtract the 1.0 to every value
return brightnessValue - 1.0f;
@Override
public float getParameter1() {
// parameter is 0...1, brightness is 1...2.
return getBrightness() - 1F;
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "float brightness ;\n" + "varying vec2 vTextureCoord;\n"
+ "void main() {\n" + " brightness =" + brightnessValue
+ ";\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " gl_FragColor = brightness * color;\n" + "}\n";
return shader;
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness");
GlUtils.checkLocation(brightnessLocation, "brightness");
}
@Override
public void onDestroy() {
super.onDestroy();
brightnessLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(brightnessLocation, brightness);
GlUtils.checkError("glUniform1f");
}
}

@ -1,55 +1,96 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Adjusts the contrast of the preview.
* Adjusts the contrast.
*/
public class ContrastFilter extends Filter {
private float contrast = 2.0f;
public class ContrastFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float contrast;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " color -= 0.5;\n"
+ " color *= contrast;\n"
+ " color += 0.5;\n"
+ " gl_FragColor = color;\n"
+ "}\n";
private float contrast = 2F;
private int contrastLocation = -1;
public ContrastFilter() { }
/**
* Initialize Effect
* Sets the current contrast adjustment.
* 1.0: no adjustment
* 2.0: increased contrast
*
* @param contrast contrast
*/
public ContrastFilter() {
@SuppressWarnings("WeakerAccess")
public void setContrast(float contrast) {
if (contrast < 1.0f) contrast = 1.0f;
if (contrast > 2.0f) contrast = 2.0f;
this.contrast = contrast;
}
/**
* setContrast
* Returns the current contrast.
*
* @param contrast Range should be between 0.0- 1.0 with 0.0 being normal.
* @see #setContrast(float)
* @return contrast
*/
public void setContrast(float contrast) {
if (contrast < 0.0f)
contrast = 0.0f;
else if (contrast > 1.0f)
contrast = 1.0f;
@SuppressWarnings({"unused", "WeakerAccess"})
public float getContrast() {
return contrast;
}
//since the shader excepts a range of 1.0 - 2.0
//will add the 1.0 to every value
this.contrast = contrast + 1.0f;
@Override
public void setParameter1(float value) {
// parameter is 0...1, contrast is 1...2.
setContrast(value + 1);
}
public float getContrast() {
//since the shader excepts a range of 1.0 - 2.0
//to keep it between 0.0f - 1.0f range, will subtract the 1.0 to every value
return contrast - 1.0f;
@Override
public float getParameter1() {
// parameter is 0...1, contrast is 1...2.
return getContrast() - 1F;
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " float contrast;\n" + "varying vec2 vTextureCoord;\n"
+ "void main() {\n" + " contrast =" + contrast + ";\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " color -= 0.5;\n" + " color *= contrast;\n"
+ " color += 0.5;\n" + " gl_FragColor = color;\n" + "}\n";
return shader;
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
contrastLocation = GLES20.glGetUniformLocation(programHandle, "contrast");
GlUtils.checkLocation(contrastLocation, "contrast");
}
@Override
public void onDestroy() {
super.onDestroy();
contrastLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(contrastLocation, contrast);
GlUtils.checkError("glUniform1f");
}
}

@ -2,43 +2,53 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Applies a cross process effect on preview, in which the red and green channels
* Applies a cross process effect, in which the red and green channels
* are enhanced while the blue channel is restricted.
*/
public class CrossProcessFilter extends Filter {
/**
* Initialize Effect
*/
public CrossProcessFilter() {
}
public class CrossProcessFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 ncolor = vec3(0.0, 0.0, 0.0);\n"
+ " float value;\n"
+ " if (color.r < 0.5) {\n"
+ " value = color.r;\n"
+ " } else {\n"
+ " value = 1.0 - color.r;\n"
+ " }\n"
+ " float red = 4.0 * value * value * value;\n"
+ " if (color.r < 0.5) {\n"
+ " ncolor.r = red;\n"
+ " } else {\n"
+ " ncolor.r = 1.0 - red;\n"
+ " }\n"
+ " if (color.g < 0.5) {\n"
+ " value = color.g;\n"
+ " } else {\n"
+ " value = 1.0 - color.g;\n"
+ " }\n"
+ " float green = 2.0 * value * value;\n"
+ " if (color.g < 0.5) {\n"
+ " ncolor.g = green;\n"
+ " } else {\n"
+ " ncolor.g = 1.0 - green;\n"
+ " }\n"
+ " ncolor.b = color.b * 0.5 + 0.25;\n"
+ " gl_FragColor = vec4(ncolor.rgb, color.a);\n"
+ "}\n";
public CrossProcessFilter() { }
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 ncolor = vec3(0.0, 0.0, 0.0);\n" + " float value;\n"
+ " if (color.r < 0.5) {\n" + " value = color.r;\n"
+ " } else {\n" + " value = 1.0 - color.r;\n" + " }\n"
+ " float red = 4.0 * value * value * value;\n"
+ " if (color.r < 0.5) {\n" + " ncolor.r = red;\n"
+ " } else {\n" + " ncolor.r = 1.0 - red;\n" + " }\n"
+ " if (color.g < 0.5) {\n" + " value = color.g;\n"
+ " } else {\n" + " value = 1.0 - color.g;\n" + " }\n"
+ " float green = 2.0 * value * value;\n"
+ " if (color.g < 0.5) {\n" + " ncolor.g = green;\n"
+ " } else {\n" + " ncolor.g = 1.0 - green;\n" + " }\n"
+ " ncolor.b = color.b * 0.5 + 0.25;\n"
+ " gl_FragColor = vec4(ncolor.rgb, color.a);\n" + "}\n";
return shader;
return FRAGMENT_SHADER;
}
}

@ -1,27 +0,0 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
/**
* This class is to implement any custom effect.
*/
public class CustomFilter extends Filter {
/**
* Parameterized constructor with vertex and fragment shader as parameter
*
* @param vertexShader
* @param fragmentShader
*/
public CustomFilter(String vertexShader, String fragmentShader) {
this.mVertexShader = vertexShader;
this.mFragmentShader = fragmentShader;
}
@NonNull
@Override
public String getFragmentShader() {
return mFragmentShader;
}
}

@ -1,96 +1,115 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import java.util.Date;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.util.Random;
/**
* Applies black and white documentary style effect on preview.
* Applies black and white documentary style effect.
*/
public class DocumentaryFilter extends Filter {
private Random mRandom;
public class DocumentaryFilter extends BaseFilter {
private final static Random RANDOM = new Random();
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "vec2 seed;\n"
+ "float stepsize;\n"
+ "uniform float inv_max_dist;\n"
+ "uniform vec2 scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
+
// keep value of part1 in range: (2^-14 to 2^14).
" float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " return fract(part1 + part2 + part3);\n"
+ "}\n"
+ "void main() {\n"
+ " seed[0] = " + RANDOM.nextFloat() + ";\n"
+ " seed[1] = " + RANDOM.nextFloat() + ";\n"
+ " stepsize = " + 1.0f / 255.0f + ";\n"
// black white
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float dither = rand(vTextureCoord + seed);\n"
+ " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n"
+ " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n"
+ " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n"
// grayscale
+ " float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n"
+ " new_color = vec3(gray, gray, gray);\n"
// vignette
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n"
+ " gl_FragColor = vec4(new_color * lumen, color.a);\n"
+ "}\n";
private int mWidth = 1;
private int mHeight = 1;
private int mScaleLocation = -1;
private int mMaxDistLocation = -1;
public DocumentaryFilter() {
mRandom = new Random(new Date().getTime());
public DocumentaryFilter() { }
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
mWidth = width;
mHeight = height;
}
@NonNull
@Override
public String getFragmentShader() {
float scale[] = new float[2];
if (mPreviewingViewWidth > mPreviewingViewHeight) {
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(mScaleLocation, "scale");
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist");
}
@Override
public void onDestroy() {
super.onDestroy();
mScaleLocation = -1;
mMaxDistLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float[] scale = new float[2];
if (mWidth > mHeight) {
scale[0] = 1f;
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth;
scale[1] = ((float) mHeight) / mWidth;
} else {
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight;
scale[0] = ((float) mWidth) / mHeight;
scale[1] = 1f;
}
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1]
* scale[1])) * 0.5f;
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()};
String scaleString[] = new String[2];
String seedString[] = new String[2];
scaleString[0] = "scale[0] = " + scale[0] + ";\n";
scaleString[1] = "scale[1] = " + scale[1] + ";\n";
seedString[0] = "seed[0] = " + seed[0] + ";\n";
seedString[1] = "seed[1] = " + seed[1] + ";\n";
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n";
String stepsizeString = "stepsize = " + 1.0f / 255.0f + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " vec2 seed;\n"
+ " float stepsize;\n"
+ " float inv_max_dist;\n"
+ " vec2 scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
+
// keep value of part1 in range: (2^-14 to 2^14).
" float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " return fract(part1 + part2 + part3);\n"
+ "}\n"
+ "void main() {\n"
// Parameters that were created above
+ scaleString[0]
+ scaleString[1]
+ seedString[0]
+ seedString[1]
+ inv_max_distString
+ stepsizeString
// black white
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float dither = rand(vTextureCoord + seed);\n"
+ " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n"
+ " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n"
+ " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n"
+
// grayscale
" float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n"
+ " new_color = vec3(gray, gray, gray);\n"
+
// vignette
" vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n"
+ " gl_FragColor = vec4(new_color * lumen, color.a);\n"
+ "}\n";
return shader;
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv");
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
float invMaxDist = 1F / maxDist;
GLES20.glUniform1f(mMaxDistLocation, invMaxDist);
GlUtils.checkError("glUniform1f");
}
}

@ -1,85 +1,158 @@
package com.otaliastudios.cameraview.filters;
import android.graphics.Color;
import android.opengl.GLES20;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.TwoParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Representation of preview using only two color tones.
* Representation of input frames using only two color tones.
*/
public class DuotoneFilter extends Filter {
public class DuotoneFilter extends BaseFilter implements TwoParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform vec3 first;\n"
+ "uniform vec3 second;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float energy = (color.r + color.g + color.b) * 0.3333;\n"
+ " vec3 new_color = (1.0 - energy) * first + energy * second;\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n"
+ "}\n";
// Default values
private int mFirstColor = Color.MAGENTA;
private int mSecondColor = Color.YELLOW;
private int mFirstColorLocation = -1;
private int mSecondColorLocation = -1;
public DuotoneFilter() { }
/**
* Initialize effect
* Sets the two duotone ARGB colors.
* @param firstColor first
* @param secondColor second
*/
@SuppressWarnings({"unused"})
public void setColors(@ColorInt int firstColor, @ColorInt int secondColor) {
setFirstColor(firstColor);
setSecondColor(secondColor);
}
/**
* Sets the first of the duotone ARGB colors.
* Defaults to {@link Color#MAGENTA}.
*
* @param color first color
*/
public DuotoneFilter() {
@SuppressWarnings("WeakerAccess")
public void setFirstColor(@ColorInt int color) {
mFirstColor = color;
}
/**
* setDuoToneColors
* Sets the second of the duotone ARGB colors.
* Defaults to {@link Color#YELLOW}.
*
* @param firstColor Integer, representing an ARGB color with 8 bits per channel.
* May be created using Color class.
* @param secondColor Integer, representing an ARGB color with 8 bits per channel.
* May be created using Color class.
* @param color second color
*/
public void setDuoToneColors(int firstColor, int secondColor) {
this.mFirstColor = firstColor;
this.mSecondColor = secondColor;
@SuppressWarnings("WeakerAccess")
public void setSecondColor(@ColorInt int color) {
mSecondColor = color;
}
/**
* Returns the first color.
*
* @see #setFirstColor(int)
* @return first
*/
@SuppressWarnings({"unused", "WeakerAccess"})
@ColorInt
public int getFirstColor() {
return mFirstColor;
}
/**
* Returns the second color.
*
* @see #setSecondColor(int)
* @return second
*/
@SuppressWarnings({"unused", "WeakerAccess"})
@ColorInt
public int getSecondColor() {
return mSecondColor;
}
@Override
public void setParameter1(float value) {
// no easy way to transform 0...1 into a color.
setFirstColor((int) (value * Integer.MAX_VALUE));
}
@Override
public float getParameter1() {
return (float) getFirstColor() / Integer.MAX_VALUE;
}
@Override
public void setParameter2(float value) {
// no easy way to transform 0...1 into a color.
setSecondColor((int) (value * Integer.MAX_VALUE));
}
@Override
public float getParameter2() {
return (float) getSecondColor() / Integer.MAX_VALUE;
}
@NonNull
@Override
public String getFragmentShader() {
float first[] = {Color.red(mFirstColor) / 255f,
Color.green(mFirstColor) / 255f, Color.blue(mFirstColor) / 255f};
float second[] = {Color.red(mSecondColor) / 255f,
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
mFirstColorLocation = GLES20.glGetUniformLocation(programHandle, "first");
GlUtils.checkLocation(mFirstColorLocation, "first");
mSecondColorLocation = GLES20.glGetUniformLocation(programHandle, "second");
GlUtils.checkLocation(mSecondColorLocation, "second");
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float[] first = new float[]{
Color.red(mFirstColor) / 255f,
Color.green(mFirstColor) / 255f,
Color.blue(mFirstColor) / 255f
};
float[] second = new float[]{
Color.red(mSecondColor) / 255f,
Color.green(mSecondColor) / 255f,
Color.blue(mSecondColor) / 255f};
String firstColorString[] = new String[3];
String secondColorString[] = new String[3];
firstColorString[0] = "first[0] = " + first[0] + ";\n";
firstColorString[1] = "first[1] = " + first[1] + ";\n";
firstColorString[2] = "first[2] = " + first[2] + ";\n";
secondColorString[0] = "second[0] = " + second[0] + ";\n";
secondColorString[1] = "second[1] = " + second[1] + ";\n";
secondColorString[2] = "second[2] = " + second[2] + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " vec3 first;\n"
+ " vec3 second;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
// Parameters that were created above
+ firstColorString[0]
+ firstColorString[1]
+ firstColorString[2]
+ secondColorString[0]
+ secondColorString[1]
+ secondColorString[2]
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float energy = (color.r + color.g + color.b) * 0.3333;\n"
+ " vec3 new_color = (1.0 - energy) * first + energy * second;\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
return shader;
Color.blue(mSecondColor) / 255f
};
GLES20.glUniform3fv(mFirstColorLocation, 1, first, 0);
GlUtils.checkError("glUniform3fv");
GLES20.glUniform3fv(mSecondColorLocation, 1, second, 0);
GlUtils.checkError("glUniform3fv");
}
@Override
public void onDestroy() {
super.onDestroy();
mFirstColorLocation = -1;
mSecondColorLocation = -1;
}
}

@ -1,73 +1,111 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Applies back-light filling to the preview.
* Applies back-light filling to the frames.
*/
public class FillLightFilter extends Filter {
public class FillLightFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float mult;\n"
+ "uniform float igamma;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float lightmask = dot(color.rgb, color_weights);\n"
+ " float backmask = (1.0 - lightmask);\n"
+ " vec3 ones = vec3(1.0, 1.0, 1.0);\n"
+ " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n"
+ " diff = min(diff, 1.0);\n"
+ " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n"
+ " gl_FragColor = vec4(new_color, color.a);\n"
+ "}\n";
private float strength = 0.5f;
private int multiplierLocation = -1;
private int gammaLocation = -1;
/**
* Initialize Effect
*/
public FillLightFilter() {
}
public FillLightFilter() { }
/**
* setStrength
* Sets the current strength.
* 0.0: no change.
* 1.0: max strength.
*
* @param strength Float, between 0.0 and 1.0 where 0.0 means no change.
* @param strength strength
*/
@SuppressWarnings("WeakerAccess")
public void setStrength(float strength) {
if (strength < 0.0f)
strength = 0f;
else if (strength > 1.0f)
strength = 1f;
if (strength < 0.0f) strength = 0f;
if (strength > 1.0f) strength = 1f;
this.strength = strength;
}
/**
* Returns the current strength.
*
* @see #setStrength(float)
* @return strength
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public float getStrength() {
return strength;
}
@Override
public void setParameter1(float value) {
setStrength(value);
}
@Override
public float getParameter1() {
return getStrength();
}
@NonNull
@Override
public String getFragmentShader() {
float fade_gamma = 0.3f;
float amt = 1.0f - strength;
float mult = 1.0f / (amt * 0.7f + 0.3f);
float faded = fade_gamma + (1.0f - fade_gamma) * mult;
float igamma = 1.0f / faded;
String multString = "mult = " + mult + ";\n";
String igammaString = "igamma = " + igamma + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " float mult;\n"
+ " float igamma;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main()\n"
+ "{\n"
// Parameters that were created above
+ multString
+ igammaString
+ " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float lightmask = dot(color.rgb, color_weights);\n"
+ " float backmask = (1.0 - lightmask);\n"
+ " vec3 ones = vec3(1.0, 1.0, 1.0);\n"
+ " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n"
+ " diff = min(diff, 1.0);\n"
+ " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n"
+ " gl_FragColor = vec4(new_color, color.a);\n" + "}\n";
return shader;
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
multiplierLocation = GLES20.glGetUniformLocation(programHandle, "mult");
GlUtils.checkLocation(multiplierLocation, "mult");
gammaLocation = GLES20.glGetUniformLocation(programHandle, "igamma");
GlUtils.checkLocation(gammaLocation, "igamma");
}
@Override
public void onDestroy() {
super.onDestroy();
multiplierLocation = -1;
gammaLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float amount = 1.0f - strength;
float multiplier = 1.0f / (amount * 0.7f + 0.3f);
GLES20.glUniform1f(multiplierLocation, multiplier);
GlUtils.checkError("glUniform1f");
float fadeGamma = 0.3f;
float faded = fadeGamma + (1.0f - fadeGamma) * multiplier;
float gamma = 1.0f / faded;
GLES20.glUniform1f(gammaLocation, gamma);
GlUtils.checkError("glUniform1f");
}
}

@ -1,128 +0,0 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
/**
* A Base abstract class that every effect must extend so that there is a common getShader method.
* <p>
* This class has a default Vertex Shader implementation which in most cases not required to touch.
* In Effects like sepia, B/W any many, only pixel color changes which can be managed by only fragment shader.
* If there is some other effect which requires vertex shader also change, you can override it.
* <p>
* If your provide your own vertex and fragment shader,
* please set the {@link #mPositionVariableName}, {@link #mTextureCoordinateVariableName},
* {@link #mMVPMatrixVariableName}, {@link #mTextureMatrixVariableName}
* according to your shader code.
* <p>
* Please note that these shader applies on live preview as well as pictureSnapshot and videoSnapshot,
* we only support GLES11Ext.GL_TEXTURE_EXTERNAL_OES
* check EglViewport()
* <p>
* The default implementation of this class is NoEffect.
*/
public abstract class Filter {
/**
* Vertex shader code written in Shader Language (C) and stored as String.
* This wil be used by GL to apply any effect.
*/
@NonNull
String mVertexShader =
"uniform mat4 uMVPMatrix;\n" +
"uniform mat4 uTexMatrix;\n" +
"attribute vec4 aPosition;\n" +
"attribute vec4 aTextureCoord;\n" +
"varying vec2 vTextureCoord;\n" +
"void main() {\n" +
" gl_Position = uMVPMatrix * aPosition;\n" +
" vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" +
"}\n";
/**
* Fragment shader code written in Shader Language (C) and stored as String.
* This wil be used by GL to apply any effect.
*/
@NonNull
String mFragmentShader =
"#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n"
+ "}\n";
/**
* Width and height of previewing GlSurfaceview.
* This will be used by a few effects.
*/
int mPreviewingViewWidth = 0;
int mPreviewingViewHeight = 0;
public void setPreviewingViewSize(int width, int height) {
mPreviewingViewWidth = width;
mPreviewingViewHeight = height;
}
/**
* Local variable name which were used in the shader code.
* These will be used by openGL program to render these vertex and fragment shader
*/
private String mPositionVariableName = "aPosition";
private String mTextureCoordinateVariableName = "aTextureCoord";
private String mMVPMatrixVariableName = "uMVPMatrix";
private String mTextureMatrixVariableName = "uTexMatrix";
public String getPositionVariableName() {
return mPositionVariableName;
}
public void setPositionVariableName(String positionVariableName) {
this.mPositionVariableName = positionVariableName;
}
public String getTexttureCoordinateVariableName() {
return mTextureCoordinateVariableName;
}
public void setTexttureCoordinateVariableName(String texttureCoordinateVariableName) {
this.mTextureCoordinateVariableName = texttureCoordinateVariableName;
}
public String getMVPMatrixVariableName() {
return mMVPMatrixVariableName;
}
public void setMVPMatrixVariableName(String mvpMatrixVariableName) {
this.mMVPMatrixVariableName = mvpMatrixVariableName;
}
public String getTextureMatrixVariableName() {
return mTextureMatrixVariableName;
}
public void setTextureMatrixVariableName(String textureMatrixVariableName) {
this.mTextureMatrixVariableName = textureMatrixVariableName;
}
/**
* Get vertex Shader code
*
* @return complete shader code in C
*/
@NonNull
public String getVertexShader() {
return mVertexShader;
}
/**
* Get fragment Shader code
*
* @return complete shader code in C
*/
@NonNull
public abstract String getFragmentShader();
}

@ -1,124 +0,0 @@
package com.otaliastudios.cameraview.filters;
public enum Filters {
NO_FILTER,
AUTO_FIX_FILTER,
BLACK_AND_WHITE_FILTER,
BRIGHTNESS_FILTER,
CONTRAST_FILTER,
CROSS_PROCESS_FILTER,
DOCUMENTARY_FILTER,
DUO_TONE_COLOR_FILTER,
FILL_LIGHT_FILTER,
GAMMA_FILTER,
GRAIN_FILTER,
GREY_SCALE_FILTER,
HUE_FILTER,
INVERT_COLOR_FILTER,
LAMOISH_FILTER,
POSTERIZE_FILTER,
SATURATION_FILTER,
SEPIA_FILTER,
SHARPNESS_FILTER,
TEMPERATURE_FILTER,
TINT_FILTER,
VIGNETTE_FILTER;
public Filter newInstance() {
Filter shaderEffect;
switch (this) {
case AUTO_FIX_FILTER:
shaderEffect = new AutoFixFilter();
break;
case BLACK_AND_WHITE_FILTER:
shaderEffect = new BlackAndWhiteFilter();
break;
case BRIGHTNESS_FILTER:
shaderEffect = new BrightnessFilter();
break;
case CONTRAST_FILTER:
shaderEffect = new ContrastFilter();
break;
case CROSS_PROCESS_FILTER:
shaderEffect = new CrossProcessFilter();
break;
case DOCUMENTARY_FILTER:
shaderEffect = new DocumentaryFilter();
break;
case DUO_TONE_COLOR_FILTER:
shaderEffect = new DuotoneFilter();
break;
case FILL_LIGHT_FILTER:
shaderEffect = new FillLightFilter();
break;
case GAMMA_FILTER:
shaderEffect = new GammaFilter();
break;
case GRAIN_FILTER:
shaderEffect = new GrainFilter();
break;
case GREY_SCALE_FILTER:
shaderEffect = new GreyScaleFilter();
break;
case HUE_FILTER:
shaderEffect = new HueFilter();
break;
case INVERT_COLOR_FILTER:
shaderEffect = new InvertColorsFilter();
break;
case LAMOISH_FILTER:
shaderEffect = new LamoishFilter();
break;
case POSTERIZE_FILTER:
shaderEffect = new PosterizeFilter();
break;
case SATURATION_FILTER:
shaderEffect = new SaturationFilter();
break;
case SEPIA_FILTER:
shaderEffect = new SepiaFilter();
break;
case SHARPNESS_FILTER:
shaderEffect = new SharpnessFilter();
break;
case TEMPERATURE_FILTER:
shaderEffect = new TemperatureFilter();
break;
case TINT_FILTER:
shaderEffect = new TintFilter();
break;
case VIGNETTE_FILTER:
shaderEffect = new VignetteFilter();
break;
case NO_FILTER:
default:
shaderEffect = new NoFilter();
}
return shaderEffect;
}
}

@ -1,59 +1,90 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Apply Gamma Effect on preview being played
* Applies gamma correction to the frames.
*/
public class GammaFilter extends Filter {
private float gammaValue = 2.0f;
public class GammaFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float gamma;\n"
+ "void main() {\n"
+ " vec4 textureColor = texture2D(sTexture, vTextureCoord);\n"
+ " gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);\n"
+ "}\n";
private float gamma = 2.0f;
private int gammaLocation = -1;
public GammaFilter() { }
/**
* Initialize Effect
* Sets the new gamma value in the 0.0 - 2.0 range.
* The 1.0 value means no correction will be applied.
*
* @param gamma gamma value
*/
public GammaFilter() {
@SuppressWarnings("WeakerAccess")
public void setGamma(float gamma) {
if (gamma < 0.0f) gamma = 0.0f;
if (gamma > 2.0f) gamma = 2.0f;
this.gamma = gamma;
}
/**
* setGammaValue
* Returns the current gamma.
*
* @param gammaValue Range should be between 0.0 - 1.0 with 0.5 being normal.
* @see #setGamma(float)
* @return gamma
*/
public void setGammaValue(float gammaValue) {
if (gammaValue < 0.0f)
gammaValue = 0.0f;
else if (gammaValue > 1.0f)
gammaValue = 1.0f;
@SuppressWarnings("WeakerAccess")
public float getGamma() {
return gamma;
}
//since the shader excepts a range of 0.0 - 2.0
//will multiply the 2.0 to every value
this.gammaValue = gammaValue * 2.0f;
@Override
public void setParameter1(float value) {
setGamma(value * 2F);
}
public float getGammaValue() {
//since the shader excepts a range of 0.0 - 2.0
//to keep it between 0.0f - 1.0f range, will divide it with 2.0
return gammaValue / 2.0f;
@Override
public float getParameter1() {
return getGamma() / 2F;
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "float gamma=" + gammaValue + ";\n"
+ "void main() {\n"
+ "vec4 textureColor = texture2D(sTexture, vTextureCoord);\n"
+ "gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);\n"
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
gammaLocation = GLES20.glGetUniformLocation(programHandle, "gamma");
GlUtils.checkLocation(gammaLocation, "gamma");
}
+ "}\n";
@Override
public void onDestroy() {
super.onDestroy();
gammaLocation = -1;
}
return shader;
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(gammaLocation, gamma);
GlUtils.checkError("glUniform1f");
}
}

@ -1,98 +1,144 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import java.util.Date;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.util.Random;
/**
* Applies film grain effect to preview.
* Applies film grain effect to the frames.
*/
public class GrainFilter extends Filter {
public class GrainFilter extends BaseFilter implements OneParameterFilter {
private final static Random RANDOM = new Random();
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "vec2 seed;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES tex_sampler_0;\n"
+ "uniform samplerExternalOES tex_sampler_1;\n"
+ "uniform float scale;\n"
+ "uniform float stepX;\n"
+ "uniform float stepY;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
// keep value of part1 in range: (2^-14 to 2^14).
+ " float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " float sum = (part1 + part2 + part3);\n"
+ " return fract(sum)*scale;\n"
+ "}\n"
+ "void main() {\n"
+ " seed[0] = " + RANDOM.nextFloat() + ";\n"
+ " seed[1] = " + RANDOM.nextFloat() + ";\n"
+ " float noise = texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, -stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, -stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, stepY)).r * 0.224;\n"
+ " noise += 0.4448;\n"
+ " noise *= scale;\n"
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n"
+ " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n"
+ " float mask = (1.0 - sqrt(energy));\n"
+ " float weight = 1.0 - 1.333 * mask * noise;\n"
+ " gl_FragColor = vec4(color.rgb * weight, color.a);\n"
+ " gl_FragColor = gl_FragColor+vec4(rand(vTextureCoord + seed), rand(vTextureCoord + seed),rand(vTextureCoord + seed),1);\n"
+ "}\n";
private float strength = 0.5f;
private Random mRandom;
private int width = 1;
private int height = 1;
private int strengthLocation = -1;
private int stepXLocation = -1;
private int stepYLocation = -1;
/**
* Initialize Effect
*/
public GrainFilter() {
mRandom = new Random(new Date().getTime());
@SuppressWarnings("WeakerAccess")
public GrainFilter() { }
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
this.width = width;
this.height = height;
}
/**
* setDistortionStrength
* Sets the current distortion strength.
* 0.0: no distortion.
* 1.0: maximum distortion.
*
* @param strength Float, between 0.0f and 1.0. Zero means no distortion, while 1
* indicates the maximum amount of adjustment.
* @param strength strength
*/
public void setDistortionStrength(float strength) {
if (strength < 0.0f)
strength = 0.0f;
else if (strength > 1.0f)
strength = 1.0f;
@SuppressWarnings("WeakerAccess")
public void setStrength(float strength) {
if (strength < 0.0f) strength = 0.0f;
if (strength > 1.0f) strength = 1.0f;
this.strength = strength;
}
/**
* Returns the current strength.
*
* @see #setStrength(float)
* @return strength
*/
@SuppressWarnings({"unused", "WeakerAccess"})
public float getStrength() {
return strength;
}
@Override
public void setParameter1(float value) {
setStrength(value);
}
@Override
public float getParameter1() {
return getStrength();
}
@NonNull
@Override
public String getFragmentShader() {
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()};
String scaleString = "scale = " + strength + ";\n";
String seedString[] = new String[2];
seedString[0] = "seed[0] = " + seed[0] + ";\n";
seedString[1] = "seed[1] = " + seed[1] + ";\n";
String stepX = "stepX = " + 0.5f / mPreviewingViewWidth + ";\n";
String stepY = "stepY = " + 0.5f / mPreviewingViewHeight + ";\n";
// locString[1] = "loc[1] = loc[1]+" + seedString[1] + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ " vec2 seed;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES tex_sampler_0;\n"
+ "uniform samplerExternalOES tex_sampler_1;\n"
+ "float scale;\n"
+ " float stepX;\n"
+ " float stepY;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
+
// keep value of part1 in range: (2^-14 to 2^14).
" float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " float sum = (part1 + part2 + part3);\n"
+ " return fract(sum)*scale;\n"
+ "}\n"
+ "void main() {\n"
// Parameters that were created above
+ seedString[0]
+ seedString[1]
+ scaleString
+ stepX
+ stepY
+ " float noise = texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, -stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, -stepY)).r * 0.224;\n"
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, stepY)).r * 0.224;\n"
+ " noise += 0.4448;\n"
+ " noise *= scale;\n"
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n"
+ " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n"
+ " float mask = (1.0 - sqrt(energy));\n"
+ " float weight = 1.0 - 1.333 * mask * noise;\n"
+ " gl_FragColor = vec4(color.rgb * weight, color.a);\n"
+ " gl_FragColor = gl_FragColor+vec4(rand(vTextureCoord + seed), rand(vTextureCoord + seed),rand(vTextureCoord + seed),1);\n"
+ "}\n";
return shader;
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
strengthLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(strengthLocation, "scale");
stepXLocation = GLES20.glGetUniformLocation(programHandle, "stepX");
GlUtils.checkLocation(stepXLocation, "stepX");
stepYLocation = GLES20.glGetUniformLocation(programHandle, "stepY");
GlUtils.checkLocation(stepYLocation, "stepY");
}
@Override
public void onDestroy() {
super.onDestroy();
strengthLocation = -1;
stepXLocation = -1;
stepYLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(strengthLocation, strength);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepXLocation, 0.5f / width);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepYLocation, 0.5f / height);
GlUtils.checkError("glUniform1f");
}
}

@ -0,0 +1,29 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Converts frames to gray scale.
*/
public class GrayscaleFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n"
+ " gl_FragColor = vec4(y, y, y, color.a);\n"
+ "}\n";
public GrayscaleFilter() { }
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
}

@ -1,31 +0,0 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
/**
* Converts preview to GreyScale.
*/
public class GreyScaleFilter extends Filter {
/**
* Initialize Effect
*/
public GreyScaleFilter() {
}
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n"
+ " gl_FragColor = vec4(y, y, y, color.a);\n" + "}\n";
;
return shader;
}
}

@ -1,72 +1,106 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Apply Hue effect on the preview
* Applies a hue effect on the input frames.
*/
public class HueFilter extends Filter {
float hueValue = 0.0f;
public class HueFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float hue;\n"
+ "void main() {\n"
+ " vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n"
+ " vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n"
+ " vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n"
+ " vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n"
+ " vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n"
+ " vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float YPrime = dot(color, kRGBToYPrime);\n"
+ " float I = dot(color, kRGBToI);\n"
+ " float Q = dot(color, kRGBToQ);\n"
+ " float chroma = sqrt (I * I + Q * Q);\n"
+ " Q = chroma * sin (hue);\n"
+ " I = chroma * cos (hue);\n"
+ " vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n"
+ " color.r = dot (yIQ, kYIQToR);\n"
+ " color.g = dot (yIQ, kYIQToG);\n"
+ " color.b = dot (yIQ, kYIQToB);\n"
+ " gl_FragColor = color;\n"
+ "}\n";
private float hue = 0.0f;
private int hueLocation = -1;
public HueFilter() { }
/**
* Initialize Effect
* Sets the hue value in degrees. See the values chart:
* https://cloud.githubusercontent.com/assets/2201511/21810115/b99ac22a-d74a-11e6-9f6c-ef74d15c88c7.jpg
*
* @param hue hue degrees
*/
public HueFilter() {
@SuppressWarnings({"unused", "WeakerAccess"})
public void setHue(float hue) {
this.hue = hue % 360;
}
/**
* Hue value chart - https://cloud.githubusercontent.com/assets/2201511/21810115/b99ac22a-d74a-11e6-9f6c-ef74d15c88c7.jpg"
* Returns the current hue value.
*
* @param hueDegrees Range of value should be between 0 to 360 degrees as described in the image above
* @see #setHue(float)
* @return hue
*/
public void setHueDegreeValue(float hueDegrees) {
// manipulating input value so that we can map it on 360 degree circle
hueValue = ((hueDegrees - 45) / 45f + 0.5f) * -1;
@SuppressWarnings("WeakerAccess")
public float getHue() {
return hue;
}
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "float hue=" + hueValue + ";\n"
+ "void main() {\n"
+ "vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n"
+ "vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n"
+ "vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n"
+ "vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n"
+ "vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n"
+ "vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n"
+ "vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ "float YPrime = dot(color, kRGBToYPrime);\n"
+ "float I = dot(color, kRGBToI);\n"
+ "float Q = dot(color, kRGBToQ);\n"
+ "float chroma = sqrt (I * I + Q * Q);\n"
+ "Q = chroma * sin (hue);\n"
public void setParameter1(float value) {
setHue(value * 360F);
}
+ "I = chroma * cos (hue);\n"
@Override
public float getParameter1() {
return getHue() / 360F;
}
+ "vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n"
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
+ "color.r = dot (yIQ, kYIQToR);\n"
+ "color.g = dot (yIQ, kYIQToG);\n"
+ "color.b = dot (yIQ, kYIQToB);\n"
+ "gl_FragColor = color;\n"
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
hueLocation = GLES20.glGetUniformLocation(programHandle, "hue");
GlUtils.checkLocation(hueLocation, "hue");
}
+ "}\n";
@Override
public void onDestroy() {
super.onDestroy();
hueLocation = -1;
}
return shader;
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
// map it on 360 degree circle
float shaderHue = ((hue - 45) / 45f + 0.5f) * -1;
GLES20.glUniform1f(hueLocation, shaderHue);
GlUtils.checkError("glUniform1f");
}
}

@ -2,32 +2,30 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Inverts the preview colors. This can also be known as negative Effect.
* Inverts the input colors. This is also known as negative effect.
*/
public class InvertColorsFilter extends Filter {
/**
* Initialize Effect
*/
public InvertColorsFilter() {
}
public class InvertColorsFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float colorR = (1.0 - color.r) / 1.0;\n"
+ " float colorG = (1.0 - color.g) / 1.0;\n"
+ " float colorB = (1.0 - color.b) / 1.0;\n"
+ " gl_FragColor = vec4(colorR, colorG, colorB, color.a);\n"
+ "}\n";
public InvertColorsFilter() { }
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCoord;\n"
+ "uniform samplerExternalOES sTexture;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float colorR = (1.0 - color.r) / 1.0;\n"
+ " float colorG = (1.0 - color.g) / 1.0;\n"
+ " float colorB = (1.0 - color.b) / 1.0;\n"
+ " gl_FragColor = vec4(colorR, colorG, colorB, color.a);\n"
+ "}\n";
return shader;
return FRAGMENT_SHADER;
}
}

@ -1,143 +0,0 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import java.util.Date;
import java.util.Random;
/**
* Applies lomo-camera style effect to preview.
*/
public class LamoishFilter extends Filter {
private Random mRandom;
/**
* Initialize Effect
*/
public LamoishFilter() {
mRandom = new Random(new Date().getTime());
}
@NonNull
@Override
public String getFragmentShader() {
float scale[] = new float[2];
if (mPreviewingViewWidth > mPreviewingViewHeight) {
scale[0] = 1f;
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth;
} else {
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight;
scale[1] = 1f;
}
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1]
* scale[1])) * 0.5f;
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()};
String scaleString[] = new String[2];
String seedString[] = new String[2];
scaleString[0] = "scale[0] = " + scale[0] + ";\n";
scaleString[1] = "scale[1] = " + scale[1] + ";\n";
seedString[0] = "seed[0] = " + seed[0] + ";\n";
seedString[1] = "seed[1] = " + seed[1] + ";\n";
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n";
String stepsizeString = "stepsize = " + 1.0f / 255.0f + ";\n";
String stepsizeXString = "stepsizeX = " + 1.0f / mPreviewingViewWidth + ";\n";
String stepsizeYString = "stepsizeY = " + 1.0f / mPreviewingViewHeight + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " vec2 seed;\n"
+ " float stepsizeX;\n"
+ " float stepsizeY;\n"
+ " float stepsize;\n"
+ " vec2 scale;\n"
+ " float inv_max_dist;\n"
+ "varying vec2 vTextureCoord;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
+
// keep value of part1 in range: (2^-14 to 2^14).
" float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " return fract(part1 + part2 + part3);\n" + "}\n"
+ "void main() {\n"
// Parameters that were created above
+ scaleString[0]
+ scaleString[1]
+ seedString[0]
+ seedString[1]
+ inv_max_distString
+ stepsizeString
+ stepsizeXString
+ stepsizeYString
// sharpen
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ " vec2 coord;\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ " coord.y = vTextureCoord.y - stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x - stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n"
+
// cross process
" vec3 c_color = vec3(0.0, 0.0, 0.0);\n"
+ " float value;\n"
+ " if (s_color.r < 0.5) {\n"
+ " value = s_color.r;\n"
+ " } else {\n"
+ " value = 1.0 - s_color.r;\n"
+ " }\n"
+ " float red = 4.0 * value * value * value;\n"
+ " if (s_color.r < 0.5) {\n"
+ " c_color.r = red;\n"
+ " } else {\n"
+ " c_color.r = 1.0 - red;\n"
+ " }\n"
+ " if (s_color.g < 0.5) {\n"
+ " value = s_color.g;\n"
+ " } else {\n"
+ " value = 1.0 - s_color.g;\n"
+ " }\n"
+ " float green = 2.0 * value * value;\n"
+ " if (s_color.g < 0.5) {\n"
+ " c_color.g = green;\n"
+ " } else {\n"
+ " c_color.g = 1.0 - green;\n"
+ " }\n"
+ " c_color.b = s_color.b * 0.5 + 0.25;\n"
+
// blackwhite
" float dither = rand(vTextureCoord + seed);\n"
+ " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n"
+ " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n"
+ " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n"
+
// vignette
" coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n"
+ " gl_FragColor = vec4(bw_color * lumen, color.a);\n" + "}\n";
;
return shader;
}
}

@ -0,0 +1,165 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.util.Date;
import java.util.Random;
/**
* Applies a lomo-camera style effect to the input frames.
*/
public class LomoishFilter extends BaseFilter {
private final static Random RANDOM = new Random();
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float stepsizeX;\n"
+ "uniform float stepsizeY;\n"
+ "uniform vec2 scale;\n"
+ "uniform float inv_max_dist;\n"
+ "vec2 seed;\n"
+ "float stepsize;\n"
+ "varying vec2 vTextureCoord;\n"
+ "float rand(vec2 loc) {\n"
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n"
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n"
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n"
// keep value of part1 in range: (2^-14 to 2^14).
+ " float temp = mod(197.0 * value, 1.0) + value;\n"
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n"
+ " float part2 = value * 0.5453;\n"
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n"
+ " return fract(part1 + part2 + part3);\n"
+ "}\n"
+ "void main() {\n"
+ " seed[0] = " + RANDOM.nextFloat() + ";\n"
+ " seed[1] = " + RANDOM.nextFloat() + ";\n"
+ " stepsize = " + 1.0f / 255.0f + ";\n"
// sharpen
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ " vec2 coord;\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ " coord.y = vTextureCoord.y - stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x - stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n"
// cross process
+ " vec3 c_color = vec3(0.0, 0.0, 0.0);\n"
+ " float value;\n"
+ " if (s_color.r < 0.5) {\n"
+ " value = s_color.r;\n"
+ " } else {\n"
+ " value = 1.0 - s_color.r;\n"
+ " }\n"
+ " float red = 4.0 * value * value * value;\n"
+ " if (s_color.r < 0.5) {\n"
+ " c_color.r = red;\n"
+ " } else {\n"
+ " c_color.r = 1.0 - red;\n"
+ " }\n"
+ " if (s_color.g < 0.5) {\n"
+ " value = s_color.g;\n"
+ " } else {\n"
+ " value = 1.0 - s_color.g;\n"
+ " }\n"
+ " float green = 2.0 * value * value;\n"
+ " if (s_color.g < 0.5) {\n"
+ " c_color.g = green;\n"
+ " } else {\n"
+ " c_color.g = 1.0 - green;\n"
+ " }\n"
+ " c_color.b = s_color.b * 0.5 + 0.25;\n"
// blackwhite
+ " float dither = rand(vTextureCoord + seed);\n"
+ " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n"
+ " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n"
+ " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n"
// vignette
+ " coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n"
+ " gl_FragColor = vec4(bw_color * lumen, color.a);\n"
+ "}\n";
private int width = 1;
private int height = 1;
private int scaleLocation = -1;
private int maxDistLocation = -1;
private int stepSizeXLocation = -1;
private int stepSizeYLocation = -1;
public LomoishFilter() { }
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
this.width = width;
this.height = height;
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale");
maxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(maxDistLocation, "inv_max_dist");
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX");
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX");
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY");
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY");
}
@Override
public void onDestroy() {
super.onDestroy();
scaleLocation = -1;
maxDistLocation = -1;
stepSizeXLocation = -1;
stepSizeYLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float[] scale = new float[2];
if (width > height) {
scale[0] = 1f;
scale[1] = ((float) height) / width;
} else {
scale[0] = ((float) width) / height;
scale[1] = 1f;
}
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
GLES20.glUniform2fv(scaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv");
GLES20.glUniform1f(maxDistLocation, 1.0F / maxDist);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height);
GlUtils.checkError("glUniform1f");
}
}

@ -1,12 +0,0 @@
package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
public class NoFilter extends Filter {
@NonNull
@Override
public String getFragmentShader() {
return mFragmentShader;
}
}

@ -2,31 +2,30 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Applies Posterization effect to Preview.
* Applies a posterization effect to the input frames.
*/
public class PosterizeFilter extends Filter {
/**
* Initialize Effect
*/
public PosterizeFilter() {
}
public class PosterizeFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 pcolor;\n"
+ " pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n"
+ " pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n"
+ " pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n"
+ " gl_FragColor = vec4(pcolor, color.a);\n"
+ "}\n";
public PosterizeFilter() { }
@NonNull
@Override
public String getFragmentShader() {
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 pcolor;\n"
+ " pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n"
+ " pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n"
+ " pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n"
+ " gl_FragColor = vec4(pcolor, color.a);\n" + "}\n";
return shader;
return FRAGMENT_SHADER;
}
}

@ -1,102 +1,124 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Adjusts color saturation of preview.
* Adjusts color saturation.
*/
public class SaturationFilter extends Filter {
private float scale = 1.0f;
public class SaturationFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float scale;\n"
+ "uniform vec3 exponents;\n"
+ "float shift;\n"
+ "vec3 weights;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " weights[0] = " + 2f / 8f + ";\n"
+ " weights[1] = " + 5f / 8f + ";\n"
+ " weights[2] = " + 1f / 8f + ";\n"
+ " shift = " + 1.0f / 255.0f + ";\n"
+ " vec4 oldcolor = texture2D(sTexture, vTextureCoord);\n"
+ " float kv = dot(oldcolor.rgb, weights) + shift;\n"
+ " vec3 new_color = scale * oldcolor.rgb + (1.0 - scale) * kv;\n"
+ " gl_FragColor = vec4(new_color, oldcolor.a);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float de = dot(color.rgb, weights);\n"
+ " float inv_de = 1.0 / de;\n"
+ " vec3 verynew_color = de * pow(color.rgb * inv_de, exponents);\n"
+ " float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0);\n"
+ " gl_FragColor = gl_FragColor+vec4(verynew_color / max_color, color.a);\n"
+ "}\n";
private float scale = 1F; // -1...1
private int scaleLocation = -1;
private int exponentsLocation = -1;
public SaturationFilter() { }
/**
* Initialize Effect
* Sets the saturation correction value:
* -1.0: fully desaturated, grayscale.
* 0.0: no change.
* +1.0: fully saturated.
*
* @param value new value
*/
public SaturationFilter() {
@SuppressWarnings("WeakerAccess")
public void setSaturation(float value) {
if (value < -1F) value = -1F;
if (value > 1F) value = 1F;
scale = value;
}
/**
* @param value Float, between 0.0 and 1. 0 means no change, while 0.0 indicates
* full desaturated, i.e. grayscale.
* and 1.0 indicates full saturation
* Returns the current saturation.
*
* @see #setSaturation(float)
* @return saturation
*/
public void setSaturationValue(float value) {
if (value < 0.0f)
value = 0.0f;
else if (value > 1.0f)
value = 1.0f;
@SuppressWarnings("WeakerAccess")
public float getSaturation() {
return scale;
}
//since the shader excepts a range of -1.0 to 1.0
//will multiply it by 2.0f and subtract 1.0 to every value
this.scale = (2.0f * value) - 1.0f;
@Override
public void setParameter1(float value) {
setSaturation(2F * value - 1F);
}
public float getSaturationValue() {
//since the shader excepts a range of -1.0 to 1.0
//will add 1.0 to every value and divide it by 2.0f
return (scale + 1.0f) / 2.0f;
@Override
public float getParameter1() {
return (getSaturation() + 1F) / 2F;
}
@NonNull
@Override
public String getFragmentShader() {
float shift = 1.0f / 255.0f;
float weights[] = {2f / 8f, 5f / 8f, 1f / 8f};
float exponents[] = new float[3];
String weightsString[] = new String[3];
String exponentsString[] = new String[3];
exponentsString[0] = "";
exponentsString[1] = "";
exponentsString[2] = "";
String scaleString = "";
if (scale > 0.0f) {
exponents[0] = (0.9f * scale) + 1.0f;
exponents[1] = (2.1f * scale) + 1.0f;
exponents[2] = (2.7f * scale) + 1.0f;
exponentsString[0] = "exponents[0] = " + exponents[0] + ";\n";
exponentsString[1] = "exponents[1] = " + exponents[1] + ";\n";
exponentsString[2] = "exponents[2] = " + exponents[2] + ";\n";
} else
scaleString = "scale = " + (1.0f + scale) + ";\n";
weightsString[0] = "weights[0] = " + weights[0] + ";\n";
weightsString[1] = "weights[1] = " + weights[1] + ";\n";
weightsString[2] = "weights[2] = " + weights[2] + ";\n";
String shiftString = "shift = " + shift + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n" + " float scale;\n"
+ " float shift;\n" + " vec3 weights;\n" + " vec3 exponents;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
// Parameters that were created above
+ weightsString[0]
+ weightsString[1]
+ weightsString[2]
+ shiftString
+ scaleString
+ " vec4 oldcolor = texture2D(sTexture, vTextureCoord);\n"
+ " float kv = dot(oldcolor.rgb, weights) + shift;\n"
+ " vec3 new_color = scale * oldcolor.rgb + (1.0 - scale) * kv;\n"
+ " gl_FragColor= vec4(new_color, oldcolor.a);\n"
// Parameters that were created above
+ weightsString[0]
+ weightsString[1]
+ weightsString[2]
+ exponentsString[0]
+ exponentsString[1]
+ exponentsString[2]
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float de = dot(color.rgb, weights);\n"
+ " float inv_de = 1.0 / de;\n"
+ " vec3 verynew_color = de * pow(color.rgb * inv_de, exponents);\n"
+ " float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0);\n"
+ " gl_FragColor = gl_FragColor+vec4(verynew_color / max_color, color.a);\n"
+ "}\n";
return FRAGMENT_SHADER;
}
return shader;
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale");
exponentsLocation = GLES20.glGetUniformLocation(programHandle, "exponents");
GlUtils.checkLocation(exponentsLocation, "exponents");
}
@Override
public void onDestroy() {
super.onDestroy();
scaleLocation = -1;
exponentsLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
if (scale > 0.0f) {
GLES20.glUniform1f(scaleLocation, 0F);
GlUtils.checkError("glUniform1f");
GLES20.glUniform3f(exponentsLocation,
(0.9f * scale) + 1.0f,
(2.1f * scale) + 1.0f,
(2.7f * scale) + 1.0f
);
GlUtils.checkError("glUniform3f");
} else {
GLES20.glUniform1f(scaleLocation, 1.0F + scale);
GlUtils.checkError("glUniform1f");
GLES20.glUniform3f(exponentsLocation, 0F, 0F, 0F);
GlUtils.checkError("glUniform3f");
}
}
}

@ -2,47 +2,38 @@ package com.otaliastudios.cameraview.filters;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
/**
* Converts preview to Sepia tone.
* Converts frames to sepia tone.
*/
public class SepiaFilter extends Filter {
/**
* Initialize Effect
*/
public SepiaFilter() {
}
public class SepiaFilter extends BaseFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "mat3 matrix;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " matrix[0][0]=" + 805.0f / 2048.0f + ";\n"
+ " matrix[0][1]=" + 715.0f / 2048.0f + ";\n"
+ " matrix[0][2]=" + 557.0f / 2048.0f + ";\n"
+ " matrix[1][0]=" + 1575.0f / 2048.0f + ";\n"
+ " matrix[1][1]=" + 1405.0f / 2048.0f + ";\n"
+ " matrix[1][2]=" + 1097.0f / 2048.0f + ";\n"
+ " matrix[2][0]=" + 387.0f / 2048.0f + ";\n"
+ " matrix[2][1]=" + 344.0f / 2048.0f + ";\n"
+ " matrix[2][2]=" + 268.0f / 2048.0f + ";\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 new_color = min(matrix * color.rgb, 1.0);\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n"
+ "}\n";
public SepiaFilter() { }
@NonNull
@Override
public String getFragmentShader() {
float weights[] = {805.0f / 2048.0f, 715.0f / 2048.0f,
557.0f / 2048.0f, 1575.0f / 2048.0f, 1405.0f / 2048.0f,
1097.0f / 2048.0f, 387.0f / 2048.0f, 344.0f / 2048.0f,
268.0f / 2048.0f};
String matrixString[] = new String[9];
matrixString[0] = " matrix[0][0]=" + weights[0] + ";\n";
matrixString[1] = " matrix[0][1]=" + weights[1] + ";\n";
matrixString[2] = " matrix[0][2]=" + weights[2] + ";\n";
matrixString[3] = " matrix[1][0]=" + weights[3] + ";\n";
matrixString[4] = " matrix[1][1]=" + weights[4] + ";\n";
matrixString[5] = " matrix[1][2]=" + weights[5] + ";\n";
matrixString[6] = " matrix[2][0]=" + weights[6] + ";\n";
matrixString[7] = " matrix[2][1]=" + weights[7] + ";\n";
matrixString[8] = " matrix[2][2]=" + weights[8] + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n" + " mat3 matrix;\n"
+ "varying vec2 vTextureCoord;\n" + "void main() {\n"
+ matrixString[0] + matrixString[1] + matrixString[2]
+ matrixString[3] + matrixString[4] + matrixString[5]
+ matrixString[6] + matrixString[7] + matrixString[8]
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 new_color = min(matrix * color.rgb, 1.0);\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
return shader;
return FRAGMENT_SHADER;
}
}

@ -1,75 +1,128 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Sharpens the preview.
* Sharpens the input frames.
*/
public class SharpnessFilter extends Filter {
public class SharpnessFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float scale;\n"
+ "uniform float stepsizeX;\n"
+ "uniform float stepsizeY;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ " vec2 coord;\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ " coord.y = vTextureCoord.y - stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x - stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n"
+ "}\n";
private float scale = 0.5f;
private int width = 1;
private int height = 1;
private int scaleLocation = -1;
private int stepSizeXLocation = -1;
private int stepSizeYLocation = -1;
public SharpnessFilter() { }
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
this.width = width;
this.height = height;
}
/**
* Initialize Effect
* Sets the current sharpness value:
* 0.0: no change.
* 1.0: maximum sharpness.
*
* @param value new sharpness
*/
public SharpnessFilter() {
@SuppressWarnings("WeakerAccess")
public void setSharpness(float value) {
if (value < 0.0f) value = 0.0f;
if (value > 1.0f) value = 1.0f;
this.scale = value;
}
/**
* @param value Float, between 0 and 1. 0 means no change.
* Returns the current sharpness.
*
* @see #setSharpness(float)
* @return sharpness
*/
public void setSharpnessValue(float value) {
if (value < 0.0f)
value = 0.0f;
else if (value > 1.0f)
value = 1.0f;
@SuppressWarnings("WeakerAccess")
public float getSharpness() {
return scale;
}
this.scale = value;
@Override
public void setParameter1(float value) {
setSharpness(value);
}
public float getSharpnessValue() {
return scale;
@Override
public float getParameter1() {
return getSharpness();
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String stepsizeXString = "stepsizeX = " + 1.0f / mPreviewingViewWidth + ";\n";
String stepsizeYString = "stepsizeY = " + 1.0f / mPreviewingViewHeight + ";\n";
String scaleString = "scale = " + scale + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " float scale;\n"
+ " float stepsizeX;\n"
+ " float stepsizeY;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
// Parameters that were created above
+ stepsizeXString
+ stepsizeYString
+ scaleString
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n"
+ " vec2 coord;\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n"
+ " coord.y = vTextureCoord.y - stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x - stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " coord.x = vTextureCoord.x + stepsizeX;\n"
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n"
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n"
+ " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n"
+ "}\n";
return shader;
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale");
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX");
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX");
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY");
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY");
}
@Override
public void onDestroy() {
super.onDestroy();
scaleLocation = -1;
stepSizeXLocation = -1;
stepSizeYLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width);
GlUtils.checkError("glUniform1f");
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height);
GlUtils.checkError("glUniform1f");
}
}

@ -1,61 +1,102 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Adjusts color temperature of the preview.
* Adjusts color temperature.
*/
public class TemperatureFilter extends Filter {
private float scale = 0f;
public class TemperatureFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 new_color = color.rgb;\n"
+ " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n"
+ " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n"
+ " if (scale > 0.0) { \n"
+ " new_color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n"
+ " }\n"
+ " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n"
+ " if (max_value > 1.0) { \n"
+ " new_color /= max_value;\n"
+ " } \n"
+ " gl_FragColor = vec4(new_color, color.a);\n"
+ "}\n";
private float scale = 1F; // -1...1
private int scaleLocation = -1;
public TemperatureFilter() { }
/**
* Initialize Effect
* Sets the new temperature value:
* -1.0: cool colors
* 0.0: no change
* 1.0: warm colors
*
* @param value new value
*/
public TemperatureFilter() {
@SuppressWarnings("WeakerAccess")
public void setTemperature(float value) {
if (value < -1F) value = -1F;
if (value > 1F) value = 1F;
this.scale = value;
}
/**
* @param scale Float, between 0 and 1, with 0 indicating cool, and 1
* indicating warm. A value of of 0.5 indicates no change.
* Returns the current temperature.
*
* @see #setTemperature(float)
* @return temperature
*/
public void setTemperatureScale(float scale) {
if (scale < 0.0f)
scale = 0.0f;
else if (scale > 1.0f)
scale = 1.0f;
this.scale = scale;
@SuppressWarnings("WeakerAccess")
public float getTemperature() {
return scale;
}
public float getTemperatureScale() {
return scale;
@Override
public void setParameter1(float value) {
setTemperature((2F * value - 1F));
}
@Override
public float getParameter1() {
return (getTemperature() + 1F) / 2F;
}
@NonNull
@Override
public String getFragmentShader() {
return FRAGMENT_SHADER;
}
String scaleString = "scale = " + (2.0f * scale - 1.0f) + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " float scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n" // Parameters that were created above
+ scaleString
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " vec3 new_color = color.rgb;\n"
+ " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n"
+ " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n"
+ " if (scale > 0.0) { \n"
+ " new_color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n"
+ " }\n"
+ " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n"
+ " if (max_value > 1.0) { \n"
+ " new_color /= max_value;\n" + " } \n"
+ " gl_FragColor = vec4(new_color, color.a);\n" + "}\n";
return shader;
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(scaleLocation, "scale");
}
@Override
public void onDestroy() {
super.onDestroy();
scaleLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
GLES20.glUniform1f(scaleLocation, scale);
GlUtils.checkError("glUniform1f");
}
}

@ -1,66 +1,101 @@
package com.otaliastudios.cameraview.filters;
import android.graphics.Color;
import android.opengl.GLES20;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.OneParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Tints the preview with specified color..
* Tints the frames with specified color.
*/
public class TintFilter extends Filter {
private int mTint = 0xFFFF0000;
public class TintFilter extends BaseFilter implements OneParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform vec3 tint;\n"
+ "vec3 color_ratio;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " color_ratio[0] = " + 0.21f + ";\n"
+ " color_ratio[1] = " + 0.71f + ";\n"
+ " color_ratio[2] = " + 0.07f + ";\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float avg_color = dot(color_ratio, color.rgb);\n"
+ " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
private int tint = Color.RED;
private int tintLocation = -1;
public TintFilter() { }
/**
* Initialize Effect
* Sets the current tint.
* @param color current tint
*/
public TintFilter() {
@SuppressWarnings("WeakerAccess")
public void setTint(@ColorInt int color) {
this.tint = color;
}
public void setTintColor(int color) {
this.mTint = color;
/**
* Returns the current tint.
*
* @see #setTint(int)
* @return tint
*/
@SuppressWarnings("WeakerAccess")
@ColorInt
public int getTint() {
return tint;
}
@Override
public void setParameter1(float value) {
// no easy way to transform 0...1 into a color.
setTint((int) (value * Integer.MAX_VALUE));
}
public int getTintColor() {
return mTint;
@Override
public float getParameter1() {
return (float) getTint() / Integer.MAX_VALUE;
}
@NonNull
@Override
public String getFragmentShader() {
float color_ratio[] = {0.21f, 0.71f, 0.07f};
String color_ratioString[] = new String[3];
color_ratioString[0] = "color_ratio[0] = " + color_ratio[0] + ";\n";
color_ratioString[1] = "color_ratio[1] = " + color_ratio[1] + ";\n";
color_ratioString[2] = "color_ratio[2] = " + color_ratio[2] + ";\n";
float tint_color[] = {Color.red(mTint) / 255f,
Color.green(mTint) / 255f, Color.blue(mTint) / 255f};
String tintString[] = new String[3];
tintString[0] = "tint[0] = " + tint_color[0] + ";\n";
tintString[1] = "tint[1] = " + tint_color[1] + ";\n";
tintString[2] = "tint[2] = " + tint_color[2] + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " vec3 tint;\n"
+ " vec3 color_ratio;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
// Parameters that were created above
+ color_ratioString[0]
+ color_ratioString[1]
+ color_ratioString[2]
+ tintString[0]
+ tintString[1]
+ tintString[2]
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " float avg_color = dot(color_ratio, color.rgb);\n"
+ " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n"
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n";
return shader;
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
tintLocation = GLES20.glGetUniformLocation(programHandle, "tint");
GlUtils.checkLocation(tintLocation, "tint");
}
@Override
public void onDestroy() {
super.onDestroy();
tintLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float[] channels = new float[]{
Color.red(tint) / 255f,
Color.green(tint) / 255f,
Color.blue(tint) / 255f
};
GLES20.glUniform3fv(tintLocation, 1, channels, 0);
GlUtils.checkError("glUniform3fv");
}
}

@ -1,99 +1,174 @@
package com.otaliastudios.cameraview.filters;
import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.BaseFilter;
import com.otaliastudios.cameraview.filter.TwoParameterFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
/**
* Applies lomo-camera style effect to your preview.
* Applies a vignette effect to input frames.
*/
public class VignetteFilter extends Filter {
private float mScale = 0.85f;
private float mShade = 0.5f;
public class VignetteFilter extends BaseFilter implements TwoParameterFilter {
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "uniform float range;\n"
+ "uniform float inv_max_dist;\n"
+ "uniform float shade;\n"
+ "uniform vec2 scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
+ " const float slope = 20.0;\n"
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " gl_FragColor = vec4(color.rgb * lumen, color.a);\n"
+ "}\n";
private float mScale = 0.85f; // 0...1
private float mShade = 0.5f; // 0...1
private int mWidth = 1;
private int mHeight = 1;
private int mRangeLocation = -1;
private int mMaxDistLocation = -1;
private int mShadeLocation = -1;
private int mScaleLocation = -1;
public VignetteFilter() { }
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
mWidth = width;
mHeight = height;
}
/**
* Initialize Effect
* Sets the vignette effect scale (0.0 - 1.0).
* @param scale new scale
*/
public VignetteFilter() {
@SuppressWarnings("WeakerAccess")
public void setVignetteScale(float scale) {
if (scale < 0.0f) scale = 0.0f;
if (scale > 1.0f) scale = 1.0f;
mScale = scale;
}
/**
* setVignetteEffectScale
* Sets the vignette effect shade (0.0 - 1.0).
* @param shade new shade
*/
@SuppressWarnings("WeakerAccess")
public void setVignetteShade(float shade) {
if (shade < 0.0f) shade = 0.0f;
if (shade > 1.0f) shade = 1.0f;
this.mShade = shade;
}
/**
* Gets the current vignette scale.
*
* @param scale Float, between 0.0 and 1. 0
* @see #setVignetteScale(float)
* @return scale
*/
public void setVignetteEffectScale(float scale) {
if (scale < 0.0f)
scale = 0.0f;
else if (scale > 1.0f)
scale = 1.0f;
this.mScale = scale;
@SuppressWarnings("WeakerAccess")
public float getVignetteScale() {
return mScale;
}
/**
* setVignetteEffectShade
* Gets the current vignette shade.
*
* @param shade Float, between 0.0 and 1. 0
* @see #setVignetteShade(float)
* @return shade
*/
public void setVignetteEffectShade(float shade) {
if (shade < 0.0f)
shade = 0.0f;
else if (shade > 1.0f)
shade = 1.0f;
this.mShade = shade;
@SuppressWarnings("WeakerAccess")
public float getVignetteShade() {
return mShade;
}
@Override
public void setParameter1(float value) {
setVignetteScale(value);
}
@Override
public float getParameter1() {
return getVignetteScale();
}
@Override
public void setParameter2(float value) {
setVignetteShade(value);
}
@Override
public float getParameter2() {
return getVignetteShade();
}
@NonNull
@Override
public String getFragmentShader() {
float scale[] = new float[2];
if (mPreviewingViewWidth > mPreviewingViewHeight) {
return FRAGMENT_SHADER;
}
@Override
public void onCreate(int programHandle) {
super.onCreate(programHandle);
mRangeLocation = GLES20.glGetUniformLocation(programHandle, "range");
GlUtils.checkLocation(mRangeLocation, "range");
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist");
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist");
mShadeLocation = GLES20.glGetUniformLocation(programHandle, "shade");
GlUtils.checkLocation(mShadeLocation, "shade");
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale");
GlUtils.checkLocation(mScaleLocation, "scale");
}
@Override
public void onDestroy() {
super.onDestroy();
mRangeLocation = -1;
mMaxDistLocation = -1;
mShadeLocation = -1;
mScaleLocation = -1;
}
@Override
protected void onPreDraw(float[] transformMatrix) {
super.onPreDraw(transformMatrix);
float[] scale = new float[2];
if (mWidth > mHeight) {
scale[0] = 1f;
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth;
scale[1] = ((float) mHeight) / mWidth;
} else {
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight;
scale[0] = ((float) mWidth) / mHeight;
scale[1] = 1f;
}
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1]
* scale[1])) * 0.5f;
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0);
GlUtils.checkError("glUniform2fv");
String scaleString[] = new String[2];
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f;
GLES20.glUniform1f(mMaxDistLocation, 1F / maxDist);
GlUtils.checkError("glUniform1f");
scaleString[0] = "scale[0] = " + scale[0] + ";\n";
scaleString[1] = "scale[1] = " + scale[1] + ";\n";
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n";
String shadeString = "shade = " + mShade + ";\n";
GLES20.glUniform1f(mShadeLocation, mShade);
GlUtils.checkError("glUniform1f");
// The 'range' is between 1.3 to 0.6. When scale is zero then range is
// 1.3
// The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
// which means no vignette at all because the luminousity difference is
// less than 1/256 and will cause nothing.
String rangeString = "range = "
+ (1.30f - (float) Math.sqrt(mScale) * 0.7f) + ";\n";
String shader = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ " float range;\n"
+ " float inv_max_dist;\n"
+ " float shade;\n"
+ " vec2 scale;\n"
+ "varying vec2 vTextureCoord;\n"
+ "void main() {\n"
// Parameters that were created above
+ scaleString[0]
+ scaleString[1]
+ inv_max_distString
+ shadeString
+ rangeString
+ " const float slope = 20.0;\n"
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n"
+ " float dist = length(coord * scale);\n"
+ " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n"
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n"
+ " gl_FragColor = vec4(color.rgb * lumen, color.a);\n"
+ "}\n";
return shader;
float range = (1.30f - (float) Math.sqrt(mScale) * 0.7f);
GLES20.glUniform1f(mRangeLocation, range);
GlUtils.checkError("glUniform1f");
}
}

@ -1,27 +1,29 @@
package com.otaliastudios.cameraview.internal.egl;
package com.otaliastudios.cameraview.internal;
import android.opengl.GLES20;
import android.opengl.Matrix;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
class EglElement {
public class GlUtils {
private final static CameraLogger LOG = CameraLogger.create(EglElement.class.getSimpleName());
private final static String TAG = GlUtils.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
// Identity matrix for general use.
protected static final float[] IDENTITY_MATRIX = new float[16];
public static final float[] IDENTITY_MATRIX = new float[16];
static {
Matrix.setIdentityM(IDENTITY_MATRIX, 0);
}
// Check for GLES errors.
protected static void check(String opName) {
public static void checkError(@NonNull String opName) {
int error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
String message = LOG.e("Error during", opName, "glError 0x", Integer.toHexString(error));
@ -29,18 +31,17 @@ class EglElement {
}
}
// Check for valid location.
protected static void checkLocation(int location, String label) {
public static void checkLocation(int location, @NonNull String name) {
if (location < 0) {
String message = LOG.e("Unable to locate", label, "in program");
String message = LOG.e("Unable to locate", name, "in program");
throw new RuntimeException(message);
}
}
// Compiles the given shader, returns a handle.
protected static int loadShader(int shaderType, String source) {
@SuppressWarnings("WeakerAccess")
public static int loadShader(int shaderType, @NonNull String source) {
int shader = GLES20.glCreateShader(shaderType);
check("glCreateShader type=" + shaderType);
checkError("glCreateShader type=" + shaderType);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
int[] compiled = new int[1];
@ -54,21 +55,21 @@ class EglElement {
}
// Creates a program with given vertex shader and pixel shader.
protected static int createProgram(String vertexSource, String fragmentSource) {
public static int createProgram(@NonNull String vertexSource, @NonNull String fragmentSource) {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) return 0;
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
if (pixelShader == 0) return 0;
int program = GLES20.glCreateProgram();
check("glCreateProgram");
checkError("glCreateProgram");
if (program == 0) {
LOG.e("Could not create program");
}
GLES20.glAttachShader(program, vertexShader);
check("glAttachShader");
checkError("glAttachShader");
GLES20.glAttachShader(program, pixelShader);
check("glAttachShader");
checkError("glAttachShader");
GLES20.glLinkProgram(program);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
@ -81,7 +82,7 @@ class EglElement {
}
// Allocates a direct float buffer, and populates it with the float array data.
protected static FloatBuffer floatBuffer(float[] coords) {
public static FloatBuffer floatBuffer(@NonNull float[] coords) {
// Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4);
bb.order(ByteOrder.nativeOrder());

@ -25,6 +25,7 @@ import androidx.annotation.RequiresApi;
import android.util.Log;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
@ -40,7 +41,7 @@ import java.nio.ByteOrder;
* There can be multiple surfaces associated with a single context.
*/
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class EglBaseSurface extends EglElement {
public class EglBaseSurface {
protected static final String TAG = EglBaseSurface.class.getSimpleName();
private final static CameraLogger LOG = CameraLogger.create(TAG);
@ -187,7 +188,7 @@ public class EglBaseSurface extends EglElement {
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glReadPixels(0, 0, width, height,
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
check("glReadPixels");
GlUtils.checkError("glReadPixels");
buf.rewind();
BufferedOutputStream bos = null;
@ -229,7 +230,7 @@ public class EglBaseSurface extends EglElement {
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
buf.order(ByteOrder.LITTLE_ENDIAN);
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
check("glReadPixels");
GlUtils.checkError("glReadPixels");
buf.rewind();
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length);

@ -7,186 +7,89 @@ import android.opengl.GLES20;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filters.NoFilter;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.cameraview.internal.GlUtils;
import java.nio.FloatBuffer;
/**
* This is a mix of 3 grafika classes, FullFrameRect, Texture2dProgram, Drawable2d.
*/
public class EglViewport extends EglElement {
public class EglViewport {
private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName());
// Stuff from Drawable2d.FULL_RECTANGLE
// A full square, extending from -1 to +1 in both dimensions.
// When the model/view/projection matrix is identity, this will exactly cover the viewport.
private static final float[] FULL_RECTANGLE_COORDS = {
-1.0f, -1.0f, // 0 bottom left
1.0f, -1.0f, // 1 bottom right
-1.0f, 1.0f, // 2 top left
1.0f, 1.0f, // 3 top right
};
// Stuff from Drawable2d.FULL_RECTANGLE
// A full square, extending from -1 to +1 in both dimensions.
private static final float[] FULL_RECTANGLE_TEX_COORDS = {
0.0f, 0.0f, // 0 bottom left
1.0f, 0.0f, // 1 bottom right
0.0f, 1.0f, // 2 top left
1.0f, 1.0f // 3 top right
};
// Stuff from Drawable2d.FULL_RECTANGLE
private final static int VERTEX_COUNT = FULL_RECTANGLE_COORDS.length / 2;
private FloatBuffer mVertexCoordinatesArray = floatBuffer(FULL_RECTANGLE_COORDS);
private FloatBuffer mTextureCoordinatesArray = floatBuffer(FULL_RECTANGLE_TEX_COORDS);
// Stuff from Texture2dProgram
private int mProgramHandle;
private int mProgramHandle = -1;
private int mTextureTarget;
private int mTextureUnit;
// Program attributes
private int muMVPMatrixLocation;
private int muTexMatrixLocation;
private int maPositionLocation;
private int maTextureCoordLocation;
// private int muKernelLoc; // Used for filtering
// private int muTexOffsetLoc; // Used for filtering
// private int muColorAdjustLoc; // Used for filtering
private Filter mShaderEffect;
private boolean mIsShaderChanged = false;
private Filter mFilter;
private boolean mFilterChanged = false;
public EglViewport() {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
mTextureUnit = GLES20.GL_TEXTURE0;
//init the default shader effect
mShaderEffect = new NoFilter();
initProgram();
this(new NoFilter());
}
private void initProgram() {
release();
mProgramHandle = createProgram(mShaderEffect.getVertexShader(), mShaderEffect.getFragmentShader());
maPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, mShaderEffect.getPositionVariableName());
checkLocation(maPositionLocation, mShaderEffect.getPositionVariableName());
maTextureCoordLocation = GLES20.glGetAttribLocation(mProgramHandle, mShaderEffect.getTexttureCoordinateVariableName());
checkLocation(maTextureCoordLocation, mShaderEffect.getTexttureCoordinateVariableName());
muMVPMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, mShaderEffect.getMVPMatrixVariableName());
checkLocation(muMVPMatrixLocation, mShaderEffect.getMVPMatrixVariableName());
muTexMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, mShaderEffect.getTextureMatrixVariableName());
checkLocation(muTexMatrixLocation, mShaderEffect.getTextureMatrixVariableName());
public EglViewport(@NonNull Filter filter) {
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
mTextureUnit = GLES20.GL_TEXTURE0;
mFilter = filter;
createProgram();
}
public void release(boolean doEglCleanup) {
if (doEglCleanup) GLES20.glDeleteProgram(mProgramHandle);
mProgramHandle = -1;
private void createProgram() {
release(); // Release old program if present.
mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), mFilter.getFragmentShader());
mFilter.onCreate(mProgramHandle);
}
public void release() {
release(true);
if (mProgramHandle != -1) {
mFilter.onDestroy();
GLES20.glDeleteProgram(mProgramHandle);
mProgramHandle = -1;
}
}
public int createTexture() {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
check("glGenTextures");
GlUtils.checkError("glGenTextures");
int texId = textures[0];
GLES20.glActiveTexture(mTextureUnit);
GLES20.glBindTexture(mTextureTarget, texId);
check("glBindTexture " + texId);
GlUtils.checkError("glBindTexture " + texId);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
check("glTexParameter");
GlUtils.checkError("glTexParameter");
return texId;
}
public void changeShaderFilter(@NonNull Filter shaderEffect){
this.mShaderEffect = shaderEffect;
mIsShaderChanged = true;
public void setFilter(@NonNull Filter filter){
this.mFilter = filter;
mFilterChanged = true;
}
public void drawFrame(int textureId, float[] textureMatrix) {
drawFrame(textureId, textureMatrix,
mVertexCoordinatesArray,
mTextureCoordinatesArray);
}
/**
* The issue with the CIRCLE shader is that if the textureMatrix has a scale value,
* it fails miserably, not taking the scale into account.
* So what we can do here is
*
* - read textureMatrix scaleX and scaleY values. This is pretty much impossible to do from the matrix itself
* without making risky assumptions over the order of operations.
* https://www.opengl.org/discussion_boards/showthread.php/159215-Is-it-possible-to-extract-rotation-translation-scale-given-a-matrix
* So we prefer passing scaleX and scaleY here to the draw function.
* - pass these values to the vertex shader
* - pass them to the fragment shader
* - in the fragment shader, take this scale value into account
*/
private void drawFrame(int textureId, float[] textureMatrix,
FloatBuffer vertexBuffer,
FloatBuffer texBuffer) {
if (mIsShaderChanged){
initProgram();
mIsShaderChanged = false;
if (mFilterChanged) {
createProgram();
mFilterChanged = false;
}
check("draw start");
GlUtils.checkError("draw start");
// Select the program.
// Select the program and the active texture.
GLES20.glUseProgram(mProgramHandle);
check("glUseProgram");
// Set the texture.
GlUtils.checkError("glUseProgram");
GLES20.glActiveTexture(mTextureUnit);
GLES20.glBindTexture(mTextureTarget, textureId);
// Copy the model / view / projection matrix over.
GLES20.glUniformMatrix4fv(muMVPMatrixLocation, 1, false, IDENTITY_MATRIX, 0);
check("glUniformMatrix4fv");
// Copy the texture transformation matrix over.
GLES20.glUniformMatrix4fv(muTexMatrixLocation, 1, false, textureMatrix, 0);
check("glUniformMatrix4fv");
// Enable the "aPosition" vertex attribute.
// Connect vertexBuffer to "aPosition".
GLES20.glEnableVertexAttribArray(maPositionLocation);
check("glEnableVertexAttribArray");
GLES20.glVertexAttribPointer(maPositionLocation, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);
check("glVertexAttribPointer");
// Enable the "aTextureCoord" vertex attribute.
// Connect texBuffer to "aTextureCoord".
GLES20.glEnableVertexAttribArray(maTextureCoordLocation);
check("glEnableVertexAttribArray");
GLES20.glVertexAttribPointer(maTextureCoordLocation, 2, GLES20.GL_FLOAT, false, 8, texBuffer);
check("glVertexAttribPointer");
// Draw the rect.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COUNT);
check("glDrawArrays");
// Draw.
mFilter.draw(textureMatrix);
// Done -- disable vertex array, texture, and program.
GLES20.glDisableVertexAttribArray(maPositionLocation);
GLES20.glDisableVertexAttribArray(maTextureCoordLocation);
// Release.
GLES20.glBindTexture(mTextureTarget, 0);
GLES20.glUseProgram(0);
}

@ -26,7 +26,7 @@ import com.otaliastudios.cameraview.overlay.OverlayDrawer;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.size.AspectRatio;
import com.otaliastudios.cameraview.size.Size;
@ -97,15 +97,14 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
@RendererThread
@Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, Filter shaderEffect) {
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
mPreview.removeRendererFrameCallback(this);
SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY, shaderEffect);
SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY);
}
@Override
public void
onFilterChanged(@NonNull Filter filter) {
mViewport.changeShaderFilter(filter);
public void onFilterChanged(@NonNull Filter filter) {
mViewport.setFilter(filter.copy());
}
});
}
@ -151,7 +150,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
*/
@RendererThread
@TargetApi(Build.VERSION_CODES.KITKAT)
private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, @NonNull Filter filter) {
private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) {
// Get egl context from the RendererThread, which is the one in which we have created
// the textureId and the overlayTextureId, managed by the GlSurfaceView.
// Next operations can then be performed on different threads using this handle.

@ -0,0 +1,39 @@
package com.otaliastudios.cameraview.preview;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filter.Filter;
/**
* A preview that support GL filters defined through the {@link Filter} interface.
*
* The preview has the responsibility of calling {@link Filter#setSize(int, int)}
* whenever the preview size changes and as soon as the filter is applied.
*/
public abstract class FilterCameraPreview<T extends View, Output> extends CameraPreview<T, Output> {
@SuppressWarnings("WeakerAccess")
public FilterCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) {
super(context, parent);
}
/**
* Sets a new filter.
* @param filter new filter
*/
public abstract void setFilter(@NonNull Filter filter);
/**
* Returns the currently used filter.
* @return currently used filter
*/
@SuppressWarnings("unused")
@NonNull
public abstract Filter getCurrentFilter();
}

@ -15,8 +15,8 @@ import android.view.ViewGroup;
import com.otaliastudios.cameraview.R;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.internal.utils.Op;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filters.NoFilter;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.filter.NoFilter;
import com.otaliastudios.cameraview.size.AspectRatio;
import java.util.Collections;
@ -58,7 +58,7 @@ import javax.microedition.khronos.opengles.GL10;
* Callbacks are guaranteed to be called on the renderer thread, which means that we can fetch
* the GL context that was created and is managed by the {@link GLSurfaceView}.
*/
public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture> {
public class GlCameraPreview extends FilterCameraPreview<GLSurfaceView, SurfaceTexture> {
private boolean mDispatched;
private final float[] mTransformMatrix = new float[16];
@ -69,8 +69,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
@VisibleForTesting float mCropScaleX = 1F;
@VisibleForTesting float mCropScaleY = 1F;
private View mRootView;
private Filter mCurrentShaderEffect;
private Filter mCurrentFilter;
public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) {
super(context, parent);
@ -142,7 +141,8 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
@RendererThread
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mOutputViewport = new EglViewport();
if (mCurrentFilter == null) mCurrentFilter = new NoFilter();
mOutputViewport = new EglViewport(mCurrentFilter);
mOutputTextureId = mOutputViewport.createTexture();
mInputSurfaceTexture = new SurfaceTexture(mOutputTextureId);
getView().queueEvent(new Runnable() {
@ -165,15 +165,13 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
getView().requestRender(); // requestRender is thread-safe.
}
});
//init the default shader effect
mCurrentShaderEffect = new NoFilter();
}
@RendererThread
@Override
public void onSurfaceChanged(GL10 gl, final int width, final int height) {
gl.glViewport(0, 0, width, height);
mCurrentFilter.setSize(width, height);
if (!mDispatched) {
dispatchOnSurfaceAvailable(width, height);
mDispatched = true;
@ -216,7 +214,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
synchronized (mRendererFrameCallbacks) {
// Need to synchronize when iterating the Collections.synchronizedSet
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY, mCurrentShaderEffect);
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY);
}
}
}
@ -284,7 +282,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
public void run() {
mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
callback.onFilterChanged(mCurrentShaderEffect);
callback.onFilterChanged(mCurrentFilter);
}
});
}
@ -317,14 +315,30 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
return new Renderer();
}
public void setShaderEffect(@NonNull Filter shaderEffect){
shaderEffect.setPreviewingViewSize(getView().getWidth(), getView().getHeight());
mCurrentShaderEffect = shaderEffect;
//region Filters
@NonNull
@Override
public Filter getCurrentFilter() {
return mCurrentFilter;
}
mOutputViewport.changeShaderFilter(shaderEffect);
@Override
public void setFilter(@NonNull Filter filter) {
if (hasSurface()) {
filter.setSize(mOutputSurfaceWidth, mOutputSurfaceHeight);
}
mCurrentFilter = filter;
if (mOutputViewport != null) {
mOutputViewport.setFilter(filter);
}
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onFilterChanged(shaderEffect);
// Need to synchronize when iterating the Collections.synchronizedSet
synchronized (mRendererFrameCallbacks) {
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onFilterChanged(filter);
}
}
}
}

@ -4,7 +4,7 @@ import android.graphics.SurfaceTexture;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filter.Filter;
/**
* Callback for renderer frames.
@ -30,7 +30,7 @@ public interface RendererFrameCallback {
* @param scaleY the scaleY (in REF_VIEW) value
*/
@RendererThread
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect);
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY);
/**
* Called on the change of shader filter

@ -13,7 +13,7 @@ import com.otaliastudios.cameraview.overlay.OverlayDrawer;
import com.otaliastudios.cameraview.preview.GlCameraPreview;
import com.otaliastudios.cameraview.preview.RendererFrameCallback;
import com.otaliastudios.cameraview.preview.RendererThread;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.size.Size;
import com.otaliastudios.cameraview.video.encoding.AudioConfig;
import com.otaliastudios.cameraview.video.encoding.AudioMediaEncoder;
@ -102,9 +102,17 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
}
}
@Override
public void onFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter.copy();
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
}
}
@RendererThread
@Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect) {
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY) {
if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) {
LOG.i("Starting the encoder engine.");
@ -160,13 +168,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
// Engine
mEncoderEngine = new MediaEncoderEngine(mResult.file, videoEncoder, audioEncoder,
mResult.maxDuration, mResult.maxSize, SnapshotVideoRecorder.this);
//set current filter
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
}
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, mCurrentFilter);
mEncoderEngine.start();
mResult.rotation = 0; // We will rotate the result instead.
mCurrentState = STATE_RECORDING;
@ -192,15 +194,6 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
}
@Override
public void onFilterChanged(@NonNull Filter filter) {
mCurrentFilter = filter;
if (mEncoderEngine != null) {
mEncoderEngine.notify(TextureMediaEncoder.FILTER_EVENT, filter);
}
}
@Override
public void onEncodingStart() {
dispatchVideoRecordingStart();

@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.filter.Filter;
import com.otaliastudios.cameraview.internal.egl.EglCore;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface;
@ -31,7 +31,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
private EglCore mEglCore;
private EglWindowSurface mWindow;
private EglViewport mViewport;
private Pool<Frame> mFramePool = new Pool<>(100, new Pool.Factory<Frame>() {
private Pool<Frame> mFramePool = new Pool<>(Integer.MAX_VALUE, new Pool.Factory<Frame>() {
@Override
public Frame create() {
return new Frame();
@ -137,7 +137,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
if (event.equals(FILTER_EVENT)) {
Filter filter = (Filter) data;
mViewport.changeShaderFilter(filter);
mViewport.setFilter(filter);
} else if (event.equals(FRAME_EVENT)) {
Frame frame = (Frame) data;
if (frame == null) {
@ -224,7 +224,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
mWindow = null;
}
if (mViewport != null) {
mViewport.release(true);
mViewport.release();
mViewport = null;
}
if (mEglCore != null) {

@ -131,6 +131,8 @@
<attr name="cameraAutoFocusMarker" format="string|reference"/>
<attr name="cameraFilter" format="string|reference"/>
<attr name="cameraUseDeviceOrientation" format="boolean"/>
<attr name="cameraExperimental" format="boolean" />

@ -1,4 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cameraview_default_autofocus_marker">com.otaliastudios.cameraview.markers.DefaultAutoFocusMarker</string>
<string name="cameraview_filter_none">com.otaliastudios.cameraview.filter.NoFilter</string>
<string name="cameraview_filter_autofix">com.otaliastudios.cameraview.filters.AutoFixFilter</string>
<string name="cameraview_filter_black_and_white">com.otaliastudios.cameraview.filters.BlackAndWhiteFilter</string>
<string name="cameraview_filter_brightness">com.otaliastudios.cameraview.filters.BrightnessFilter</string>
<string name="cameraview_filter_contrast">com.otaliastudios.cameraview.filters.ContrastFilter</string>
<string name="cameraview_filter_cross_process">com.otaliastudios.cameraview.filters.CrossProcessFilter</string>
<string name="cameraview_filter_documentary">com.otaliastudios.cameraview.filters.DocumentaryFilter</string>
<string name="cameraview_filter_duotone">com.otaliastudios.cameraview.filters.DuotoneFilter</string>
<string name="cameraview_filter_fill_light">com.otaliastudios.cameraview.filters.FillLightFilter</string>
<string name="cameraview_filter_gamma">com.otaliastudios.cameraview.filters.GammaFilter</string>
<string name="cameraview_filter_grain">com.otaliastudios.cameraview.filters.GrainFilter</string>
<string name="cameraview_filter_grayscale">com.otaliastudios.cameraview.filters.GrayscaleFilter</string>
<string name="cameraview_filter_hue">com.otaliastudios.cameraview.filters.HueFilter</string>
<string name="cameraview_filter_invert_colors">com.otaliastudios.cameraview.filters.InvertColorsFilter</string>
<string name="cameraview_filter_lomoish">com.otaliastudios.cameraview.filters.LomoishFilter</string>
<string name="cameraview_filter_posterize">com.otaliastudios.cameraview.filters.PosterizeFilter</string>
<string name="cameraview_filter_saturation">com.otaliastudios.cameraview.filters.SaturationFilter</string>
<string name="cameraview_filter_sepia">com.otaliastudios.cameraview.filters.SepiaFilter</string>
<string name="cameraview_filter_sharpness">com.otaliastudios.cameraview.filters.SharpnessFilter</string>
<string name="cameraview_filter_temperature">com.otaliastudios.cameraview.filters.TemperatureFilter</string>
<string name="cameraview_filter_tint">com.otaliastudios.cameraview.filters.TintFilter</string>
<string name="cameraview_filter_vignette">com.otaliastudios.cameraview.filters.VignetteFilter</string>
</resources>

@ -27,7 +27,8 @@ import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.controls.Mode;
import com.otaliastudios.cameraview.VideoResult;
import com.otaliastudios.cameraview.controls.Preview;
import com.otaliastudios.cameraview.filters.Filters;
import com.otaliastudios.cameraview.filter.Filters;
import com.otaliastudios.cameraview.filters.BrightnessFilter;
import com.otaliastudios.cameraview.frame.Frame;
import com.otaliastudios.cameraview.frame.FrameProcessor;
@ -36,8 +37,6 @@ import java.io.File;
import java.util.Arrays;
import java.util.List;
import static com.otaliastudios.cameraview.filters.Filters.*;
public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback {
@ -49,7 +48,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
private ViewGroup controlPanel;
private long mCaptureTime;
private Filters mCurrentEffect = NO_FILTER;
private int mCurrentFilter = 0;
private final Filters[] mAllFilters = Filters.values();
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -321,84 +321,17 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
}
}
private void changeCurrentFilter(){
private void changeCurrentFilter() {
if (camera.getPreview() != Preview.GL_SURFACE) {
message("Filters are supported only for GLSurfaceView", true);
message("Filters are supported only when preview is Preview.GL_SURFACE.", true);
return;
}
switch (mCurrentEffect){
case NO_FILTER:
mCurrentEffect = AUTO_FIX_FILTER;
break;
case AUTO_FIX_FILTER:
mCurrentEffect = BLACK_AND_WHITE_FILTER;
break;
case BLACK_AND_WHITE_FILTER:
mCurrentEffect = BRIGHTNESS_FILTER;
break;
case BRIGHTNESS_FILTER:
mCurrentEffect = CONTRAST_FILTER;
break;
case CONTRAST_FILTER:
mCurrentEffect = CROSS_PROCESS_FILTER;
break;
case CROSS_PROCESS_FILTER:
mCurrentEffect = DOCUMENTARY_FILTER;
break;
case DOCUMENTARY_FILTER:
mCurrentEffect = DUO_TONE_COLOR_FILTER;
break;
case DUO_TONE_COLOR_FILTER:
mCurrentEffect = FILL_LIGHT_FILTER;
break;
case FILL_LIGHT_FILTER:
mCurrentEffect = GAMMA_FILTER;
break;
case GAMMA_FILTER:
mCurrentEffect = GRAIN_FILTER;
break;
case GRAIN_FILTER:
mCurrentEffect = GREY_SCALE_FILTER;
break;
case GREY_SCALE_FILTER:
mCurrentEffect = HUE_FILTER;
break;
case HUE_FILTER:
mCurrentEffect = INVERT_COLOR_FILTER;
break;
case INVERT_COLOR_FILTER:
mCurrentEffect = LAMOISH_FILTER;
break;
case LAMOISH_FILTER:
mCurrentEffect = POSTERIZE_FILTER;
break;
case POSTERIZE_FILTER:
mCurrentEffect = SATURATION_FILTER;
break;
case SATURATION_FILTER:
mCurrentEffect = SEPIA_FILTER;
break;
case SEPIA_FILTER:
mCurrentEffect = SHARPNESS_FILTER;
break;
case SHARPNESS_FILTER:
mCurrentEffect = TEMPERATURE_FILTER;
break;
case TEMPERATURE_FILTER:
mCurrentEffect = TINT_FILTER;
break;
case TINT_FILTER:
mCurrentEffect = VIGNETTE_FILTER;
break;
case VIGNETTE_FILTER:
default:
mCurrentEffect = NO_FILTER;
break;
if (mCurrentFilter < mAllFilters.length - 1) {
mCurrentFilter++;
} else {
mCurrentFilter = 0;
}
camera.setFilter(mCurrentEffect);
camera.setFilter(mAllFilters[mCurrentFilter].newInstance());
}
@Override

@ -2,7 +2,7 @@
layout: page
title: "Debugging"
category: docs
order: 13
order: 14
date: 2018-12-20 20:02:38
disqus: 1
---

@ -2,7 +2,7 @@
layout: page
title: "Error Handling"
category: docs
order: 12
order: 13
date: 2018-12-20 20:02:31
disqus: 1
---

@ -4,7 +4,7 @@ title: "More features"
subtitle: "Undocumented features & more"
description: "Undocumented features & more"
category: docs
order: 14
order: 15
date: 2018-12-20 20:41:20
disqus: 1
---

@ -39,7 +39,7 @@ to use all the features available. However, experienced user might prefer a diff
|-------|---------|----|
|`Preview.SURFACE`|A `SurfaceView`|Can be good for battery, but will not work well with dynamic layout changes and similar things. No support for video snapshots.|
|`Preview.TEXTURE`|A `TextureView`|Better. Requires hardware acceleration. No support for video snapshots.|
|`Preview.GL_SURFACE`|A `GLSurfaceView`|Recommended. Supports video snapshots. Might support GL real time filters in the future.|
|`Preview.GL_SURFACE`|A `GLSurfaceView`|Recommended. Supports video snapshots. Supports [overlays](watermarks-and-overlays.html). Supports [real-time filters](filters.html).|
The GL surface, as an extra benefit, has a much more efficient way of capturing picture snapshots,
that avoids OOM errors, rotating the image on the fly, reading EXIF, and other horrible things belonging to v1.

@ -4,7 +4,7 @@ title: "Runtime Permissions"
subtitle: "Permissions and Manifest setup"
description: "Permissions and Manifest setup"
category: docs
order: 11
order: 12
date: 2018-12-20 20:03:03
disqus: 1
---

@ -0,0 +1,102 @@
---
layout: page
title: "Real-time Filters"
subtitle: "Apply filters to preview and snapshots"
description: "Apply filters to preview and snapshots"
category: docs
order: 11
date: 2019-08-06 17:10:17
disqus: 1
---
Starting from version `2.1.0`, CameraView experimentally supports real-time filters that can modify
the camera frames before they are shown and recorded. Just like [overlays](watermarks-and-overlays.html),
these filters are applied to the preview and to any [picture or video snapshots](capturing-media.html).
Conditions:
- you must set the experimental flag: `app:cameraExperimental="true"`
- you must use `Preview.GL_SURFACE` as a preview
### Simple usage
```xml
<com.otaliastudios.cameraview.CameraView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cameraFilter="@string/cameraview_filter_none"/>
```
Real-time filters are applied at creation time, through the `app:cameraFilter` XML attribute,
or anytime during the camera lifecycle using `cameraView.setFilter()`.
We offers a reasonable amount of filters through the `Filters` class, for example:
```java
cameraView.setFilter(Filters.BLACK_AND_WHITE.newInstance());
cameraView.setFilter(Filters.VIGNETTE.newInstance());
cameraView.setFilter(Filters.SEPIA.newInstance());
```
All of the filters stored in the `Filters` class have an XML string resource that you can use
to quickly setup the camera view. For example, `Filters.BLACK_AND_WHITE` can be set by using
`app:cameraFilter="@string/cameraview_filter_black_and_white`.
The default filter is called `NoFilter` (`Filters.NONE`) and can be used to clear up any other
filter that was previously set and return back to normal.
|Filter class|Filters value|XML resource value|
|------------|-------------|---------------------|
|`NoFilter`|`Filters.NONE`|`@string/cameraview_filter_none`|
|`AutoFixFilter`|`Filters.AUTO_FIX`|`@string/cameraview_filter_autofix`|
|`BlackAndWhiteFilter`|`Filters.BLACK_AND_WHITE`|`@string/cameraview_filter_black_and_white`|
|`BrightnessFilter`|`Filters.BRIGHTNESS`|`@string/cameraview_filter_brightness`|
|`ContrastFilter`|`Filters.CONTRAST`|`@string/cameraview_filter_contrast`|
|`CrossProcessFilter`|`Filters.CROSS_PROCESS`|`@string/cameraview_filter_cross_process`|
|`DocumentaryFilter`|`Filters.DOCUMENTARY`|`@string/cameraview_filter_documentary`|
|`DuotoneFilter`|`Filters.DUOTONE`|`@string/cameraview_filter_duotone`|
|`FillLightFilter`|`Filters.FILL_LIGHT`|`@string/cameraview_filter_fill_light`|
|`GammaFilter`|`Filters.GAMMA`|`@string/cameraview_filter_gamma`|
|`GrainFilter`|`Filters.GRAIN`|`@string/cameraview_filter_grain`|
|`GrayscaleFilter`|`Filters.GRAYSCALE`|`@string/cameraview_filter_grayscale`|
|`HueFilter`|`Filters.HUE`|`@string/cameraview_filter_hue`|
|`InvertColorsFilter`|`Filters.INVERT_COLORS`|`@string/cameraview_filter_invert_colors`|
|`LomoishFilter`|`Filters.LOMOISH`|`@string/cameraview_filter_lomoish`|
|`PosterizeFilter`|`Filters.POSTERIZE`|`@string/cameraview_filter_posterize`|
|`SaturationFilter`|`Filters.SATURATION`|`@string/cameraview_filter_saturation`|
|`SepiaFilter`|`Filters.SEPIA`|`@string/cameraview_filter_sepia`|
|`SharpnessFilter`|`Filters.SHARPNESS`|`@string/cameraview_filter_sharpness`|
|`TemperatureFilter`|`Filters.TEMPERATURE`|`@string/cameraview_filter_temperature`|
|`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
accept two colors to apply the duotone effect.
```java
duotoneFilter.setFirstColor(Color.RED);
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.
### Advanced usage
Advanced users with OpenGL experience can create their own filters by implementing the `Filter` interface
and passing in a fragment shader and a vertex shader that will be used for drawing.
We recommend:
- Subclassing `BaseFilter` instead of implementing `Filter`, since that takes care of most of the work
- If accepting parameters, implementing `OneParameterFilter` or `TwoParameterFilter` as well
Most of all, the best way of learning is by looking at the current filters implementations in the
`com.otaliastudios.cameraview.filters` package.
### Related APIs
|Method|Description|
|------|-----------|
|`setFilter(Filter)`|Sets a new real-time filter.|
|`getFilter()`|Returns the current real-time filter.|

@ -14,6 +14,7 @@ addressing most of the common issues and needs, and still leaving you with flexi
- Fast & reliable
- Gestures support [[docs]](docs/gestures.html)
- Real-time filters [[docs]](docs/filters.html)
- Camera1 or Camera2 powered engine [[docs]](docs/previews.html)
- Frame processing support [[docs]](docs/frame-processing.html)
- Watermarks & animated overlays [[docs]](docs/watermarks-and-overlays.html)

Loading…
Cancel
Save