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
pull/541/head
Suneet Agrawal 6 years ago committed by Mattia Iavarone
parent ab48a33249
commit facd26f11d
  1. 60
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 96
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java
  3. 35
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/BlackAndWhiteFilter.java
  4. 56
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java
  5. 55
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java
  6. 44
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/CrossProcessFilter.java
  7. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/CustomFilter.java
  8. 96
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java
  9. 85
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java
  10. 73
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java
  11. 128
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filter.java
  12. 124
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/Filters.java
  13. 59
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java
  14. 98
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java
  15. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/GreyScaleFilter.java
  16. 72
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java
  17. 33
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/InvertColorsFilter.java
  18. 143
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/LamoishFilter.java
  19. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/NoFilter.java
  20. 32
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/PosterizeFilter.java
  21. 102
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java
  22. 48
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SepiaFilter.java
  23. 75
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java
  24. 61
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java
  25. 66
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java
  26. 99
      cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java
  27. 76
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java
  28. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java
  29. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java
  30. 12
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/RendererFrameCallback.java
  31. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  32. 153
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/TextureMediaEncoder.java
  33. 92
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  34. 6
      demo/src/main/res/drawable/ic_filters.xml
  35. 34
      demo/src/main/res/layout/activity_camera.xml

@ -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
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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.
* <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();
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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.

@ -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.

@ -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<GLSurfaceView, SurfaceTexture
@VisibleForTesting float mCropScaleY = 1F;
private View mRootView;
private Filter mCurrentShaderEffect;
public GlCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) {
super(context, parent);
}
@ -161,6 +165,9 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
getView().requestRender(); // requestRender is thread-safe.
}
});
//init the default shader effect
mCurrentShaderEffect = new NoFilter();
}
@RendererThread
@ -209,7 +216,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);
callback.onRendererFrame(mInputSurfaceTexture, mCropScaleX, mCropScaleY, mCurrentShaderEffect);
}
}
}
@ -277,6 +284,7 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
public void run() {
mRendererFrameCallbacks.add(callback);
if (mOutputTextureId != 0) callback.onRendererTextureCreated(mOutputTextureId);
callback.onFilterChanged(mCurrentShaderEffect);
}
});
}
@ -308,4 +316,15 @@ public class GlCameraPreview extends CameraPreview<GLSurfaceView, SurfaceTexture
protected Renderer instantiateRenderer() {
return new Renderer();
}
public void setShaderEffect(@NonNull Filter shaderEffect){
shaderEffect.setPreviewingViewSize(getView().getWidth(), getView().getHeight());
mCurrentShaderEffect = shaderEffect;
mOutputViewport.changeShaderFilter(shaderEffect);
for (RendererFrameCallback callback : mRendererFrameCallbacks) {
callback.onFilterChanged(shaderEffect);
}
}
}

@ -4,6 +4,8 @@ import android.graphics.SurfaceTexture;
import androidx.annotation.NonNull;
import com.otaliastudios.cameraview.filters.Filter;
/**
* Callback for renderer frames.
*/
@ -28,5 +30,13 @@ public interface RendererFrameCallback {
* @param scaleY the scaleY (in REF_VIEW) value
*/
@RendererThread
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY);
void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect);
/**
* Called on the change of shader filter
* We should update the EglViewPort once you receives this event
*
* @param filter the new filter applied
*/
void onFilterChanged(@NonNull Filter filter);
}

