From facd26f11d3ecf62ed0aec9c76d1a6079e2f9a90 Mon Sep 17 00:00:00 2001 From: Suneet Agrawal Date: Mon, 5 Aug 2019 18:50:27 +0530 Subject: [PATCH] Feature Live Filters using Shaders (#527) * added shader effects * added setting shaders * intermediate commit * added shader implementation * refatored code * implemented initial effects * modified few filters * modified all effects * implemented shader while taking picture and video * updated all effects * modified all filters range to 0.0f-1.0f * added control to filter * added shader effects * added setting shaders * intermediate commit * added shader implementation * refatored code * implemented initial effects * modified few filters * modified all effects * implemented shader while taking picture and video * updated all effects * modified all filters range to 0.0f-1.0f * added control to filter * modified shaders and removed the glsurfaceview dependency * changed folder structure * changed allignmen and filters * added annotations * code refacor * changed effect name to filters * changed filter implementation for image and video * fixed for travis build --- .../otaliastudios/cameraview/CameraView.java | 60 ++++--- .../cameraview/filters/AutoFixFilter.java | 96 +++++++++++ .../filters/BlackAndWhiteFilter.java | 35 ++++ .../cameraview/filters/BrightnessFilter.java | 56 +++++++ .../cameraview/filters/ContrastFilter.java | 55 +++++++ .../filters/CrossProcessFilter.java | 44 +++++ .../cameraview/filters/CustomFilter.java | 27 ++++ .../cameraview/filters/DocumentaryFilter.java | 96 +++++++++++ .../cameraview/filters/DuotoneFilter.java | 85 ++++++++++ .../cameraview/filters/FillLightFilter.java | 73 +++++++++ .../cameraview/filters/Filter.java | 128 +++++++++++++++ .../cameraview/filters/Filters.java | 124 ++++++++++++++ .../cameraview/filters/GammaFilter.java | 59 +++++++ .../cameraview/filters/GrainFilter.java | 98 +++++++++++ .../cameraview/filters/GreyScaleFilter.java | 31 ++++ .../cameraview/filters/HueFilter.java | 72 +++++++++ .../filters/InvertColorsFilter.java | 33 ++++ .../cameraview/filters/LamoishFilter.java | 143 ++++++++++++++++ .../cameraview/filters/NoFilter.java | 12 ++ .../cameraview/filters/PosterizeFilter.java | 32 ++++ .../cameraview/filters/SaturationFilter.java | 102 ++++++++++++ .../cameraview/filters/SepiaFilter.java | 48 ++++++ .../cameraview/filters/SharpnessFilter.java | 75 +++++++++ .../cameraview/filters/TemperatureFilter.java | 61 +++++++ .../cameraview/filters/TintFilter.java | 66 ++++++++ .../cameraview/filters/VignetteFilter.java | 99 ++++++++++++ .../cameraview/internal/egl/EglViewport.java | 76 ++++----- .../picture/SnapshotGlPictureRecorder.java | 17 +- .../cameraview/preview/GlCameraPreview.java | 21 ++- .../preview/RendererFrameCallback.java | 12 +- .../video/SnapshotVideoRecorder.java | 21 ++- .../video/encoding/TextureMediaEncoder.java | 153 +++++++++--------- .../cameraview/demo/CameraActivity.java | 92 ++++++++++- demo/src/main/res/drawable/ic_filters.xml | 6 + demo/src/main/res/layout/activity_camera.xml | 34 ++-- 35 files changed, 2086 insertions(+), 156 deletions(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java create mode 100644 demo/src/main/res/drawable/ic_filters.xml diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 38b2b2bb..b76543a6 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -4,12 +4,6 @@ import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; - -import androidx.annotation.VisibleForTesting; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.OnLifecycleEvent; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageInfo; @@ -22,36 +16,43 @@ import android.media.MediaActionSound; import android.os.Build; import android.os.Handler; import android.os.Looper; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.OnLifecycleEvent; + import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Control; import com.otaliastudios.cameraview.controls.ControlParser; import com.otaliastudios.cameraview.controls.Engine; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; -import com.otaliastudios.cameraview.engine.Camera2Engine; -import com.otaliastudios.cameraview.engine.offset.Reference; -import com.otaliastudios.cameraview.markers.MarkerLayout; -import com.otaliastudios.cameraview.engine.Camera1Engine; -import com.otaliastudios.cameraview.engine.CameraEngine; -import com.otaliastudios.cameraview.frame.Frame; -import com.otaliastudios.cameraview.frame.FrameProcessor; -import com.otaliastudios.cameraview.gesture.Gesture; -import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.controls.Grid; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.Preview; import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.WhiteBalance; +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.frame.Frame; +import com.otaliastudios.cameraview.frame.FrameProcessor; +import com.otaliastudios.cameraview.gesture.Gesture; +import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.gesture.GestureFinder; import com.otaliastudios.cameraview.gesture.GestureParser; import com.otaliastudios.cameraview.gesture.PinchGestureFinder; @@ -61,6 +62,9 @@ import com.otaliastudios.cameraview.internal.GridLinesLayout; import com.otaliastudios.cameraview.internal.utils.CropHelper; import com.otaliastudios.cameraview.internal.utils.OrientationHelper; import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.markers.AutoFocusMarker; +import com.otaliastudios.cameraview.markers.AutoFocusTrigger; +import com.otaliastudios.cameraview.markers.MarkerLayout; import com.otaliastudios.cameraview.markers.MarkerParser; import com.otaliastudios.cameraview.overlay.OverlayLayout; import com.otaliastudios.cameraview.preview.CameraPreview; @@ -72,8 +76,6 @@ import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.size.SizeSelector; import com.otaliastudios.cameraview.size.SizeSelectorParser; import com.otaliastudios.cameraview.size.SizeSelectors; -import com.otaliastudios.cameraview.markers.AutoFocusMarker; -import com.otaliastudios.cameraview.markers.AutoFocusTrigger; import java.io.File; import java.util.ArrayList; @@ -84,7 +86,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.UNSPECIFIED; - import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; /** @@ -572,6 +573,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver { LOG.i("onTouchEvent", "tap!"); onGesture(mTapGestureFinder, options); } + return true; } @@ -2129,4 +2131,20 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } //endregion + + //region Effects + + public void setFilter(@NonNull Filters filter) { + setFilter(filter.newInstance()); + } + + public void setFilter(@NonNull Filter filter) { + if (mCameraPreview instanceof GlCameraPreview) { + ((GlCameraPreview) mCameraPreview).setShaderEffect(filter); + } else { + LOG.w("setFilter", "setFilter is supported only for GLSurfaceView"); + } + } + + //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java new file mode 100644 index 00000000..92549229 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java @@ -0,0 +1,96 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Attempts to auto-fix the preview based on histogram equalization. + */ +public class AutoFixFilter extends Filter { + + private float scale = 1.0f; + + /** + * Initialize Effect + */ + public AutoFixFilter() { + } + + 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; + + this.scale = scale; + } + + @NonNull + @Override + public String getFragmentShader() { + + 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"; + + return shader; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java new file mode 100644 index 00000000..c7316ea0 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java @@ -0,0 +1,35 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Converts the preview into black and white colors + */ +public class BlackAndWhiteFilter extends Filter { + + + /** + * Initialize effect + */ + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java new file mode 100644 index 00000000..f2718b8c --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java @@ -0,0 +1,56 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Adjusts the brightness of the preview. + */ +public class BrightnessFilter extends Filter { + private float brightnessValue = 2.0f; + + /** + * Initialize Effect + */ + public BrightnessFilter() { + } + + /** + * setBrightnessValue + * + * @param brightnessvalue Range should be between 0.0- 1.0 with 0.0 being normal. + */ + public void setBrightnessValue(float brightnessvalue) { + if (brightnessvalue < 0.0f) + brightnessvalue = 0.0f; + else if (brightnessvalue > 1.0f) + brightnessvalue = 1.0f; + + //since the shader excepts a range of 1.0 - 2.0 + // will add the 1.0 to every value + this.brightnessValue = 1.0f + brightnessvalue; + } + + 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; + } + + @NonNull + @Override + public String getFragmentShader() { + + 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; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java new file mode 100644 index 00000000..03034f46 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java @@ -0,0 +1,55 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Adjusts the contrast of the preview. + */ +public class ContrastFilter extends Filter { + private float contrast = 2.0f; + + /** + * Initialize Effect + */ + public ContrastFilter() { + } + + /** + * setContrast + * + * @param contrast Range should be between 0.0- 1.0 with 0.0 being normal. + */ + public void setContrast(float contrast) { + if (contrast < 0.0f) + contrast = 0.0f; + else if (contrast > 1.0f) + contrast = 1.0f; + + //since the shader excepts a range of 1.0 - 2.0 + //will add the 1.0 to every value + this.contrast = contrast + 1.0f; + } + + 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; + } + + @NonNull + @Override + public String getFragmentShader() { + + 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; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java new file mode 100644 index 00000000..883a15bb --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java @@ -0,0 +1,44 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Applies a cross process effect on preview, in which the red and green channels + * are enhanced while the blue channel is restricted. + */ +public class CrossProcessFilter extends Filter { + + /** + * Initialize Effect + */ + 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; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java new file mode 100644 index 00000000..f1b53220 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java @@ -0,0 +1,27 @@ +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; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java new file mode 100644 index 00000000..c81fcac4 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java @@ -0,0 +1,96 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import java.util.Date; +import java.util.Random; + +/** + * Applies black and white documentary style effect on preview. + */ +public class DocumentaryFilter extends Filter { + private Random mRandom; + + public DocumentaryFilter() { + 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 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java new file mode 100644 index 00000000..de34f7d5 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java @@ -0,0 +1,85 @@ +package com.otaliastudios.cameraview.filters; + +import android.graphics.Color; + +import androidx.annotation.NonNull; + +/** + * Representation of preview using only two color tones. + */ +public class DuotoneFilter extends Filter { + // Default values + private int mFirstColor = Color.MAGENTA; + private int mSecondColor = Color.YELLOW; + + /** + * Initialize effect + */ + public DuotoneFilter() { + } + + /** + * setDuoToneColors + * + * @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. + */ + public void setDuoToneColors(int firstColor, int secondColor) { + this.mFirstColor = firstColor; + this.mSecondColor = secondColor; + } + + public int getFirstColor() { + return mFirstColor; + } + + public int getSecondColor() { + return mSecondColor; + } + + @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, + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java new file mode 100644 index 00000000..8d807137 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java @@ -0,0 +1,73 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Applies back-light filling to the preview. + */ +public class FillLightFilter extends Filter { + private float strength = 0.5f; + + /** + * Initialize Effect + */ + public FillLightFilter() { + } + + /** + * setStrength + * + * @param strength Float, between 0.0 and 1.0 where 0.0 means no change. + */ + public void setStrength(float strength) { + if (strength < 0.0f) + strength = 0f; + else if (strength > 1.0f) + strength = 1f; + + this.strength = strength; + } + + public float getStrength() { + return strength; + } + + @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; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java new file mode 100644 index 00000000..da11521e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java @@ -0,0 +1,128 @@ +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. + *

+ * 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. + *

+ * 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. + *

+ * 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() + *

+ * 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(); + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java new file mode 100644 index 00000000..26bf6d82 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java @@ -0,0 +1,124 @@ +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; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java new file mode 100644 index 00000000..aa17715a --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java @@ -0,0 +1,59 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Apply Gamma Effect on preview being played + */ +public class GammaFilter extends Filter { + private float gammaValue = 2.0f; + + /** + * Initialize Effect + */ + public GammaFilter() { + } + + /** + * setGammaValue + * + * @param gammaValue Range should be between 0.0 - 1.0 with 0.5 being normal. + */ + public void setGammaValue(float gammaValue) { + if (gammaValue < 0.0f) + gammaValue = 0.0f; + else if (gammaValue > 1.0f) + gammaValue = 1.0f; + + //since the shader excepts a range of 0.0 - 2.0 + //will multiply the 2.0 to every value + this.gammaValue = gammaValue * 2.0f; + } + + 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; + } + + @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 gamma=" + gammaValue + ";\n" + + + "void main() {\n" + + + "vec4 textureColor = texture2D(sTexture, vTextureCoord);\n" + + "gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);\n" + + + "}\n"; + + return shader; + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java new file mode 100644 index 00000000..e7292ac7 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java @@ -0,0 +1,98 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +import java.util.Date; +import java.util.Random; + +/** + * Applies film grain effect to preview. + */ +public class GrainFilter extends Filter { + private float strength = 0.5f; + private Random mRandom; + + /** + * Initialize Effect + */ + public GrainFilter() { + mRandom = new Random(new Date().getTime()); + } + + /** + * setDistortionStrength + * + * @param strength Float, between 0.0f and 1.0. Zero means no distortion, while 1 + * indicates the maximum amount of adjustment. + */ + public void setDistortionStrength(float strength) { + if (strength < 0.0f) + strength = 0.0f; + else if (strength > 1.0f) + strength = 1.0f; + this.strength = strength; + } + + public float getStrength() { + return strength; + } + + @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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java new file mode 100644 index 00000000..a5872562 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java @@ -0,0 +1,31 @@ +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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java new file mode 100644 index 00000000..7a74cd2f --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java @@ -0,0 +1,72 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Apply Hue effect on the preview + */ +public class HueFilter extends Filter { + float hueValue = 0.0f; + + /** + * Initialize Effect + */ + public HueFilter() { + } + + /** + * Hue value chart - https://cloud.githubusercontent.com/assets/2201511/21810115/b99ac22a-d74a-11e6-9f6c-ef74d15c88c7.jpg" + * + * @param hueDegrees Range of value should be between 0 to 360 degrees as described in the image above + */ + 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; + } + + @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" + + + "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"; + + return shader; + } +} \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java new file mode 100644 index 00000000..638819d8 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java @@ -0,0 +1,33 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Inverts the preview colors. This can also be known as negative Effect. + */ +public class InvertColorsFilter extends Filter { + /** + * Initialize Effect + */ + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java new file mode 100644 index 00000000..b4fce780 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java @@ -0,0 +1,143 @@ +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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java new file mode 100644 index 00000000..5f76028d --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java @@ -0,0 +1,12 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +public class NoFilter extends Filter { + + @NonNull + @Override + public String getFragmentShader() { + return mFragmentShader; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java new file mode 100644 index 00000000..df70697e --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java @@ -0,0 +1,32 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Applies Posterization effect to Preview. + */ +public class PosterizeFilter extends Filter { + /** + * Initialize Effect + */ + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java new file mode 100644 index 00000000..ddb02931 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java @@ -0,0 +1,102 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Adjusts color saturation of preview. + */ +public class SaturationFilter extends Filter { + private float scale = 1.0f; + + /** + * Initialize Effect + */ + public SaturationFilter() { + } + + /** + * @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 + */ + public void setSaturationValue(float value) { + if (value < 0.0f) + value = 0.0f; + else if (value > 1.0f) + value = 1.0f; + + //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; + } + + 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; + } + + @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 shader; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java new file mode 100644 index 00000000..c312bee2 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java @@ -0,0 +1,48 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Converts preview to Sepia tone. + */ +public class SepiaFilter extends Filter { + /** + * Initialize Effect + */ + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java new file mode 100644 index 00000000..8984a12d --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java @@ -0,0 +1,75 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Sharpens the preview. + */ +public class SharpnessFilter extends Filter { + private float scale = 0.5f; + + /** + * Initialize Effect + */ + public SharpnessFilter() { + } + + /** + * @param value Float, between 0 and 1. 0 means no change. + */ + public void setSharpnessValue(float value) { + if (value < 0.0f) + value = 0.0f; + else if (value > 1.0f) + value = 1.0f; + + this.scale = value; + } + + public float getSharpnessValue() { + return scale; + } + + @NonNull + @Override + public String getFragmentShader() { + + 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; + + } + +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java new file mode 100644 index 00000000..fbcf5710 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java @@ -0,0 +1,61 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + +/** + * Adjusts color temperature of the preview. + */ +public class TemperatureFilter extends Filter { + private float scale = 0f; + + /** + * Initialize Effect + */ + public TemperatureFilter() { + } + + /** + * @param scale Float, between 0 and 1, with 0 indicating cool, and 1 + * indicating warm. A value of of 0.5 indicates no change. + */ + public void setTemperatureScale(float scale) { + if (scale < 0.0f) + scale = 0.0f; + else if (scale > 1.0f) + scale = 1.0f; + this.scale = scale; + } + + public float getTemperatureScale() { + return scale; + } + + @NonNull + @Override + public String getFragmentShader() { + + 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java new file mode 100644 index 00000000..a7a9cd08 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java @@ -0,0 +1,66 @@ +package com.otaliastudios.cameraview.filters; + +import android.graphics.Color; + +import androidx.annotation.NonNull; + + +/** + * Tints the preview with specified color.. + */ +public class TintFilter extends Filter { + private int mTint = 0xFFFF0000; + + /** + * Initialize Effect + */ + public TintFilter() { + } + + public void setTintColor(int color) { + this.mTint = color; + } + + public int getTintColor() { + return mTint; + } + + @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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java new file mode 100644 index 00000000..3d515e48 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java @@ -0,0 +1,99 @@ +package com.otaliastudios.cameraview.filters; + +import androidx.annotation.NonNull; + + +/** + * Applies lomo-camera style effect to your preview. + */ +public class VignetteFilter extends Filter { + private float mScale = 0.85f; + private float mShade = 0.5f; + + /** + * Initialize Effect + */ + public VignetteFilter() { + } + + /** + * setVignetteEffectScale + * + * @param scale Float, between 0.0 and 1. 0 + */ + public void setVignetteEffectScale(float scale) { + if (scale < 0.0f) + scale = 0.0f; + else if (scale > 1.0f) + scale = 1.0f; + this.mScale = scale; + } + + /** + * setVignetteEffectShade + * + * @param shade Float, between 0.0 and 1. 0 + */ + public void setVignetteEffectShade(float shade) { + if (shade < 0.0f) + shade = 0.0f; + else if (shade > 1.0f) + shade = 1.0f; + this.mShade = shade; + } + + @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; + + String scaleString[] = new String[2]; + + 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"; + + // 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; + + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java index 7a67e8ae..d1be7c14 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java @@ -4,7 +4,11 @@ package com.otaliastudios.cameraview.internal.egl; import android.opengl.GLES11Ext; 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 java.nio.FloatBuffer; @@ -15,28 +19,6 @@ public class EglViewport extends EglElement { private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName()); - // Simple vertex shader. - private static final String SIMPLE_VERTEX_SHADER = - "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"; - - // Simple fragment shader for use with external 2D textures - private static final String SIMPLE_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" + - " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + - "}\n"; - // 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. @@ -76,25 +58,30 @@ public class EglViewport extends EglElement { // private int muTexOffsetLoc; // Used for filtering // private int muColorAdjustLoc; // Used for filtering + private Filter mShaderEffect; + + private boolean mIsShaderChanged = false; + public EglViewport() { - this(GLES20.GL_TEXTURE0, GLES11Ext.GL_TEXTURE_EXTERNAL_OES); - } + mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; + mTextureUnit = GLES20.GL_TEXTURE0; - private EglViewport(int textureUnit, int textureTarget) { - mTextureUnit = textureUnit; - mTextureTarget = textureTarget; - mProgramHandle = createProgram(SIMPLE_VERTEX_SHADER, SIMPLE_FRAGMENT_SHADER); - maPositionLocation = GLES20.glGetAttribLocation(mProgramHandle, "aPosition"); - checkLocation(maPositionLocation, "aPosition"); - maTextureCoordLocation = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord"); - checkLocation(maTextureCoordLocation, "aTextureCoord"); - muMVPMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, "uMVPMatrix"); - checkLocation(muMVPMatrixLocation, "uMVPMatrix"); - muTexMatrixLocation = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix"); - checkLocation(muTexMatrixLocation, "uTexMatrix"); - - // Stuff from Drawable2d.FULL_RECTANGLE + //init the default shader effect + mShaderEffect = new NoFilter(); + initProgram(); + } + 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 void release(boolean doEglCleanup) { @@ -125,6 +112,13 @@ public class EglViewport extends EglElement { return texId; } + public void changeShaderFilter(@NonNull Filter shaderEffect){ + this.mShaderEffect = shaderEffect; + mIsShaderChanged = true; + } + + + public void drawFrame(int textureId, float[] textureMatrix) { drawFrame(textureId, textureMatrix, mVertexCoordinatesArray, @@ -147,6 +141,12 @@ public class EglViewport extends EglElement { private void drawFrame(int textureId, float[] textureMatrix, FloatBuffer vertexBuffer, FloatBuffer texBuffer) { + + if (mIsShaderChanged){ + initProgram(); + mIsShaderChanged = false; + } + check("draw start"); // Select the program. diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java index b1e286af..9e88c955 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java @@ -2,9 +2,6 @@ package com.otaliastudios.cameraview.picture; import android.annotation.TargetApi; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.opengl.EGL14; @@ -14,7 +11,6 @@ import android.os.Build; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.internal.Issue514Workaround; import com.otaliastudios.cameraview.internal.egl.EglBaseSurface; import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.controls.Facing; @@ -30,6 +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.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; @@ -100,9 +97,15 @@ public class SnapshotGlPictureRecorder extends PictureRecorder { @RendererThread @Override - public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY) { + public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, Filter shaderEffect) { mPreview.removeRendererFrameCallback(this); - SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY); + SnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture, scaleX, scaleY, shaderEffect); + } + + @Override + public void + onFilterChanged(@NonNull Filter filter) { + mViewport.changeShaderFilter(filter); } }); } @@ -148,7 +151,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) { + private void onRendererFrame(final @NonNull SurfaceTexture surfaceTexture, final float scaleX, final float scaleY, @NonNull Filter filter) { // 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. diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java index 525c579d..28142c89 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java @@ -15,6 +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.size.AspectRatio; import java.util.Collections; @@ -68,6 +70,8 @@ public class GlCameraPreview extends CameraPreview { private static final CameraLogger LOG = CameraLogger.create(TAG); public final static String FRAME_EVENT = "frame"; + public final static String FILTER_EVENT = "filter"; private int mTransformRotation; private EglCore mEglCore; @@ -132,80 +134,85 @@ public class TextureMediaEncoder extends VideoMediaEncoder { @EncoderThread @Override protected void onEvent(@NonNull String event, @Nullable Object data) { - if (!event.equals(FRAME_EVENT)) return; - Frame frame = (Frame) data; - if (frame == null) { - throw new IllegalArgumentException("Got null frame for FRAME_EVENT."); - } - if (!shouldRenderFrame(frame.timestampUs())) { - mFramePool.recycle(frame); - return; - } - // Notify we're got the first frame and its absolute time. - if (mFrameNumber == 1) { - notifyFirstFrameMillis(frame.timestampMillis); - } + if (event.equals(FILTER_EVENT)) { + Filter filter = (Filter) data; + mViewport.changeShaderFilter(filter); + } else if (event.equals(FRAME_EVENT)) { + Frame frame = (Frame) data; + if (frame == null) { + throw new IllegalArgumentException("Got null frame for FRAME_EVENT."); + } + if (!shouldRenderFrame(frame.timestampUs())) { + mFramePool.recycle(frame); + return; + } - // Notify we have reached the max length value. - if (mFirstTimeUs == Long.MIN_VALUE) mFirstTimeUs = frame.timestampUs(); - if (!hasReachedMaxLength()) { - boolean didReachMaxLength = (frame.timestampUs() - mFirstTimeUs) > getMaxLengthMillis() * 1000L; - if (didReachMaxLength) { - LOG.w("onEvent -", - "frameNumber:", mFrameNumber, - "timestampUs:", frame.timestampUs(), - "firstTimeUs:", mFirstTimeUs, - "- reached max length! deltaUs:", frame.timestampUs() - mFirstTimeUs); - notifyMaxLengthReached(); + // Notify we're got the first frame and its absolute time. + if (mFrameNumber == 1) { + notifyFirstFrameMillis(frame.timestampMillis); } - } - // First, drain any previous data. - LOG.i("onEvent -", - "frameNumber:", mFrameNumber, - "timestampUs:", frame.timestampUs(), - "- draining."); - drainOutput(false); - - // Then draw on the surface. - LOG.i("onEvent -", - "frameNumber:", mFrameNumber, - "timestampUs:", frame.timestampUs(), - "- rendering."); - - // 1. We must scale this matrix like GlCameraPreview does, because it might have some cropping. - // Scaling takes place with respect to the (0, 0, 0) point, so we must apply a Translation to compensate. - float[] transform = frame.transform; - float scaleX = mConfig.scaleX; - float scaleY = mConfig.scaleY; - float scaleTranslX = (1F - scaleX) / 2F; - float scaleTranslY = (1F - scaleY) / 2F; - Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0); - Matrix.scaleM(transform, 0, scaleX, scaleY, 1); - - // 2. We also must rotate this matrix. In GlCameraPreview it is not needed because it is a live - // stream, but the output video, must be correctly rotated based on the device rotation at the moment. - // Rotation also takes place with respect to the origin (the Z axis), so we must - // translate to origin, rotate, then back to where we were. - Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); - Matrix.rotateM(transform, 0, mTransformRotation, 0, 0, 1); - Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); - - // 3. Do the same for overlays with their own rotation. - if (mConfig.hasOverlay()) { - mConfig.overlayDrawer.draw(mConfig.overlayTarget); - Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, 0.5F, 0.5F, 0); - Matrix.rotateM(mConfig.overlayDrawer.getTransform(), 0, mConfig.overlayRotation, 0, 0, 1); - Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, -0.5F, -0.5F, 0); - } - mViewport.drawFrame(mConfig.textureId, transform); - if (mConfig.hasOverlay()) { - mConfig.overlayDrawer.render(); + // Notify we have reached the max length value. + if (mFirstTimeUs == Long.MIN_VALUE) mFirstTimeUs = frame.timestampUs(); + if (!hasReachedMaxLength()) { + boolean didReachMaxLength = (frame.timestampUs() - mFirstTimeUs) > getMaxLengthMillis() * 1000L; + if (didReachMaxLength) { + LOG.w("onEvent -", + "frameNumber:", mFrameNumber, + "timestampUs:", frame.timestampUs(), + "firstTimeUs:", mFirstTimeUs, + "- reached max length! deltaUs:", frame.timestampUs() - mFirstTimeUs); + notifyMaxLengthReached(); + } + } + + // First, drain any previous data. + LOG.i("onEvent -", + "frameNumber:", mFrameNumber, + "timestampUs:", frame.timestampUs(), + "- draining."); + drainOutput(false); + + // Then draw on the surface. + LOG.i("onEvent -", + "frameNumber:", mFrameNumber, + "timestampUs:", frame.timestampUs(), + "- rendering."); + + // 1. We must scale this matrix like GlCameraPreview does, because it might have some cropping. + // Scaling takes place with respect to the (0, 0, 0) point, so we must apply a Translation to compensate. + float[] transform = frame.transform; + float scaleX = mConfig.scaleX; + float scaleY = mConfig.scaleY; + float scaleTranslX = (1F - scaleX) / 2F; + float scaleTranslY = (1F - scaleY) / 2F; + Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0); + Matrix.scaleM(transform, 0, scaleX, scaleY, 1); + + // 2. We also must rotate this matrix. In GlCameraPreview it is not needed because it is a live + // stream, but the output video, must be correctly rotated based on the device rotation at the moment. + // Rotation also takes place with respect to the origin (the Z axis), so we must + // translate to origin, rotate, then back to where we were. + Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); + Matrix.rotateM(transform, 0, mTransformRotation, 0, 0, 1); + Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); + + // 3. Do the same for overlays with their own rotation. + if (mConfig.hasOverlay()) { + mConfig.overlayDrawer.draw(mConfig.overlayTarget); + Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, 0.5F, 0.5F, 0); + Matrix.rotateM(mConfig.overlayDrawer.getTransform(), 0, mConfig.overlayRotation, 0, 0, 1); + Matrix.translateM(mConfig.overlayDrawer.getTransform(), 0, -0.5F, -0.5F, 0); + } + mViewport.drawFrame(mConfig.textureId, transform); + if (mConfig.hasOverlay()) { + mConfig.overlayDrawer.render(); + } + mWindow.setPresentationTime(frame.timestampNanos); + mWindow.swapBuffers(); + mFramePool.recycle(frame); } - mWindow.setPresentationTime(frame.timestampNanos); - mWindow.swapBuffers(); - mFramePool.recycle(frame); } @Override diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index 0cb652cd..9e3880c9 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -1,8 +1,6 @@ package com.otaliastudios.cameraview.demo; -import android.animation.Animator; import android.animation.ValueAnimator; -import android.annotation.SuppressLint; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -14,13 +12,10 @@ import android.os.Bundle; import androidx.annotation.NonNull; import com.google.android.material.bottomsheet.BottomSheetBehavior; import androidx.appcompat.app.AppCompatActivity; -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.TextView; import android.widget.Toast; import com.otaliastudios.cameraview.CameraException; @@ -32,6 +27,7 @@ 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.frame.Frame; import com.otaliastudios.cameraview.frame.FrameProcessor; @@ -40,6 +36,8 @@ 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 { @@ -51,6 +49,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis private ViewGroup controlPanel; private long mCaptureTime; + private Filters mCurrentEffect = NO_FILTER; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -95,6 +95,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis findViewById(R.id.captureVideo).setOnClickListener(this); findViewById(R.id.captureVideoSnapshot).setOnClickListener(this); findViewById(R.id.toggleCamera).setOnClickListener(this); + findViewById(R.id.changeFilter).setOnClickListener(this); controlPanel = findViewById(R.id.controls); ViewGroup group = (ViewGroup) controlPanel.getChildAt(0); @@ -243,6 +244,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis case R.id.captureVideo: captureVideo(); break; case R.id.captureVideoSnapshot: captureVideoSnapshot(); break; case R.id.toggleCamera: toggleCamera(); break; + case R.id.changeFilter: changeCurrentFilter(); break; } } @@ -319,6 +321,86 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis } } + private void changeCurrentFilter(){ + if (camera.getPreview() != Preview.GL_SURFACE) { + message("Filters are supported only for GLSurfaceView", 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; + } + + camera.setFilter(mCurrentEffect); + } + @Override public boolean onValueChanged(@NonNull Option option, @NonNull T value, @NonNull String name) { if ((option instanceof Option.Width || option instanceof Option.Height)) { diff --git a/demo/src/main/res/drawable/ic_filters.xml b/demo/src/main/res/drawable/ic_filters.xml new file mode 100644 index 00000000..c807bb6d --- /dev/null +++ b/demo/src/main/res/drawable/ic_filters.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index 9e58c634..f02ec291 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -44,15 +44,31 @@ - + + + + + +