@ -13,6 +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.size.Size;
import com.otaliastudios.cameraview.video.encoding.AudioConfig;
import com.otaliastudios.cameraview.video.encoding.AudioMediaEncoder;
@ -60,6 +61,8 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
private boolean mHasOverlay;
private int mOverlayRotation;
private Filter mCurrentFilter;
public SnapshotVideoRecorder(@NonNull CameraEngine engine,
@NonNull GlCameraPreview preview,
@Nullable Overlay overlay,
@ -101,7 +104,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@RendererThread
@Override
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY) {
public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture, float scaleX, float scaleY, Filter shaderEffect) {
if (mCurrentState == STATE_NOT_RECORDING && mDesiredState == STATE_RECORDING) {
LOG.i("Starting the encoder engine.");
@ -157,6 +160,13 @@ 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.start();
mResult.rotation = 0; // We will rotate the result instead.
mCurrentState = STATE_RECORDING;
@ -182,6 +192,15 @@ 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();

@ -4,16 +4,17 @@ import android.graphics.SurfaceTexture;
import android.opengl.Matrix;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.filters.Filter;
import com.otaliastudios.cameraview.internal.egl.EglCore;
import com.otaliastudios.cameraview.internal.egl.EglViewport;
import com.otaliastudios.cameraview.internal.egl.EglWindowSurface;
import com.otaliastudios.cameraview.internal.utils.Pool;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
/**
* Default implementation for video encoding.
*/
@ -24,6 +25,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder<TextureConfig> {
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<TextureConfig> {
@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

@ -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 <T> boolean onValueChanged(@NonNull Option<T> option, @NonNull T value, @NonNull String name) {
if ((option instanceof Option.Width || option instanceof Option.Height)) {

@ -0,0 +1,6 @@
<vector android:height="24dp" android:viewportHeight="480.3"
android:viewportWidth="480.3" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M254.15,234.1V13.5c0,-7.5 -6,-13.5 -13.5,-13.5s-13.5,6 -13.5,13.5v220.6c-31.3,6.3 -55,34 -55,67.2s23.7,60.9 55,67.2v98.2c0,7.5 6,13.5 13.5,13.5s13.5,-6 13.5,-13.5v-98.2c31.3,-6.3 55,-34 55,-67.2C309.15,268.2 285.55,240.4 254.15,234.1zM240.65,342.8c-22.9,0 -41.5,-18.6 -41.5,-41.5s18.6,-41.5 41.5,-41.5s41.5,18.6 41.5,41.5S263.55,342.8 240.65,342.8z"/>
<path android:fillColor="#FFFFFF" android:pathData="M88.85,120.9V13.5c0,-7.5 -6,-13.5 -13.5,-13.5s-13.5,6 -13.5,13.5v107.4c-31.3,6.3 -55,34 -55,67.2s23.7,60.9 55,67.2v211.4c0,7.5 6,13.5 13.5,13.5s13.5,-6 13.5,-13.5V255.2c31.3,-6.3 55,-34 55,-67.2S120.15,127.2 88.85,120.9zM75.35,229.6c-22.9,0 -41.5,-18.6 -41.5,-41.5s18.6,-41.5 41.5,-41.5s41.5,18.6 41.5,41.5S98.15,229.6 75.35,229.6z"/>
<path android:fillColor="#FFFFFF" android:pathData="M418.45,120.9V13.5c0,-7.5 -6,-13.5 -13.5,-13.5s-13.5,6 -13.5,13.5v107.4c-31.3,6.3 -55,34 -55,67.2s23.7,60.9 55,67.2v211.5c0,7.5 6,13.5 13.5,13.5s13.5,-6 13.5,-13.5V255.2c31.3,-6.3 55,-34 55,-67.2S449.85,127.2 418.45,120.9zM404.95,229.6c-22.9,0 -41.5,-18.6 -41.5,-41.5s18.6,-41.5 41.5,-41.5s41.5,18.6 41.5,41.5S427.85,229.6 404.95,229.6z"/>
</vector>

@ -44,15 +44,31 @@
</com.otaliastudios.cameraview.CameraView>
<ImageButton
android:id="@+id/toggleCamera"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="16dp"
android:layout_gravity="top|end"
android:background="@drawable/background"
android:elevation="3dp"
app:srcCompat="@drawable/ic_switch" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="96dp"
android:layout_gravity="bottom|start">
<ImageButton
android:id="@+id/changeFilter"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/background"
android:elevation="3dp"
app:srcCompat="@drawable/ic_filters" />
<ImageButton
android:id="@+id/toggleCamera"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/background"
android:elevation="3dp"
app:srcCompat="@drawable/ic_switch" />
</LinearLayout>
<!-- Controls -->
<LinearLayout

Loading…
Cancel
Save