Improve realtime filters (#535)
* Simplify Filters class * Simplify filter switching in demo app * Create FilterCameraPreview, improve GlCameraPreview * Add comments * Cleanup EglViewport * Rename setPreviewingViewSize * Create Filter interface and BaseFilter abstract class * Move GL drawing code into BaseFilter * Avoid releasing location pointers * Add more docs and Filter.copy() * Split two packages * Remove filters package from code coverage computation * Document all filters, implement onCopy, suppress warnings * Add javadocs in Filters class * Move NoFilter, add string resources * XML support, require experimental flag * Update first 6 filters with onPreDraw * Update DuotoneFilter with onPreDraw * Update FillLightFilter with onPreDraw * Update Gamma, Grain, Grayscale, Hue, InvertColors, Lomoish with onPreDraw * Update Posterize, Saturation, Sepia with onPreDraw * Update all filters with onPreDraw * Add OneParameterFilter and TwoParameterFilter * Implement OneParameterFilter and TwoParameterFilter in all filters * Improve comments * Remove commented out code in demo * Add FilterParser test * Add GlCameraPreview and CameraView tests * Add documentation * Fix testspull/541/head
parent
facd26f11d
commit
bf41489279
@ -0,0 +1,52 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
|
||||
import android.content.res.TypedArray; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4; |
||||
import androidx.test.filters.SmallTest; |
||||
|
||||
import com.otaliastudios.cameraview.BaseTest; |
||||
import com.otaliastudios.cameraview.R; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
|
||||
import static junit.framework.TestCase.assertNotNull; |
||||
import static org.junit.Assert.assertTrue; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
|
||||
@RunWith(AndroidJUnit4.class) |
||||
@SmallTest |
||||
public class FilterParserTest extends BaseTest { |
||||
|
||||
@Test |
||||
public void testFallback() { |
||||
TypedArray array = mock(TypedArray.class); |
||||
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(false); |
||||
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(null); |
||||
FilterParser parser = new FilterParser(array); |
||||
assertNotNull(parser.getFilter()); |
||||
assertTrue(parser.getFilter() instanceof NoFilter); |
||||
} |
||||
@Test |
||||
public void testConstructor() { |
||||
TypedArray array = mock(TypedArray.class); |
||||
when(array.hasValue(R.styleable.CameraView_cameraFilter)).thenReturn(true); |
||||
when(array.getString(R.styleable.CameraView_cameraFilter)).thenReturn(MyFilter.class.getName()); |
||||
FilterParser parser = new FilterParser(array); |
||||
assertNotNull(parser.getFilter()); |
||||
assertTrue(parser.getFilter() instanceof MyFilter); |
||||
} |
||||
|
||||
public static class MyFilter extends BaseFilter { |
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return createDefaultFragmentShader(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,226 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.CameraLogger; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
import com.otaliastudios.cameraview.size.Size; |
||||
|
||||
import java.nio.FloatBuffer; |
||||
|
||||
/** |
||||
* A base implementation of {@link Filter} that just leaves the fragment shader to subclasses. |
||||
* See {@link NoFilter} for a non-abstract implementation. |
||||
* |
||||
* This class offers a default vertex shader implementation which in most cases is not required |
||||
* to be changed. Most effects can be rendered by simply changing the fragment shader, thus |
||||
* by overriding {@link #getFragmentShader()}. |
||||
* |
||||
* All {@link BaseFilter}s should have a no-op public constructor. |
||||
* This class will try to automatically implement {@link #copy()} thanks to this. |
||||
* If your filter implements public parameters, please implement {@link OneParameterFilter} |
||||
* and {@link TwoParameterFilter} to handle them and have them passed automatically to copies. |
||||
* |
||||
* NOTE - This class expects variable to have a certain name: |
||||
* - {@link #vertexPositionName} |
||||
* - {@link #vertexTransformMatrixName} |
||||
* - {@link #vertexModelViewProjectionMatrixName} |
||||
* - {@link #vertexTextureCoordinateName} |
||||
* - {@link #fragmentTextureCoordinateName} |
||||
* You can either change these variables, for example in your constructor, or change your |
||||
* vertex and fragment shader code to use them. |
||||
* |
||||
* NOTE - the {@link android.graphics.SurfaceTexture} restrictions apply: |
||||
* We only support the {@link android.opengl.GLES11Ext#GL_TEXTURE_EXTERNAL_OES} texture target |
||||
* and it must be specified in the fragment shader as a samplerExternalOES texture. |
||||
* You also have to explicitly require the extension: see {@link #createDefaultFragmentShader(String)}. |
||||
* |
||||
*/ |
||||
public abstract class BaseFilter implements Filter { |
||||
|
||||
private final static String TAG = BaseFilter.class.getSimpleName(); |
||||
private final static CameraLogger LOG = CameraLogger.create(TAG); |
||||
|
||||
private final static String DEFAULT_VERTEX_POSITION_NAME = "aPosition"; |
||||
private final static String DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME = "aTextureCoord"; |
||||
private final static String DEFAULT_VERTEX_MVP_MATRIX_NAME = "uMVPMatrix"; |
||||
private final static String DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME = "uTexMatrix"; |
||||
private final static String DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME = "vTextureCoord"; |
||||
|
||||
@NonNull |
||||
private static String createDefaultVertexShader(@NonNull String vertexPositionName, |
||||
@NonNull String vertexTextureCoordinateName, |
||||
@NonNull String vertexModelViewProjectionMatrixName, |
||||
@NonNull String vertexTransformMatrixName, |
||||
@NonNull String fragmentTextureCoordinateName) { |
||||
return "uniform mat4 "+vertexModelViewProjectionMatrixName+";\n" + |
||||
"uniform mat4 "+vertexTransformMatrixName+";\n" + |
||||
"attribute vec4 "+vertexPositionName+";\n" + |
||||
"attribute vec4 "+vertexTextureCoordinateName+";\n" + |
||||
"varying vec2 "+fragmentTextureCoordinateName+";\n" + |
||||
"void main() {\n" + |
||||
" gl_Position = "+vertexModelViewProjectionMatrixName+" * "+vertexPositionName+";\n" + |
||||
" vTextureCoord = ("+vertexTransformMatrixName+" * "+vertexTextureCoordinateName+").xy;\n" + |
||||
"}\n"; |
||||
} |
||||
|
||||
@NonNull |
||||
private static String createDefaultFragmentShader(@NonNull String fragmentTextureCoordinateName) { |
||||
return "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "varying vec2 "+fragmentTextureCoordinateName+";\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "void main() {\n" |
||||
+ " gl_FragColor = texture2D(sTexture, "+fragmentTextureCoordinateName+");\n" |
||||
+ "}\n"; |
||||
} |
||||
|
||||
// When the model/view/projection matrix is identity, this will exactly cover the viewport.
|
||||
private FloatBuffer vertexPosition = GlUtils.floatBuffer(new float[]{ |
||||
-1.0f, -1.0f, // 0 bottom left
|
||||
1.0f, -1.0f, // 1 bottom right
|
||||
-1.0f, 1.0f, // 2 top left
|
||||
1.0f, 1.0f, // 3 top right
|
||||
}); |
||||
|
||||
private FloatBuffer textureCoordinates = GlUtils.floatBuffer(new float[]{ |
||||
0.0f, 0.0f, // 0 bottom left
|
||||
1.0f, 0.0f, // 1 bottom right
|
||||
0.0f, 1.0f, // 2 top left
|
||||
1.0f, 1.0f // 3 top right
|
||||
}); |
||||
|
||||
private int vertexModelViewProjectionMatrixLocation = -1; |
||||
private int vertexTranformMatrixLocation = -1; |
||||
private int vertexPositionLocation = -1; |
||||
private int vertexTextureCoordinateLocation = -1; |
||||
private Size outputSize; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected String vertexPositionName = DEFAULT_VERTEX_POSITION_NAME; |
||||
@SuppressWarnings("WeakerAccess") |
||||
protected String vertexTextureCoordinateName = DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME; |
||||
@SuppressWarnings("WeakerAccess") |
||||
protected String vertexModelViewProjectionMatrixName = DEFAULT_VERTEX_MVP_MATRIX_NAME; |
||||
@SuppressWarnings("WeakerAccess") |
||||
protected String vertexTransformMatrixName = DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME; |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
protected String fragmentTextureCoordinateName = DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME; |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
@NonNull |
||||
protected String createDefaultVertexShader() { |
||||
return createDefaultVertexShader(vertexPositionName, |
||||
vertexTextureCoordinateName, |
||||
vertexModelViewProjectionMatrixName, |
||||
vertexTransformMatrixName, |
||||
fragmentTextureCoordinateName); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
@NonNull |
||||
protected String createDefaultFragmentShader() { |
||||
return createDefaultFragmentShader(fragmentTextureCoordinateName); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName); |
||||
GlUtils.checkLocation(vertexPositionLocation, vertexPositionName); |
||||
vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, vertexTextureCoordinateName); |
||||
GlUtils.checkLocation(vertexTextureCoordinateLocation, vertexTextureCoordinateName); |
||||
vertexModelViewProjectionMatrixLocation = GLES20.glGetUniformLocation(programHandle, vertexModelViewProjectionMatrixName); |
||||
GlUtils.checkLocation(vertexModelViewProjectionMatrixLocation, vertexModelViewProjectionMatrixName); |
||||
vertexTranformMatrixLocation = GLES20.glGetUniformLocation(programHandle, vertexTransformMatrixName); |
||||
GlUtils.checkLocation(vertexTranformMatrixLocation, vertexTransformMatrixName); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
vertexPositionLocation = -1; |
||||
vertexTextureCoordinateLocation = -1; |
||||
vertexModelViewProjectionMatrixLocation = -1; |
||||
vertexTranformMatrixLocation = -1; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getVertexShader() { |
||||
return createDefaultVertexShader(); |
||||
} |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
outputSize = new Size(width, height); |
||||
} |
||||
|
||||
@Override |
||||
public void draw(float[] transformMatrix) { |
||||
onPreDraw(transformMatrix); |
||||
onDraw(); |
||||
onPostDraw(); |
||||
} |
||||
|
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
// Copy the model / view / projection matrix over.
|
||||
GLES20.glUniformMatrix4fv(vertexModelViewProjectionMatrixLocation, 1, false, GlUtils.IDENTITY_MATRIX, 0); |
||||
GlUtils.checkError("glUniformMatrix4fv"); |
||||
|
||||
// Copy the texture transformation matrix over.
|
||||
GLES20.glUniformMatrix4fv(vertexTranformMatrixLocation, 1, false, transformMatrix, 0); |
||||
GlUtils.checkError("glUniformMatrix4fv"); |
||||
|
||||
// Enable the "aPosition" vertex attribute.
|
||||
// Connect vertexBuffer to "aPosition".
|
||||
GLES20.glEnableVertexAttribArray(vertexPositionLocation); |
||||
GlUtils.checkError("glEnableVertexAttribArray: " + vertexPositionLocation); |
||||
GLES20.glVertexAttribPointer(vertexPositionLocation, 2, GLES20.GL_FLOAT, false, 8, vertexPosition); |
||||
GlUtils.checkError("glVertexAttribPointer"); |
||||
|
||||
// Enable the "aTextureCoord" vertex attribute.
|
||||
// Connect texBuffer to "aTextureCoord".
|
||||
GLES20.glEnableVertexAttribArray(vertexTextureCoordinateLocation); |
||||
GlUtils.checkError("glEnableVertexAttribArray"); |
||||
GLES20.glVertexAttribPointer(vertexTextureCoordinateLocation, 2, GLES20.GL_FLOAT, false, 8, textureCoordinates); |
||||
GlUtils.checkError("glVertexAttribPointer"); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected void onDraw() { |
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
||||
GlUtils.checkError("glDrawArrays"); |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected void onPostDraw() { |
||||
GLES20.glDisableVertexAttribArray(vertexPositionLocation); |
||||
GLES20.glDisableVertexAttribArray(vertexTextureCoordinateLocation); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public final BaseFilter copy() { |
||||
BaseFilter copy = onCopy(); |
||||
copy.setSize(outputSize.getWidth(), outputSize.getHeight()); |
||||
if (this instanceof OneParameterFilter) { |
||||
((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1()); |
||||
} |
||||
if (this instanceof TwoParameterFilter) { |
||||
((TwoParameterFilter) copy).setParameter2(((TwoParameterFilter) this).getParameter2()); |
||||
} |
||||
return copy; |
||||
} |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
protected BaseFilter onCopy() { |
||||
try { |
||||
return getClass().newInstance(); |
||||
} catch (IllegalAccessException e) { |
||||
throw new RuntimeException("Filters should have a public no-op constructor.", e); |
||||
} catch (InstantiationException e) { |
||||
throw new RuntimeException("Filters should have a public no-op constructor.", e); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,91 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
import com.otaliastudios.cameraview.CameraView; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import java.io.File; |
||||
|
||||
|
||||
/** |
||||
* A Filter is a real time filter that operates onto the camera preview, plus any |
||||
* snapshot media taken with {@link CameraView#takePictureSnapshot()} and |
||||
* {@link CameraView#takeVideoSnapshot(File)}. |
||||
* |
||||
* You can apply filters to the camera engine using {@link CameraView#setFilter(Filter)}. |
||||
* The default filter is called {@link NoFilter} and can be used to restore the normal preview. |
||||
* A lof of other filters are collected in the {@link Filters} class. |
||||
* |
||||
* Advanced users can create custom filters using GLES. |
||||
* It is recommended to extend {@link BaseFilter} instead of this class. |
||||
* |
||||
* All {@link Filter}s should have a no-op public constructor. |
||||
* This ensures that you can pass the filter class to XML attribute {@code app:cameraFilter}, |
||||
* and also helps {@link BaseFilter} automatically make a copy of the filter. |
||||
* |
||||
* Parameterized filters can implement {@link OneParameterFilter} and {@link TwoParameterFilter} |
||||
* to receive parameters in the 0F-1F range. This helps in making filter copies and also let us |
||||
* map the filter parameter to gestures. |
||||
*/ |
||||
public interface Filter { |
||||
|
||||
/** |
||||
* Returns a String containing the vertex shader. |
||||
* Together with {@link #getFragmentShader()}, this will be used to |
||||
* create the OpenGL program. |
||||
* |
||||
* @return vertex shader |
||||
*/ |
||||
@NonNull |
||||
String getVertexShader(); |
||||
|
||||
/** |
||||
* Returns a String containing the fragment shader. |
||||
* Together with {@link #getVertexShader()}, this will be used to |
||||
* create the OpenGL program. |
||||
* |
||||
* @return fragment shader |
||||
*/ |
||||
@NonNull |
||||
String getFragmentShader(); |
||||
|
||||
/** |
||||
* The filter program was just created. We pass in a handle to the OpenGL |
||||
* program that was created, so you can fetch pointers. |
||||
* |
||||
* @param programHandle handle |
||||
*/ |
||||
void onCreate(int programHandle); |
||||
|
||||
/** |
||||
* The filter program is about to be destroyed. |
||||
* |
||||
*/ |
||||
void onDestroy(); |
||||
|
||||
/** |
||||
* Called to render the actual texture. The given transformation matrix |
||||
* should be applied. |
||||
* |
||||
* @param transformMatrix matrix |
||||
*/ |
||||
void draw(float[] transformMatrix); |
||||
|
||||
/** |
||||
* Called anytime the output size changes. |
||||
* |
||||
* @param width width |
||||
* @param height height |
||||
*/ |
||||
void setSize(int width, int height); |
||||
|
||||
/** |
||||
* Clones this filter creating a new instance of it. |
||||
* If it has any important parameters, these should be passed |
||||
* to the new instance. |
||||
* |
||||
* @return a clone |
||||
*/ |
||||
@NonNull |
||||
Filter copy(); |
||||
} |
@ -0,0 +1,32 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
import android.content.res.TypedArray; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
|
||||
import com.otaliastudios.cameraview.R; |
||||
|
||||
/** |
||||
* Parses filters from XML attributes. |
||||
*/ |
||||
public class FilterParser { |
||||
|
||||
private Filter filter = null; |
||||
|
||||
public FilterParser(@NonNull TypedArray array) { |
||||
String filterName = array.getString(R.styleable.CameraView_cameraFilter); |
||||
try { |
||||
//noinspection ConstantConditions
|
||||
Class<?> filterClass = Class.forName(filterName); |
||||
filter = (Filter) filterClass.newInstance(); |
||||
} catch (Exception ignore) { |
||||
filter = new NoFilter(); |
||||
} |
||||
} |
||||
|
||||
@NonNull |
||||
public Filter getFilter() { |
||||
return filter; |
||||
} |
||||
} |
@ -0,0 +1,121 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filters.AutoFixFilter; |
||||
import com.otaliastudios.cameraview.filters.BlackAndWhiteFilter; |
||||
import com.otaliastudios.cameraview.filters.BrightnessFilter; |
||||
import com.otaliastudios.cameraview.filters.ContrastFilter; |
||||
import com.otaliastudios.cameraview.filters.CrossProcessFilter; |
||||
import com.otaliastudios.cameraview.filters.DocumentaryFilter; |
||||
import com.otaliastudios.cameraview.filters.DuotoneFilter; |
||||
import com.otaliastudios.cameraview.filters.FillLightFilter; |
||||
import com.otaliastudios.cameraview.filters.GammaFilter; |
||||
import com.otaliastudios.cameraview.filters.GrainFilter; |
||||
import com.otaliastudios.cameraview.filters.GrayscaleFilter; |
||||
import com.otaliastudios.cameraview.filters.HueFilter; |
||||
import com.otaliastudios.cameraview.filters.InvertColorsFilter; |
||||
import com.otaliastudios.cameraview.filters.LomoishFilter; |
||||
import com.otaliastudios.cameraview.filters.PosterizeFilter; |
||||
import com.otaliastudios.cameraview.filters.SaturationFilter; |
||||
import com.otaliastudios.cameraview.filters.SepiaFilter; |
||||
import com.otaliastudios.cameraview.filters.SharpnessFilter; |
||||
import com.otaliastudios.cameraview.filters.TemperatureFilter; |
||||
import com.otaliastudios.cameraview.filters.TintFilter; |
||||
import com.otaliastudios.cameraview.filters.VignetteFilter; |
||||
|
||||
/** |
||||
* Contains commonly used {@link Filter}s. |
||||
* |
||||
* You can use {@link #newInstance()} to create a new instance and |
||||
* pass it to {@link com.otaliastudios.cameraview.CameraView#setFilter(Filter)}. |
||||
*/ |
||||
public enum Filters { |
||||
|
||||
/** @see NoFilter */ |
||||
NONE(NoFilter.class), |
||||
|
||||
/** @see AutoFixFilter */ |
||||
AUTO_FIX(AutoFixFilter.class), |
||||
|
||||
/** @see BlackAndWhiteFilter */ |
||||
BLACK_AND_WHITE(BlackAndWhiteFilter.class), |
||||
|
||||
/** @see BrightnessFilter */ |
||||
BRIGHTNESS(BrightnessFilter.class), |
||||
|
||||
/** @see ContrastFilter */ |
||||
CONTRAST(ContrastFilter.class), |
||||
|
||||
/** @see CrossProcessFilter */ |
||||
CROSS_PROCESS(CrossProcessFilter.class), |
||||
|
||||
/** @see DocumentaryFilter */ |
||||
DOCUMENTARY(DocumentaryFilter.class), |
||||
|
||||
/** @see DuotoneFilter */ |
||||
DUOTONE(DuotoneFilter.class), |
||||
|
||||
/** @see FillLightFilter */ |
||||
FILL_LIGHT(FillLightFilter.class), |
||||
|
||||
/** @see GammaFilter */ |
||||
GAMMA(GammaFilter.class), |
||||
|
||||
/** @see GrainFilter */ |
||||
GRAIN(GrainFilter.class), |
||||
|
||||
/** @see GrayscaleFilter */ |
||||
GRAYSCALE(GrayscaleFilter.class), |
||||
|
||||
/** @see HueFilter */ |
||||
HUE(HueFilter.class), |
||||
|
||||
/** @see InvertColorsFilter */ |
||||
INVERT_COLORS(InvertColorsFilter.class), |
||||
|
||||
/** @see LomoishFilter */ |
||||
LOMOISH(LomoishFilter.class), |
||||
|
||||
/** @see PosterizeFilter */ |
||||
POSTERIZE(PosterizeFilter.class), |
||||
|
||||
/** @see SaturationFilter */ |
||||
SATURATION(SaturationFilter.class), |
||||
|
||||
/** @see SepiaFilter */ |
||||
SEPIA(SepiaFilter.class), |
||||
|
||||
/** @see SharpnessFilter */ |
||||
SHARPNESS(SharpnessFilter.class), |
||||
|
||||
/** @see TemperatureFilter */ |
||||
TEMPERATURE(TemperatureFilter.class), |
||||
|
||||
/** @see TintFilter */ |
||||
TINT(TintFilter.class), |
||||
|
||||
/** @see VignetteFilter */ |
||||
VIGNETTE(VignetteFilter.class); |
||||
|
||||
private Class<? extends Filter> filterClass; |
||||
|
||||
Filters(@NonNull Class<? extends Filter> filterClass) { |
||||
this.filterClass = filterClass; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new instance of the given filter. |
||||
* @return a new instance |
||||
*/ |
||||
@NonNull |
||||
public Filter newInstance() { |
||||
try { |
||||
return filterClass.newInstance(); |
||||
} catch (IllegalAccessException e) { |
||||
return new NoFilter(); |
||||
} catch (InstantiationException e) { |
||||
return new NoFilter(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
|
||||
public class NoFilter extends BaseFilter { |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return createDefaultFragmentShader(); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
/** |
||||
* A special {@link Filter} that accepts a float parameter. |
||||
* |
||||
* The parameters will always be between 0F and 1F, so subclasses should |
||||
* map this range to their internal range if needed. |
||||
* |
||||
* A standardized range is useful for different applications. For example: |
||||
* - Filter parameters can be easily mapped to gestures since the range is fixed |
||||
* - {@link BaseFilter} can use this setters and getters to make a filter copy |
||||
*/ |
||||
public interface OneParameterFilter extends Filter { |
||||
|
||||
/** |
||||
* Sets the parameter. |
||||
* The value should always be between 0 and 1. |
||||
* |
||||
* @param value parameter |
||||
*/ |
||||
void setParameter1(float value); |
||||
|
||||
/** |
||||
* Returns the parameter. |
||||
* The returned value should always be between 0 and 1. |
||||
* |
||||
* @return parameter |
||||
*/ |
||||
float getParameter1(); |
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.otaliastudios.cameraview.filter; |
||||
|
||||
/** |
||||
* A special {@link Filter} that accepts two floats parameters. |
||||
* This is done by extending {@link OneParameterFilter}. |
||||
* |
||||
* The parameters will always be between 0F and 1F, so subclasses should |
||||
* map this range to their internal range if needed. |
||||
* |
||||
* A standardized range is useful for different applications. For example: |
||||
* - Filter parameters can be easily mapped to gestures since the range is fixed |
||||
* - {@link BaseFilter} can use this setters and getters to make a filter copy |
||||
*/ |
||||
public interface TwoParameterFilter extends OneParameterFilter { |
||||
|
||||
/** |
||||
* Sets the second parameter. |
||||
* The value should always be between 0 and 1. |
||||
* |
||||
* @param value parameter |
||||
*/ |
||||
void setParameter2(float value); |
||||
|
||||
/** |
||||
* Returns the second parameter. |
||||
* The returned value should always be between 0 and 1. |
||||
* |
||||
* @return parameter |
||||
*/ |
||||
float getParameter2(); |
||||
} |
@ -1,96 +1,124 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Attempts to auto-fix the preview based on histogram equalization. |
||||
* Attempts to auto-fix the frames based on histogram equalization. |
||||
*/ |
||||
public class AutoFixFilter extends Filter { |
||||
public class AutoFixFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_0;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_1;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_2;\n" |
||||
+ "uniform float scale;\n" |
||||
+ "float shift_scale;\n" |
||||
+ "float hist_offset;\n" |
||||
+ "float hist_scale;\n" |
||||
+ "float density_offset;\n" |
||||
+ "float density_scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " shift_scale = " + (1.0f / 256f) + ";\n" |
||||
+ " hist_offset = " + (0.5f / 766f) + ";\n" |
||||
+ " hist_scale = " + (765f / 766f) + ";\n" |
||||
+ " density_offset = " + (0.5f / 1024f) + ";\n" |
||||
+ " density_scale = " + (1023f / 1024f) + ";\n" |
||||
+ " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n" |
||||
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n" |
||||
+ " float energy = dot(color.rgb, weights);\n" |
||||
+ " float mask_value = energy - 0.5;\n" |
||||
+ " float alpha;\n" |
||||
+ " if (mask_value > 0.0) {\n" |
||||
+ " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n" |
||||
+ " } else { \n" |
||||
+ " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n" |
||||
+ " }\n" |
||||
+ " float index = energy * hist_scale + hist_offset;\n" |
||||
+ " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n" |
||||
+ " float value = temp.g + temp.r * shift_scale;\n" |
||||
+ " index = value * density_scale + density_offset;\n" |
||||
+ " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n" |
||||
+ " value = temp.g + temp.r * shift_scale;\n" |
||||
+ " float dst_energy = energy * alpha + value * (1.0 - alpha);\n" |
||||
+ " float max_energy = energy / max(color.r, max(color.g, color.b));\n" |
||||
+ " if (dst_energy > max_energy) {\n" |
||||
+ " dst_energy = max_energy;\n" |
||||
+ " }\n" |
||||
+ " if (energy == 0.0) {\n" |
||||
+ " gl_FragColor = color;\n" |
||||
+ " } else {\n" |
||||
+ " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n" |
||||
+ " }\n" |
||||
+ "}\n"; |
||||
|
||||
private float scale = 1.0f; |
||||
private int scaleLocation = -1; |
||||
|
||||
public AutoFixFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* A parameter between 0 and 1. Zero means no adjustment, while 1 indicates |
||||
* the maximum amount of adjustment. |
||||
* |
||||
* @param scale scale |
||||
*/ |
||||
public AutoFixFilter() { |
||||
public void setScale(float scale) { |
||||
if (scale < 0.0f) scale = 0.0f; |
||||
if (scale > 1.0f) scale = 1.0f; |
||||
this.scale = scale; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current scale. |
||||
* |
||||
* @see #setScale(float) |
||||
* @return current scale |
||||
*/ |
||||
public float getScale() { |
||||
return scale; |
||||
} |
||||
|
||||
/** |
||||
* @param scale Float, between 0 and 1. Zero means no adjustment, while 1 |
||||
* indicates the maximum amount of adjustment. |
||||
*/ |
||||
public void setScale(float scale) { |
||||
if (scale < 0.0f) |
||||
scale = 0.0f; |
||||
else if (scale > 1.0f) |
||||
scale = 1.0f; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setScale(value); |
||||
} |
||||
|
||||
this.scale = scale; |
||||
@Override |
||||
public float getParameter1() { |
||||
return getScale(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_0;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_1;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_2;\n" |
||||
+ " float scale;\n" + " float shift_scale;\n" |
||||
+ " float hist_offset;\n" + " float hist_scale;\n" |
||||
+ " float density_offset;\n" + " float density_scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" + "void main() {\n" |
||||
+ " shift_scale = " |
||||
+ (1.0f / 256f) |
||||
+ ";\n" |
||||
+ " hist_offset = " |
||||
+ (0.5f / 766f) |
||||
+ ";\n" |
||||
+ " hist_scale = " |
||||
+ (765f / 766f) |
||||
+ ";\n" |
||||
+ " density_offset = " |
||||
+ (0.5f / 1024f) |
||||
+ ";\n" |
||||
+ " density_scale = " |
||||
+ (1023f / 1024f) |
||||
+ ";\n" |
||||
+ " scale = " |
||||
+ scale |
||||
+ ";\n" |
||||
+ " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n" |
||||
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n" |
||||
+ " float energy = dot(color.rgb, weights);\n" |
||||
+ " float mask_value = energy - 0.5;\n" |
||||
+ " float alpha;\n" |
||||
+ " if (mask_value > 0.0) {\n" |
||||
+ " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n" |
||||
+ " } else { \n" |
||||
+ " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n" |
||||
+ " }\n" |
||||
+ " float index = energy * hist_scale + hist_offset;\n" |
||||
+ " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n" |
||||
+ " float value = temp.g + temp.r * shift_scale;\n" |
||||
+ " index = value * density_scale + density_offset;\n" |
||||
+ " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n" |
||||
+ " value = temp.g + temp.r * shift_scale;\n" |
||||
+ " float dst_energy = energy * alpha + value * (1.0 - alpha);\n" |
||||
+ " float max_energy = energy / max(color.r, max(color.g, color.b));\n" |
||||
+ " if (dst_energy > max_energy) {\n" |
||||
+ " dst_energy = max_energy;\n" |
||||
+ " }\n" |
||||
+ " if (energy == 0.0) {\n" |
||||
+ " gl_FragColor = color;\n" |
||||
+ " } else {\n" |
||||
+ " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n" |
||||
+ " }\n" + "}\n"; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(scaleLocation, "scale"); |
||||
} |
||||
|
||||
return shader; |
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
scaleLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(scaleLocation, scale); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,56 +1,94 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Adjusts the brightness of the preview. |
||||
* Adjusts the brightness of the frames. |
||||
*/ |
||||
public class BrightnessFilter extends Filter { |
||||
private float brightnessValue = 2.0f; |
||||
public class BrightnessFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float brightness;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " gl_FragColor = brightness * color;\n" |
||||
+ "}\n"; |
||||
|
||||
private float brightness = 2.0f; // 1.0F...2.0F
|
||||
private int brightnessLocation = -1; |
||||
|
||||
|
||||
public BrightnessFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the brightness adjustment. |
||||
* 1.0: normal brightness. |
||||
* 2.0: high brightness. |
||||
* |
||||
* @param brightness brightness. |
||||
*/ |
||||
public BrightnessFilter() { |
||||
@SuppressWarnings({"WeakerAccess", "unused"}) |
||||
public void setBrightness(float brightness) { |
||||
if (brightness < 1.0f) brightness = 1.0f; |
||||
if (brightness > 2.0f) brightness = 2.0f; |
||||
this.brightness = brightness; |
||||
} |
||||
|
||||
/** |
||||
* setBrightnessValue |
||||
* Returns the current brightness. |
||||
* |
||||
* @param brightnessvalue Range should be between 0.0- 1.0 with 0.0 being normal. |
||||
* @see #setBrightness(float) |
||||
* @return brightness |
||||
*/ |
||||
public void setBrightnessValue(float brightnessvalue) { |
||||
if (brightnessvalue < 0.0f) |
||||
brightnessvalue = 0.0f; |
||||
else if (brightnessvalue > 1.0f) |
||||
brightnessvalue = 1.0f; |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
public float getBrightness() { |
||||
return brightness; |
||||
} |
||||
|
||||
//since the shader excepts a range of 1.0 - 2.0
|
||||
// will add the 1.0 to every value
|
||||
this.brightnessValue = 1.0f + brightnessvalue; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
// parameter is 0...1, brightness is 1...2.
|
||||
setBrightness(value + 1); |
||||
} |
||||
|
||||
public float getBrightnessValue() { |
||||
//since the shader excepts a range of 1.0 - 2.0
|
||||
//to keep it between 0.0f - 1.0f range, will subtract the 1.0 to every value
|
||||
return brightnessValue - 1.0f; |
||||
@Override |
||||
public float getParameter1() { |
||||
// parameter is 0...1, brightness is 1...2.
|
||||
return getBrightness() - 1F; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "float brightness ;\n" + "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" + " brightness =" + brightnessValue |
||||
+ ";\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " gl_FragColor = brightness * color;\n" + "}\n"; |
||||
|
||||
return shader; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness"); |
||||
GlUtils.checkLocation(brightnessLocation, "brightness"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
brightnessLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(brightnessLocation, brightness); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,55 +1,96 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Adjusts the contrast of the preview. |
||||
* Adjusts the contrast. |
||||
*/ |
||||
public class ContrastFilter extends Filter { |
||||
private float contrast = 2.0f; |
||||
public class ContrastFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float contrast;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " color -= 0.5;\n" |
||||
+ " color *= contrast;\n" |
||||
+ " color += 0.5;\n" |
||||
+ " gl_FragColor = color;\n" |
||||
+ "}\n"; |
||||
|
||||
private float contrast = 2F; |
||||
private int contrastLocation = -1; |
||||
|
||||
public ContrastFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the current contrast adjustment. |
||||
* 1.0: no adjustment |
||||
* 2.0: increased contrast |
||||
* |
||||
* @param contrast contrast |
||||
*/ |
||||
public ContrastFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setContrast(float contrast) { |
||||
if (contrast < 1.0f) contrast = 1.0f; |
||||
if (contrast > 2.0f) contrast = 2.0f; |
||||
this.contrast = contrast; |
||||
} |
||||
|
||||
/** |
||||
* setContrast |
||||
* Returns the current contrast. |
||||
* |
||||
* @param contrast Range should be between 0.0- 1.0 with 0.0 being normal. |
||||
* @see #setContrast(float) |
||||
* @return contrast |
||||
*/ |
||||
public void setContrast(float contrast) { |
||||
if (contrast < 0.0f) |
||||
contrast = 0.0f; |
||||
else if (contrast > 1.0f) |
||||
contrast = 1.0f; |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
public float getContrast() { |
||||
return contrast; |
||||
} |
||||
|
||||
//since the shader excepts a range of 1.0 - 2.0
|
||||
//will add the 1.0 to every value
|
||||
this.contrast = contrast + 1.0f; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
// parameter is 0...1, contrast is 1...2.
|
||||
setContrast(value + 1); |
||||
} |
||||
|
||||
public float getContrast() { |
||||
//since the shader excepts a range of 1.0 - 2.0
|
||||
//to keep it between 0.0f - 1.0f range, will subtract the 1.0 to every value
|
||||
return contrast - 1.0f; |
||||
@Override |
||||
public float getParameter1() { |
||||
// parameter is 0...1, contrast is 1...2.
|
||||
return getContrast() - 1F; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " float contrast;\n" + "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" + " contrast =" + contrast + ";\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " color -= 0.5;\n" + " color *= contrast;\n" |
||||
+ " color += 0.5;\n" + " gl_FragColor = color;\n" + "}\n"; |
||||
return shader; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
contrastLocation = GLES20.glGetUniformLocation(programHandle, "contrast"); |
||||
GlUtils.checkLocation(contrastLocation, "contrast"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
contrastLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(contrastLocation, contrast); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,27 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
|
||||
/** |
||||
* This class is to implement any custom effect. |
||||
*/ |
||||
public class CustomFilter extends Filter { |
||||
|
||||
/** |
||||
* Parameterized constructor with vertex and fragment shader as parameter |
||||
* |
||||
* @param vertexShader |
||||
* @param fragmentShader |
||||
*/ |
||||
public CustomFilter(String vertexShader, String fragmentShader) { |
||||
this.mVertexShader = vertexShader; |
||||
this.mFragmentShader = fragmentShader; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return mFragmentShader; |
||||
} |
||||
} |
@ -1,96 +1,115 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import java.util.Date; |
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
import java.util.Random; |
||||
|
||||
/** |
||||
* Applies black and white documentary style effect on preview. |
||||
* Applies black and white documentary style effect. |
||||
*/ |
||||
public class DocumentaryFilter extends Filter { |
||||
private Random mRandom; |
||||
public class DocumentaryFilter extends BaseFilter { |
||||
|
||||
private final static Random RANDOM = new Random(); |
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "vec2 seed;\n" |
||||
+ "float stepsize;\n" |
||||
+ "uniform float inv_max_dist;\n" |
||||
+ "uniform vec2 scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
+ |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
" float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " return fract(part1 + part2 + part3);\n" |
||||
+ "}\n" |
||||
+ "void main() {\n" |
||||
+ " seed[0] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " seed[1] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " stepsize = " + 1.0f / 255.0f + ";\n" |
||||
|
||||
// black white
|
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float dither = rand(vTextureCoord + seed);\n" |
||||
+ " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n" |
||||
+ " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n" |
||||
+ " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" |
||||
// grayscale
|
||||
+ " float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n" |
||||
+ " new_color = vec3(gray, gray, gray);\n" |
||||
// vignette
|
||||
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n" |
||||
+ " gl_FragColor = vec4(new_color * lumen, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private int mWidth = 1; |
||||
private int mHeight = 1; |
||||
private int mScaleLocation = -1; |
||||
private int mMaxDistLocation = -1; |
||||
|
||||
public DocumentaryFilter() { |
||||
mRandom = new Random(new Date().getTime()); |
||||
public DocumentaryFilter() { } |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
super.setSize(width, height); |
||||
mWidth = width; |
||||
mHeight = height; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float scale[] = new float[2]; |
||||
if (mPreviewingViewWidth > mPreviewingViewHeight) { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(mScaleLocation, "scale"); |
||||
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); |
||||
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
mScaleLocation = -1; |
||||
mMaxDistLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float[] scale = new float[2]; |
||||
if (mWidth > mHeight) { |
||||
scale[0] = 1f; |
||||
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth; |
||||
scale[1] = ((float) mHeight) / mWidth; |
||||
} else { |
||||
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight; |
||||
scale[0] = ((float) mWidth) / mHeight; |
||||
scale[1] = 1f; |
||||
} |
||||
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] |
||||
* scale[1])) * 0.5f; |
||||
|
||||
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()}; |
||||
|
||||
String scaleString[] = new String[2]; |
||||
String seedString[] = new String[2]; |
||||
|
||||
scaleString[0] = "scale[0] = " + scale[0] + ";\n"; |
||||
scaleString[1] = "scale[1] = " + scale[1] + ";\n"; |
||||
|
||||
seedString[0] = "seed[0] = " + seed[0] + ";\n"; |
||||
seedString[1] = "seed[1] = " + seed[1] + ";\n"; |
||||
|
||||
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n"; |
||||
String stepsizeString = "stepsize = " + 1.0f / 255.0f + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " vec2 seed;\n" |
||||
+ " float stepsize;\n" |
||||
+ " float inv_max_dist;\n" |
||||
+ " vec2 scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
+ |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
" float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " return fract(part1 + part2 + part3);\n" |
||||
+ "}\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ scaleString[0] |
||||
+ scaleString[1] |
||||
+ seedString[0] |
||||
+ seedString[1] |
||||
+ inv_max_distString |
||||
+ stepsizeString |
||||
|
||||
// black white
|
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float dither = rand(vTextureCoord + seed);\n" |
||||
+ " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n" |
||||
+ " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n" |
||||
+ " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" |
||||
+ |
||||
// grayscale
|
||||
" float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n" |
||||
+ " new_color = vec3(gray, gray, gray);\n" |
||||
+ |
||||
// vignette
|
||||
" vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n" |
||||
+ " gl_FragColor = vec4(new_color * lumen, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
return shader; |
||||
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); |
||||
GlUtils.checkError("glUniform2fv"); |
||||
|
||||
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; |
||||
float invMaxDist = 1F / maxDist; |
||||
GLES20.glUniform1f(mMaxDistLocation, invMaxDist); |
||||
GlUtils.checkError("glUniform1f"); |
||||
|
||||
} |
||||
} |
||||
|
@ -1,85 +1,158 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.graphics.Color; |
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.ColorInt; |
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.TwoParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Representation of preview using only two color tones. |
||||
* Representation of input frames using only two color tones. |
||||
*/ |
||||
public class DuotoneFilter extends Filter { |
||||
public class DuotoneFilter extends BaseFilter implements TwoParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform vec3 first;\n" |
||||
+ "uniform vec3 second;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float energy = (color.r + color.g + color.b) * 0.3333;\n" |
||||
+ " vec3 new_color = (1.0 - energy) * first + energy * second;\n" |
||||
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
// Default values
|
||||
private int mFirstColor = Color.MAGENTA; |
||||
private int mSecondColor = Color.YELLOW; |
||||
private int mFirstColorLocation = -1; |
||||
private int mSecondColorLocation = -1; |
||||
|
||||
public DuotoneFilter() { } |
||||
|
||||
/** |
||||
* Initialize effect |
||||
* Sets the two duotone ARGB colors. |
||||
* @param firstColor first |
||||
* @param secondColor second |
||||
*/ |
||||
@SuppressWarnings({"unused"}) |
||||
public void setColors(@ColorInt int firstColor, @ColorInt int secondColor) { |
||||
setFirstColor(firstColor); |
||||
setSecondColor(secondColor); |
||||
} |
||||
|
||||
/** |
||||
* Sets the first of the duotone ARGB colors. |
||||
* Defaults to {@link Color#MAGENTA}. |
||||
* |
||||
* @param color first color |
||||
*/ |
||||
public DuotoneFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setFirstColor(@ColorInt int color) { |
||||
mFirstColor = color; |
||||
} |
||||
|
||||
/** |
||||
* setDuoToneColors |
||||
* Sets the second of the duotone ARGB colors. |
||||
* Defaults to {@link Color#YELLOW}. |
||||
* |
||||
* @param firstColor Integer, representing an ARGB color with 8 bits per channel. |
||||
* May be created using Color class. |
||||
* @param secondColor Integer, representing an ARGB color with 8 bits per channel. |
||||
* May be created using Color class. |
||||
* @param color second color |
||||
*/ |
||||
public void setDuoToneColors(int firstColor, int secondColor) { |
||||
this.mFirstColor = firstColor; |
||||
this.mSecondColor = secondColor; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setSecondColor(@ColorInt int color) { |
||||
mSecondColor = color; |
||||
} |
||||
|
||||
/** |
||||
* Returns the first color. |
||||
* |
||||
* @see #setFirstColor(int) |
||||
* @return first |
||||
*/ |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
@ColorInt |
||||
public int getFirstColor() { |
||||
return mFirstColor; |
||||
} |
||||
|
||||
/** |
||||
* Returns the second color. |
||||
* |
||||
* @see #setSecondColor(int) |
||||
* @return second |
||||
*/ |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
@ColorInt |
||||
public int getSecondColor() { |
||||
return mSecondColor; |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter1(float value) { |
||||
// no easy way to transform 0...1 into a color.
|
||||
setFirstColor((int) (value * Integer.MAX_VALUE)); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter1() { |
||||
return (float) getFirstColor() / Integer.MAX_VALUE; |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter2(float value) { |
||||
// no easy way to transform 0...1 into a color.
|
||||
setSecondColor((int) (value * Integer.MAX_VALUE)); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter2() { |
||||
return (float) getSecondColor() / Integer.MAX_VALUE; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float first[] = {Color.red(mFirstColor) / 255f, |
||||
Color.green(mFirstColor) / 255f, Color.blue(mFirstColor) / 255f}; |
||||
float second[] = {Color.red(mSecondColor) / 255f, |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
mFirstColorLocation = GLES20.glGetUniformLocation(programHandle, "first"); |
||||
GlUtils.checkLocation(mFirstColorLocation, "first"); |
||||
mSecondColorLocation = GLES20.glGetUniformLocation(programHandle, "second"); |
||||
GlUtils.checkLocation(mSecondColorLocation, "second"); |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float[] first = new float[]{ |
||||
Color.red(mFirstColor) / 255f, |
||||
Color.green(mFirstColor) / 255f, |
||||
Color.blue(mFirstColor) / 255f |
||||
}; |
||||
float[] second = new float[]{ |
||||
Color.red(mSecondColor) / 255f, |
||||
Color.green(mSecondColor) / 255f, |
||||
Color.blue(mSecondColor) / 255f}; |
||||
|
||||
String firstColorString[] = new String[3]; |
||||
String secondColorString[] = new String[3]; |
||||
|
||||
firstColorString[0] = "first[0] = " + first[0] + ";\n"; |
||||
firstColorString[1] = "first[1] = " + first[1] + ";\n"; |
||||
firstColorString[2] = "first[2] = " + first[2] + ";\n"; |
||||
|
||||
secondColorString[0] = "second[0] = " + second[0] + ";\n"; |
||||
secondColorString[1] = "second[1] = " + second[1] + ";\n"; |
||||
secondColorString[2] = "second[2] = " + second[2] + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " vec3 first;\n" |
||||
+ " vec3 second;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ firstColorString[0] |
||||
+ firstColorString[1] |
||||
+ firstColorString[2] |
||||
+ secondColorString[0] |
||||
+ secondColorString[1] |
||||
+ secondColorString[2] |
||||
|
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float energy = (color.r + color.g + color.b) * 0.3333;\n" |
||||
+ " vec3 new_color = (1.0 - energy) * first + energy * second;\n" |
||||
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n"; |
||||
|
||||
return shader; |
||||
Color.blue(mSecondColor) / 255f |
||||
}; |
||||
GLES20.glUniform3fv(mFirstColorLocation, 1, first, 0); |
||||
GlUtils.checkError("glUniform3fv"); |
||||
GLES20.glUniform3fv(mSecondColorLocation, 1, second, 0); |
||||
GlUtils.checkError("glUniform3fv"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
mFirstColorLocation = -1; |
||||
mSecondColorLocation = -1; |
||||
} |
||||
} |
||||
|
@ -1,73 +1,111 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Applies back-light filling to the preview. |
||||
* Applies back-light filling to the frames. |
||||
*/ |
||||
public class FillLightFilter extends Filter { |
||||
public class FillLightFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float mult;\n" |
||||
+ "uniform float igamma;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float lightmask = dot(color.rgb, color_weights);\n" |
||||
+ " float backmask = (1.0 - lightmask);\n" |
||||
+ " vec3 ones = vec3(1.0, 1.0, 1.0);\n" |
||||
+ " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n" |
||||
+ " diff = min(diff, 1.0);\n" |
||||
+ " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n" |
||||
+ " gl_FragColor = vec4(new_color, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private float strength = 0.5f; |
||||
private int multiplierLocation = -1; |
||||
private int gammaLocation = -1; |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
*/ |
||||
public FillLightFilter() { |
||||
} |
||||
public FillLightFilter() { } |
||||
|
||||
/** |
||||
* setStrength |
||||
* Sets the current strength. |
||||
* 0.0: no change. |
||||
* 1.0: max strength. |
||||
* |
||||
* @param strength Float, between 0.0 and 1.0 where 0.0 means no change. |
||||
* @param strength strength |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setStrength(float strength) { |
||||
if (strength < 0.0f) |
||||
strength = 0f; |
||||
else if (strength > 1.0f) |
||||
strength = 1f; |
||||
|
||||
if (strength < 0.0f) strength = 0f; |
||||
if (strength > 1.0f) strength = 1f; |
||||
this.strength = strength; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current strength. |
||||
* |
||||
* @see #setStrength(float) |
||||
* @return strength |
||||
*/ |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
public float getStrength() { |
||||
return strength; |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setStrength(value); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter1() { |
||||
return getStrength(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float fade_gamma = 0.3f; |
||||
float amt = 1.0f - strength; |
||||
float mult = 1.0f / (amt * 0.7f + 0.3f); |
||||
float faded = fade_gamma + (1.0f - fade_gamma) * mult; |
||||
float igamma = 1.0f / faded; |
||||
|
||||
String multString = "mult = " + mult + ";\n"; |
||||
String igammaString = "igamma = " + igamma + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " float mult;\n" |
||||
+ " float igamma;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main()\n" |
||||
+ "{\n" |
||||
// Parameters that were created above
|
||||
+ multString |
||||
+ igammaString |
||||
|
||||
+ " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float lightmask = dot(color.rgb, color_weights);\n" |
||||
+ " float backmask = (1.0 - lightmask);\n" |
||||
+ " vec3 ones = vec3(1.0, 1.0, 1.0);\n" |
||||
+ " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n" |
||||
+ " diff = min(diff, 1.0);\n" |
||||
+ " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n" |
||||
+ " gl_FragColor = vec4(new_color, color.a);\n" + "}\n"; |
||||
|
||||
return shader; |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
multiplierLocation = GLES20.glGetUniformLocation(programHandle, "mult"); |
||||
GlUtils.checkLocation(multiplierLocation, "mult"); |
||||
gammaLocation = GLES20.glGetUniformLocation(programHandle, "igamma"); |
||||
GlUtils.checkLocation(gammaLocation, "igamma"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
multiplierLocation = -1; |
||||
gammaLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float amount = 1.0f - strength; |
||||
float multiplier = 1.0f / (amount * 0.7f + 0.3f); |
||||
GLES20.glUniform1f(multiplierLocation, multiplier); |
||||
GlUtils.checkError("glUniform1f"); |
||||
|
||||
float fadeGamma = 0.3f; |
||||
float faded = fadeGamma + (1.0f - fadeGamma) * multiplier; |
||||
float gamma = 1.0f / faded; |
||||
GLES20.glUniform1f(gammaLocation, gamma); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,128 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
/** |
||||
* A Base abstract class that every effect must extend so that there is a common getShader method. |
||||
* <p> |
||||
* This class has a default Vertex Shader implementation which in most cases not required to touch. |
||||
* In Effects like sepia, B/W any many, only pixel color changes which can be managed by only fragment shader. |
||||
* If there is some other effect which requires vertex shader also change, you can override it. |
||||
* <p> |
||||
* If your provide your own vertex and fragment shader, |
||||
* please set the {@link #mPositionVariableName}, {@link #mTextureCoordinateVariableName}, |
||||
* {@link #mMVPMatrixVariableName}, {@link #mTextureMatrixVariableName} |
||||
* according to your shader code. |
||||
* <p> |
||||
* Please note that these shader applies on live preview as well as pictureSnapshot and videoSnapshot, |
||||
* we only support GLES11Ext.GL_TEXTURE_EXTERNAL_OES |
||||
* check EglViewport() |
||||
* <p> |
||||
* The default implementation of this class is NoEffect. |
||||
*/ |
||||
public abstract class Filter { |
||||
|
||||
/** |
||||
* Vertex shader code written in Shader Language (C) and stored as String. |
||||
* This wil be used by GL to apply any effect. |
||||
*/ |
||||
@NonNull |
||||
String mVertexShader = |
||||
"uniform mat4 uMVPMatrix;\n" + |
||||
"uniform mat4 uTexMatrix;\n" + |
||||
"attribute vec4 aPosition;\n" + |
||||
"attribute vec4 aTextureCoord;\n" + |
||||
"varying vec2 vTextureCoord;\n" + |
||||
"void main() {\n" + |
||||
" gl_Position = uMVPMatrix * aPosition;\n" + |
||||
" vTextureCoord = (uTexMatrix * aTextureCoord).xy;\n" + |
||||
"}\n"; |
||||
|
||||
|
||||
/** |
||||
* Fragment shader code written in Shader Language (C) and stored as String. |
||||
* This wil be used by GL to apply any effect. |
||||
*/ |
||||
@NonNull |
||||
String mFragmentShader = |
||||
"#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "void main() {\n" |
||||
+ " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" |
||||
+ "}\n"; |
||||
|
||||
/** |
||||
* Width and height of previewing GlSurfaceview. |
||||
* This will be used by a few effects. |
||||
*/ |
||||
int mPreviewingViewWidth = 0; |
||||
int mPreviewingViewHeight = 0; |
||||
|
||||
public void setPreviewingViewSize(int width, int height) { |
||||
mPreviewingViewWidth = width; |
||||
mPreviewingViewHeight = height; |
||||
} |
||||
|
||||
/** |
||||
* Local variable name which were used in the shader code. |
||||
* These will be used by openGL program to render these vertex and fragment shader |
||||
*/ |
||||
private String mPositionVariableName = "aPosition"; |
||||
private String mTextureCoordinateVariableName = "aTextureCoord"; |
||||
private String mMVPMatrixVariableName = "uMVPMatrix"; |
||||
private String mTextureMatrixVariableName = "uTexMatrix"; |
||||
|
||||
public String getPositionVariableName() { |
||||
return mPositionVariableName; |
||||
} |
||||
|
||||
public void setPositionVariableName(String positionVariableName) { |
||||
this.mPositionVariableName = positionVariableName; |
||||
} |
||||
|
||||
public String getTexttureCoordinateVariableName() { |
||||
return mTextureCoordinateVariableName; |
||||
} |
||||
|
||||
public void setTexttureCoordinateVariableName(String texttureCoordinateVariableName) { |
||||
this.mTextureCoordinateVariableName = texttureCoordinateVariableName; |
||||
} |
||||
|
||||
public String getMVPMatrixVariableName() { |
||||
return mMVPMatrixVariableName; |
||||
} |
||||
|
||||
public void setMVPMatrixVariableName(String mvpMatrixVariableName) { |
||||
this.mMVPMatrixVariableName = mvpMatrixVariableName; |
||||
} |
||||
|
||||
public String getTextureMatrixVariableName() { |
||||
return mTextureMatrixVariableName; |
||||
} |
||||
|
||||
public void setTextureMatrixVariableName(String textureMatrixVariableName) { |
||||
this.mTextureMatrixVariableName = textureMatrixVariableName; |
||||
} |
||||
|
||||
/** |
||||
* Get vertex Shader code |
||||
* |
||||
* @return complete shader code in C |
||||
*/ |
||||
@NonNull |
||||
public String getVertexShader() { |
||||
return mVertexShader; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get fragment Shader code |
||||
* |
||||
* @return complete shader code in C |
||||
*/ |
||||
@NonNull |
||||
public abstract String getFragmentShader(); |
||||
|
||||
} |
@ -1,124 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
public enum Filters { |
||||
NO_FILTER, |
||||
|
||||
AUTO_FIX_FILTER, |
||||
BLACK_AND_WHITE_FILTER, |
||||
BRIGHTNESS_FILTER, |
||||
CONTRAST_FILTER, |
||||
CROSS_PROCESS_FILTER, |
||||
DOCUMENTARY_FILTER, |
||||
DUO_TONE_COLOR_FILTER, |
||||
FILL_LIGHT_FILTER, |
||||
GAMMA_FILTER, |
||||
GRAIN_FILTER, |
||||
GREY_SCALE_FILTER, |
||||
HUE_FILTER, |
||||
INVERT_COLOR_FILTER, |
||||
LAMOISH_FILTER, |
||||
POSTERIZE_FILTER, |
||||
SATURATION_FILTER, |
||||
SEPIA_FILTER, |
||||
SHARPNESS_FILTER, |
||||
TEMPERATURE_FILTER, |
||||
TINT_FILTER, |
||||
VIGNETTE_FILTER; |
||||
|
||||
public Filter newInstance() { |
||||
Filter shaderEffect; |
||||
switch (this) { |
||||
|
||||
case AUTO_FIX_FILTER: |
||||
shaderEffect = new AutoFixFilter(); |
||||
break; |
||||
|
||||
case BLACK_AND_WHITE_FILTER: |
||||
shaderEffect = new BlackAndWhiteFilter(); |
||||
break; |
||||
|
||||
case BRIGHTNESS_FILTER: |
||||
shaderEffect = new BrightnessFilter(); |
||||
break; |
||||
|
||||
case CONTRAST_FILTER: |
||||
shaderEffect = new ContrastFilter(); |
||||
break; |
||||
|
||||
case CROSS_PROCESS_FILTER: |
||||
shaderEffect = new CrossProcessFilter(); |
||||
break; |
||||
|
||||
case DOCUMENTARY_FILTER: |
||||
shaderEffect = new DocumentaryFilter(); |
||||
break; |
||||
|
||||
case DUO_TONE_COLOR_FILTER: |
||||
shaderEffect = new DuotoneFilter(); |
||||
break; |
||||
|
||||
case FILL_LIGHT_FILTER: |
||||
shaderEffect = new FillLightFilter(); |
||||
break; |
||||
|
||||
case GAMMA_FILTER: |
||||
shaderEffect = new GammaFilter(); |
||||
break; |
||||
|
||||
case GRAIN_FILTER: |
||||
shaderEffect = new GrainFilter(); |
||||
break; |
||||
|
||||
case GREY_SCALE_FILTER: |
||||
shaderEffect = new GreyScaleFilter(); |
||||
break; |
||||
|
||||
case HUE_FILTER: |
||||
shaderEffect = new HueFilter(); |
||||
break; |
||||
|
||||
case INVERT_COLOR_FILTER: |
||||
shaderEffect = new InvertColorsFilter(); |
||||
break; |
||||
|
||||
case LAMOISH_FILTER: |
||||
shaderEffect = new LamoishFilter(); |
||||
break; |
||||
|
||||
case POSTERIZE_FILTER: |
||||
shaderEffect = new PosterizeFilter(); |
||||
break; |
||||
|
||||
case SATURATION_FILTER: |
||||
shaderEffect = new SaturationFilter(); |
||||
break; |
||||
|
||||
case SEPIA_FILTER: |
||||
shaderEffect = new SepiaFilter(); |
||||
break; |
||||
|
||||
case SHARPNESS_FILTER: |
||||
shaderEffect = new SharpnessFilter(); |
||||
break; |
||||
|
||||
case TEMPERATURE_FILTER: |
||||
shaderEffect = new TemperatureFilter(); |
||||
break; |
||||
|
||||
case TINT_FILTER: |
||||
shaderEffect = new TintFilter(); |
||||
break; |
||||
|
||||
case VIGNETTE_FILTER: |
||||
shaderEffect = new VignetteFilter(); |
||||
break; |
||||
|
||||
|
||||
case NO_FILTER: |
||||
default: |
||||
shaderEffect = new NoFilter(); |
||||
} |
||||
|
||||
return shaderEffect; |
||||
} |
||||
} |
@ -1,59 +1,90 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Apply Gamma Effect on preview being played |
||||
* Applies gamma correction to the frames. |
||||
*/ |
||||
public class GammaFilter extends Filter { |
||||
private float gammaValue = 2.0f; |
||||
public class GammaFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float gamma;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 textureColor = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);\n" |
||||
+ "}\n"; |
||||
|
||||
private float gamma = 2.0f; |
||||
private int gammaLocation = -1; |
||||
|
||||
public GammaFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the new gamma value in the 0.0 - 2.0 range. |
||||
* The 1.0 value means no correction will be applied. |
||||
* |
||||
* @param gamma gamma value |
||||
*/ |
||||
public GammaFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setGamma(float gamma) { |
||||
if (gamma < 0.0f) gamma = 0.0f; |
||||
if (gamma > 2.0f) gamma = 2.0f; |
||||
this.gamma = gamma; |
||||
} |
||||
|
||||
/** |
||||
* setGammaValue |
||||
* Returns the current gamma. |
||||
* |
||||
* @param gammaValue Range should be between 0.0 - 1.0 with 0.5 being normal. |
||||
* @see #setGamma(float) |
||||
* @return gamma |
||||
*/ |
||||
public void setGammaValue(float gammaValue) { |
||||
if (gammaValue < 0.0f) |
||||
gammaValue = 0.0f; |
||||
else if (gammaValue > 1.0f) |
||||
gammaValue = 1.0f; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getGamma() { |
||||
return gamma; |
||||
} |
||||
|
||||
//since the shader excepts a range of 0.0 - 2.0
|
||||
//will multiply the 2.0 to every value
|
||||
this.gammaValue = gammaValue * 2.0f; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setGamma(value * 2F); |
||||
} |
||||
|
||||
public float getGammaValue() { |
||||
//since the shader excepts a range of 0.0 - 2.0
|
||||
//to keep it between 0.0f - 1.0f range, will divide it with 2.0
|
||||
return gammaValue / 2.0f; |
||||
@Override |
||||
public float getParameter1() { |
||||
return getGamma() / 2F; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
|
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "float gamma=" + gammaValue + ";\n" |
||||
|
||||
+ "void main() {\n" |
||||
|
||||
+ "vec4 textureColor = texture2D(sTexture, vTextureCoord);\n" |
||||
+ "gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);\n" |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
gammaLocation = GLES20.glGetUniformLocation(programHandle, "gamma"); |
||||
GlUtils.checkLocation(gammaLocation, "gamma"); |
||||
} |
||||
|
||||
+ "}\n"; |
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
gammaLocation = -1; |
||||
} |
||||
|
||||
return shader; |
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(gammaLocation, gamma); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
@ -1,98 +1,144 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import java.util.Date; |
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
import java.util.Random; |
||||
|
||||
/** |
||||
* Applies film grain effect to preview. |
||||
* Applies film grain effect to the frames. |
||||
*/ |
||||
public class GrainFilter extends Filter { |
||||
public class GrainFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static Random RANDOM = new Random(); |
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "vec2 seed;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_0;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_1;\n" |
||||
+ "uniform float scale;\n" |
||||
+ "uniform float stepX;\n" |
||||
+ "uniform float stepY;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
+ " float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " float sum = (part1 + part2 + part3);\n" |
||||
+ " return fract(sum)*scale;\n" |
||||
+ "}\n" |
||||
+ "void main() {\n" |
||||
+ " seed[0] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " seed[1] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " float noise = texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, -stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, -stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, stepY)).r * 0.224;\n" |
||||
+ " noise += 0.4448;\n" |
||||
+ " noise *= scale;\n" |
||||
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n" |
||||
+ " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" |
||||
+ " float mask = (1.0 - sqrt(energy));\n" |
||||
+ " float weight = 1.0 - 1.333 * mask * noise;\n" |
||||
+ " gl_FragColor = vec4(color.rgb * weight, color.a);\n" |
||||
+ " gl_FragColor = gl_FragColor+vec4(rand(vTextureCoord + seed), rand(vTextureCoord + seed),rand(vTextureCoord + seed),1);\n" |
||||
+ "}\n"; |
||||
|
||||
private float strength = 0.5f; |
||||
private Random mRandom; |
||||
private int width = 1; |
||||
private int height = 1; |
||||
private int strengthLocation = -1; |
||||
private int stepXLocation = -1; |
||||
private int stepYLocation = -1; |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
*/ |
||||
public GrainFilter() { |
||||
mRandom = new Random(new Date().getTime()); |
||||
@SuppressWarnings("WeakerAccess") |
||||
public GrainFilter() { } |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
super.setSize(width, height); |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
/** |
||||
* setDistortionStrength |
||||
* Sets the current distortion strength. |
||||
* 0.0: no distortion. |
||||
* 1.0: maximum distortion. |
||||
* |
||||
* @param strength Float, between 0.0f and 1.0. Zero means no distortion, while 1 |
||||
* indicates the maximum amount of adjustment. |
||||
* @param strength strength |
||||
*/ |
||||
public void setDistortionStrength(float strength) { |
||||
if (strength < 0.0f) |
||||
strength = 0.0f; |
||||
else if (strength > 1.0f) |
||||
strength = 1.0f; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setStrength(float strength) { |
||||
if (strength < 0.0f) strength = 0.0f; |
||||
if (strength > 1.0f) strength = 1.0f; |
||||
this.strength = strength; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current strength. |
||||
* |
||||
* @see #setStrength(float) |
||||
* @return strength |
||||
*/ |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
public float getStrength() { |
||||
return strength; |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setStrength(value); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter1() { |
||||
return getStrength(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()}; |
||||
String scaleString = "scale = " + strength + ";\n"; |
||||
String seedString[] = new String[2]; |
||||
seedString[0] = "seed[0] = " + seed[0] + ";\n"; |
||||
seedString[1] = "seed[1] = " + seed[1] + ";\n"; |
||||
String stepX = "stepX = " + 0.5f / mPreviewingViewWidth + ";\n"; |
||||
String stepY = "stepY = " + 0.5f / mPreviewingViewHeight + ";\n"; |
||||
|
||||
// locString[1] = "loc[1] = loc[1]+" + seedString[1] + ";\n";
|
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ " vec2 seed;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_0;\n" |
||||
+ "uniform samplerExternalOES tex_sampler_1;\n" |
||||
+ "float scale;\n" |
||||
+ " float stepX;\n" |
||||
+ " float stepY;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
+ |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
" float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " float sum = (part1 + part2 + part3);\n" |
||||
+ " return fract(sum)*scale;\n" |
||||
+ "}\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ seedString[0] |
||||
+ seedString[1] |
||||
+ scaleString |
||||
+ stepX |
||||
+ stepY |
||||
+ " float noise = texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, -stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(-stepX, stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, -stepY)).r * 0.224;\n" |
||||
+ " noise += texture2D(tex_sampler_1, vTextureCoord + vec2(stepX, stepY)).r * 0.224;\n" |
||||
+ " noise += 0.4448;\n" |
||||
+ " noise *= scale;\n" |
||||
+ " vec4 color = texture2D(tex_sampler_0, vTextureCoord);\n" |
||||
+ " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" |
||||
+ " float mask = (1.0 - sqrt(energy));\n" |
||||
+ " float weight = 1.0 - 1.333 * mask * noise;\n" |
||||
+ " gl_FragColor = vec4(color.rgb * weight, color.a);\n" |
||||
+ " gl_FragColor = gl_FragColor+vec4(rand(vTextureCoord + seed), rand(vTextureCoord + seed),rand(vTextureCoord + seed),1);\n" |
||||
+ "}\n"; |
||||
return shader; |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
strengthLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(strengthLocation, "scale"); |
||||
stepXLocation = GLES20.glGetUniformLocation(programHandle, "stepX"); |
||||
GlUtils.checkLocation(stepXLocation, "stepX"); |
||||
stepYLocation = GLES20.glGetUniformLocation(programHandle, "stepY"); |
||||
GlUtils.checkLocation(stepYLocation, "stepY"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
strengthLocation = -1; |
||||
stepXLocation = -1; |
||||
stepYLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(strengthLocation, strength); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepXLocation, 0.5f / width); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepYLocation, 0.5f / height); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,29 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
|
||||
/** |
||||
* Converts frames to gray scale. |
||||
*/ |
||||
public class GrayscaleFilter extends BaseFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n" |
||||
+ " gl_FragColor = vec4(y, y, y, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
public GrayscaleFilter() { } |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
} |
@ -1,31 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
/** |
||||
* Converts preview to GreyScale. |
||||
*/ |
||||
public class GreyScaleFilter extends Filter { |
||||
/** |
||||
* Initialize Effect |
||||
*/ |
||||
public GreyScaleFilter() { |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "varying vec2 vTextureCoord;\n" + "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n" |
||||
+ " gl_FragColor = vec4(y, y, y, color.a);\n" + "}\n"; |
||||
; |
||||
|
||||
return shader; |
||||
|
||||
} |
||||
} |
@ -1,72 +1,106 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Apply Hue effect on the preview |
||||
* Applies a hue effect on the input frames. |
||||
*/ |
||||
public class HueFilter extends Filter { |
||||
float hueValue = 0.0f; |
||||
public class HueFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float hue;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n" |
||||
+ " vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n" |
||||
+ " vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n" |
||||
+ " vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n" |
||||
+ " vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n" |
||||
+ " vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float YPrime = dot(color, kRGBToYPrime);\n" |
||||
+ " float I = dot(color, kRGBToI);\n" |
||||
+ " float Q = dot(color, kRGBToQ);\n" |
||||
+ " float chroma = sqrt (I * I + Q * Q);\n" |
||||
+ " Q = chroma * sin (hue);\n" |
||||
+ " I = chroma * cos (hue);\n" |
||||
+ " vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n" |
||||
+ " color.r = dot (yIQ, kYIQToR);\n" |
||||
+ " color.g = dot (yIQ, kYIQToG);\n" |
||||
+ " color.b = dot (yIQ, kYIQToB);\n" |
||||
+ " gl_FragColor = color;\n" |
||||
+ "}\n"; |
||||
|
||||
private float hue = 0.0f; |
||||
private int hueLocation = -1; |
||||
|
||||
public HueFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the hue value in degrees. See the values chart: |
||||
* https://cloud.githubusercontent.com/assets/2201511/21810115/b99ac22a-d74a-11e6-9f6c-ef74d15c88c7.jpg
|
||||
* |
||||
* @param hue hue degrees |
||||
*/ |
||||
public HueFilter() { |
||||
@SuppressWarnings({"unused", "WeakerAccess"}) |
||||
public void setHue(float hue) { |
||||
this.hue = hue % 360; |
||||
} |
||||
|
||||
/** |
||||
* Hue value chart - https://cloud.githubusercontent.com/assets/2201511/21810115/b99ac22a-d74a-11e6-9f6c-ef74d15c88c7.jpg"
|
||||
* Returns the current hue value. |
||||
* |
||||
* @param hueDegrees Range of value should be between 0 to 360 degrees as described in the image above |
||||
* @see #setHue(float) |
||||
* @return hue |
||||
*/ |
||||
public void setHueDegreeValue(float hueDegrees) { |
||||
// manipulating input value so that we can map it on 360 degree circle
|
||||
hueValue = ((hueDegrees - 45) / 45f + 0.5f) * -1; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getHue() { |
||||
return hue; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
|
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "float hue=" + hueValue + ";\n" |
||||
|
||||
+ "void main() {\n" |
||||
|
||||
+ "vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);\n" |
||||
+ "vec4 kRGBToI = vec4 (0.595716, -0.274453, -0.321263, 0.0);\n" |
||||
+ "vec4 kRGBToQ = vec4 (0.211456, -0.522591, 0.31135, 0.0);\n" |
||||
|
||||
+ "vec4 kYIQToR = vec4 (1.0, 0.9563, 0.6210, 0.0);\n" |
||||
+ "vec4 kYIQToG = vec4 (1.0, -0.2721, -0.6474, 0.0);\n" |
||||
+ "vec4 kYIQToB = vec4 (1.0, -1.1070, 1.7046, 0.0);\n" |
||||
|
||||
|
||||
+ "vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
|
||||
+ "float YPrime = dot(color, kRGBToYPrime);\n" |
||||
+ "float I = dot(color, kRGBToI);\n" |
||||
+ "float Q = dot(color, kRGBToQ);\n" |
||||
|
||||
+ "float chroma = sqrt (I * I + Q * Q);\n" |
||||
|
||||
+ "Q = chroma * sin (hue);\n" |
||||
public void setParameter1(float value) { |
||||
setHue(value * 360F); |
||||
} |
||||
|
||||
+ "I = chroma * cos (hue);\n" |
||||
@Override |
||||
public float getParameter1() { |
||||
return getHue() / 360F; |
||||
} |
||||
|
||||
+ "vec4 yIQ = vec4 (YPrime, I, Q, 0.0);\n" |
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
+ "color.r = dot (yIQ, kYIQToR);\n" |
||||
+ "color.g = dot (yIQ, kYIQToG);\n" |
||||
+ "color.b = dot (yIQ, kYIQToB);\n" |
||||
+ "gl_FragColor = color;\n" |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
hueLocation = GLES20.glGetUniformLocation(programHandle, "hue"); |
||||
GlUtils.checkLocation(hueLocation, "hue"); |
||||
} |
||||
|
||||
+ "}\n"; |
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
hueLocation = -1; |
||||
} |
||||
|
||||
return shader; |
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
// map it on 360 degree circle
|
||||
float shaderHue = ((hue - 45) / 45f + 0.5f) * -1; |
||||
GLES20.glUniform1f(hueLocation, shaderHue); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
@ -1,143 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import java.util.Date; |
||||
import java.util.Random; |
||||
|
||||
/** |
||||
* Applies lomo-camera style effect to preview. |
||||
*/ |
||||
public class LamoishFilter extends Filter { |
||||
private Random mRandom; |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
*/ |
||||
public LamoishFilter() { |
||||
mRandom = new Random(new Date().getTime()); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float scale[] = new float[2]; |
||||
if (mPreviewingViewWidth > mPreviewingViewHeight) { |
||||
scale[0] = 1f; |
||||
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth; |
||||
} else { |
||||
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight; |
||||
scale[1] = 1f; |
||||
} |
||||
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] |
||||
* scale[1])) * 0.5f; |
||||
|
||||
float seed[] = {mRandom.nextFloat(), mRandom.nextFloat()}; |
||||
|
||||
String scaleString[] = new String[2]; |
||||
String seedString[] = new String[2]; |
||||
|
||||
scaleString[0] = "scale[0] = " + scale[0] + ";\n"; |
||||
scaleString[1] = "scale[1] = " + scale[1] + ";\n"; |
||||
|
||||
seedString[0] = "seed[0] = " + seed[0] + ";\n"; |
||||
seedString[1] = "seed[1] = " + seed[1] + ";\n"; |
||||
|
||||
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n"; |
||||
String stepsizeString = "stepsize = " + 1.0f / 255.0f + ";\n"; |
||||
String stepsizeXString = "stepsizeX = " + 1.0f / mPreviewingViewWidth + ";\n"; |
||||
String stepsizeYString = "stepsizeY = " + 1.0f / mPreviewingViewHeight + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " vec2 seed;\n" |
||||
+ " float stepsizeX;\n" |
||||
+ " float stepsizeY;\n" |
||||
+ " float stepsize;\n" |
||||
+ " vec2 scale;\n" |
||||
+ " float inv_max_dist;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
+ |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
" float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " return fract(part1 + part2 + part3);\n" + "}\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ scaleString[0] |
||||
+ scaleString[1] |
||||
+ seedString[0] |
||||
+ seedString[1] |
||||
+ inv_max_distString |
||||
+ stepsizeString |
||||
+ stepsizeXString |
||||
+ stepsizeYString |
||||
// sharpen
|
||||
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " vec2 coord;\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x - stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n" |
||||
+ |
||||
// cross process
|
||||
" vec3 c_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " float value;\n" |
||||
+ " if (s_color.r < 0.5) {\n" |
||||
+ " value = s_color.r;\n" |
||||
+ " } else {\n" |
||||
+ " value = 1.0 - s_color.r;\n" |
||||
+ " }\n" |
||||
+ " float red = 4.0 * value * value * value;\n" |
||||
+ " if (s_color.r < 0.5) {\n" |
||||
+ " c_color.r = red;\n" |
||||
+ " } else {\n" |
||||
+ " c_color.r = 1.0 - red;\n" |
||||
+ " }\n" |
||||
+ " if (s_color.g < 0.5) {\n" |
||||
+ " value = s_color.g;\n" |
||||
+ " } else {\n" |
||||
+ " value = 1.0 - s_color.g;\n" |
||||
+ " }\n" |
||||
+ " float green = 2.0 * value * value;\n" |
||||
+ " if (s_color.g < 0.5) {\n" |
||||
+ " c_color.g = green;\n" |
||||
+ " } else {\n" |
||||
+ " c_color.g = 1.0 - green;\n" |
||||
+ " }\n" |
||||
+ " c_color.b = s_color.b * 0.5 + 0.25;\n" |
||||
+ |
||||
// blackwhite
|
||||
" float dither = rand(vTextureCoord + seed);\n" |
||||
+ " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n" |
||||
+ " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n" |
||||
+ " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" |
||||
+ |
||||
// vignette
|
||||
" coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n" |
||||
+ " gl_FragColor = vec4(bw_color * lumen, color.a);\n" + "}\n"; |
||||
; |
||||
|
||||
return shader; |
||||
|
||||
} |
||||
} |
@ -0,0 +1,165 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
import java.util.Date; |
||||
import java.util.Random; |
||||
|
||||
/** |
||||
* Applies a lomo-camera style effect to the input frames. |
||||
*/ |
||||
public class LomoishFilter extends BaseFilter { |
||||
|
||||
private final static Random RANDOM = new Random(); |
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float stepsizeX;\n" |
||||
+ "uniform float stepsizeY;\n" |
||||
+ "uniform vec2 scale;\n" |
||||
+ "uniform float inv_max_dist;\n" |
||||
+ "vec2 seed;\n" |
||||
+ "float stepsize;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "float rand(vec2 loc) {\n" |
||||
+ " float theta1 = dot(loc, vec2(0.9898, 0.233));\n" |
||||
+ " float theta2 = dot(loc, vec2(12.0, 78.0));\n" |
||||
+ " float value = cos(theta1) * sin(theta2) + sin(theta1) * cos(theta2);\n" |
||||
// keep value of part1 in range: (2^-14 to 2^14).
|
||||
+ " float temp = mod(197.0 * value, 1.0) + value;\n" |
||||
+ " float part1 = mod(220.0 * temp, 1.0) + temp;\n" |
||||
+ " float part2 = value * 0.5453;\n" |
||||
+ " float part3 = cos(theta1 + theta2) * 0.43758;\n" |
||||
+ " return fract(part1 + part2 + part3);\n" |
||||
+ "}\n" |
||||
+ "void main() {\n" |
||||
+ " seed[0] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " seed[1] = " + RANDOM.nextFloat() + ";\n" |
||||
+ " stepsize = " + 1.0f / 255.0f + ";\n" |
||||
// sharpen
|
||||
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " vec2 coord;\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x - stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n" |
||||
// cross process
|
||||
+ " vec3 c_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " float value;\n" |
||||
+ " if (s_color.r < 0.5) {\n" |
||||
+ " value = s_color.r;\n" |
||||
+ " } else {\n" |
||||
+ " value = 1.0 - s_color.r;\n" |
||||
+ " }\n" |
||||
+ " float red = 4.0 * value * value * value;\n" |
||||
+ " if (s_color.r < 0.5) {\n" |
||||
+ " c_color.r = red;\n" |
||||
+ " } else {\n" |
||||
+ " c_color.r = 1.0 - red;\n" |
||||
+ " }\n" |
||||
+ " if (s_color.g < 0.5) {\n" |
||||
+ " value = s_color.g;\n" |
||||
+ " } else {\n" |
||||
+ " value = 1.0 - s_color.g;\n" |
||||
+ " }\n" |
||||
+ " float green = 2.0 * value * value;\n" |
||||
+ " if (s_color.g < 0.5) {\n" |
||||
+ " c_color.g = green;\n" |
||||
+ " } else {\n" |
||||
+ " c_color.g = 1.0 - green;\n" |
||||
+ " }\n" |
||||
+ " c_color.b = s_color.b * 0.5 + 0.25;\n" |
||||
// blackwhite
|
||||
+ " float dither = rand(vTextureCoord + seed);\n" |
||||
+ " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n" |
||||
+ " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n" |
||||
+ " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" |
||||
// vignette
|
||||
+ " coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n" |
||||
+ " gl_FragColor = vec4(bw_color * lumen, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private int width = 1; |
||||
private int height = 1; |
||||
|
||||
private int scaleLocation = -1; |
||||
private int maxDistLocation = -1; |
||||
private int stepSizeXLocation = -1; |
||||
private int stepSizeYLocation = -1; |
||||
|
||||
public LomoishFilter() { } |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
super.setSize(width, height); |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(scaleLocation, "scale"); |
||||
maxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); |
||||
GlUtils.checkLocation(maxDistLocation, "inv_max_dist"); |
||||
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); |
||||
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); |
||||
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); |
||||
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
scaleLocation = -1; |
||||
maxDistLocation = -1; |
||||
stepSizeXLocation = -1; |
||||
stepSizeYLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float[] scale = new float[2]; |
||||
if (width > height) { |
||||
scale[0] = 1f; |
||||
scale[1] = ((float) height) / width; |
||||
} else { |
||||
scale[0] = ((float) width) / height; |
||||
scale[1] = 1f; |
||||
} |
||||
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; |
||||
GLES20.glUniform2fv(scaleLocation, 1, scale, 0); |
||||
GlUtils.checkError("glUniform2fv"); |
||||
GLES20.glUniform1f(maxDistLocation, 1.0F / maxDist); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
@ -1,12 +0,0 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
public class NoFilter extends Filter { |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return mFragmentShader; |
||||
} |
||||
} |
@ -1,102 +1,124 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Adjusts color saturation of preview. |
||||
* Adjusts color saturation. |
||||
*/ |
||||
public class SaturationFilter extends Filter { |
||||
private float scale = 1.0f; |
||||
public class SaturationFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float scale;\n" |
||||
+ "uniform vec3 exponents;\n" |
||||
+ "float shift;\n" |
||||
+ "vec3 weights;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " weights[0] = " + 2f / 8f + ";\n" |
||||
+ " weights[1] = " + 5f / 8f + ";\n" |
||||
+ " weights[2] = " + 1f / 8f + ";\n" |
||||
+ " shift = " + 1.0f / 255.0f + ";\n" |
||||
+ " vec4 oldcolor = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float kv = dot(oldcolor.rgb, weights) + shift;\n" |
||||
+ " vec3 new_color = scale * oldcolor.rgb + (1.0 - scale) * kv;\n" |
||||
+ " gl_FragColor = vec4(new_color, oldcolor.a);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float de = dot(color.rgb, weights);\n" |
||||
+ " float inv_de = 1.0 / de;\n" |
||||
+ " vec3 verynew_color = de * pow(color.rgb * inv_de, exponents);\n" |
||||
+ " float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0);\n" |
||||
+ " gl_FragColor = gl_FragColor+vec4(verynew_color / max_color, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private float scale = 1F; // -1...1
|
||||
private int scaleLocation = -1; |
||||
private int exponentsLocation = -1; |
||||
|
||||
public SaturationFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the saturation correction value: |
||||
* -1.0: fully desaturated, grayscale. |
||||
* 0.0: no change. |
||||
* +1.0: fully saturated. |
||||
* |
||||
* @param value new value |
||||
*/ |
||||
public SaturationFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setSaturation(float value) { |
||||
if (value < -1F) value = -1F; |
||||
if (value > 1F) value = 1F; |
||||
scale = value; |
||||
} |
||||
|
||||
/** |
||||
* @param value Float, between 0.0 and 1. 0 means no change, while 0.0 indicates |
||||
* full desaturated, i.e. grayscale. |
||||
* and 1.0 indicates full saturation |
||||
* Returns the current saturation. |
||||
* |
||||
* @see #setSaturation(float) |
||||
* @return saturation |
||||
*/ |
||||
public void setSaturationValue(float value) { |
||||
if (value < 0.0f) |
||||
value = 0.0f; |
||||
else if (value > 1.0f) |
||||
value = 1.0f; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getSaturation() { |
||||
return scale; |
||||
} |
||||
|
||||
//since the shader excepts a range of -1.0 to 1.0
|
||||
//will multiply it by 2.0f and subtract 1.0 to every value
|
||||
this.scale = (2.0f * value) - 1.0f; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setSaturation(2F * value - 1F); |
||||
} |
||||
|
||||
public float getSaturationValue() { |
||||
//since the shader excepts a range of -1.0 to 1.0
|
||||
//will add 1.0 to every value and divide it by 2.0f
|
||||
return (scale + 1.0f) / 2.0f; |
||||
@Override |
||||
public float getParameter1() { |
||||
return (getSaturation() + 1F) / 2F; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float shift = 1.0f / 255.0f; |
||||
float weights[] = {2f / 8f, 5f / 8f, 1f / 8f}; |
||||
float exponents[] = new float[3]; |
||||
|
||||
String weightsString[] = new String[3]; |
||||
String exponentsString[] = new String[3]; |
||||
exponentsString[0] = ""; |
||||
exponentsString[1] = ""; |
||||
exponentsString[2] = ""; |
||||
String scaleString = ""; |
||||
|
||||
if (scale > 0.0f) { |
||||
exponents[0] = (0.9f * scale) + 1.0f; |
||||
exponents[1] = (2.1f * scale) + 1.0f; |
||||
exponents[2] = (2.7f * scale) + 1.0f; |
||||
exponentsString[0] = "exponents[0] = " + exponents[0] + ";\n"; |
||||
exponentsString[1] = "exponents[1] = " + exponents[1] + ";\n"; |
||||
exponentsString[2] = "exponents[2] = " + exponents[2] + ";\n"; |
||||
} else |
||||
scaleString = "scale = " + (1.0f + scale) + ";\n"; |
||||
|
||||
weightsString[0] = "weights[0] = " + weights[0] + ";\n"; |
||||
weightsString[1] = "weights[1] = " + weights[1] + ";\n"; |
||||
weightsString[2] = "weights[2] = " + weights[2] + ";\n"; |
||||
String shiftString = "shift = " + shift + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" + " float scale;\n" |
||||
+ " float shift;\n" + " vec3 weights;\n" + " vec3 exponents;\n" |
||||
+ "varying vec2 vTextureCoord;\n" + "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ weightsString[0] |
||||
+ weightsString[1] |
||||
+ weightsString[2] |
||||
+ shiftString |
||||
+ scaleString |
||||
+ " vec4 oldcolor = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float kv = dot(oldcolor.rgb, weights) + shift;\n" |
||||
+ " vec3 new_color = scale * oldcolor.rgb + (1.0 - scale) * kv;\n" |
||||
+ " gl_FragColor= vec4(new_color, oldcolor.a);\n" |
||||
// Parameters that were created above
|
||||
+ weightsString[0] |
||||
+ weightsString[1] |
||||
+ weightsString[2] |
||||
+ exponentsString[0] |
||||
+ exponentsString[1] |
||||
+ exponentsString[2] |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float de = dot(color.rgb, weights);\n" |
||||
+ " float inv_de = 1.0 / de;\n" |
||||
+ " vec3 verynew_color = de * pow(color.rgb * inv_de, exponents);\n" |
||||
+ " float max_color = max(max(max(verynew_color.r, verynew_color.g), verynew_color.b), 1.0);\n" |
||||
+ " gl_FragColor = gl_FragColor+vec4(verynew_color / max_color, color.a);\n" |
||||
+ "}\n"; |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
return shader; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(scaleLocation, "scale"); |
||||
exponentsLocation = GLES20.glGetUniformLocation(programHandle, "exponents"); |
||||
GlUtils.checkLocation(exponentsLocation, "exponents"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
scaleLocation = -1; |
||||
exponentsLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
if (scale > 0.0f) { |
||||
GLES20.glUniform1f(scaleLocation, 0F); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform3f(exponentsLocation, |
||||
(0.9f * scale) + 1.0f, |
||||
(2.1f * scale) + 1.0f, |
||||
(2.7f * scale) + 1.0f |
||||
); |
||||
GlUtils.checkError("glUniform3f"); |
||||
} else { |
||||
GLES20.glUniform1f(scaleLocation, 1.0F + scale); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform3f(exponentsLocation, 0F, 0F, 0F); |
||||
GlUtils.checkError("glUniform3f"); |
||||
} |
||||
} |
||||
} |
||||
|
@ -1,75 +1,128 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Sharpens the preview. |
||||
* Sharpens the input frames. |
||||
*/ |
||||
public class SharpnessFilter extends Filter { |
||||
public class SharpnessFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float scale;\n" |
||||
+ "uniform float stepsizeX;\n" |
||||
+ "uniform float stepsizeY;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " vec2 coord;\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x - stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private float scale = 0.5f; |
||||
private int width = 1; |
||||
private int height = 1; |
||||
private int scaleLocation = -1; |
||||
private int stepSizeXLocation = -1; |
||||
private int stepSizeYLocation = -1; |
||||
|
||||
public SharpnessFilter() { } |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
super.setSize(width, height); |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the current sharpness value: |
||||
* 0.0: no change. |
||||
* 1.0: maximum sharpness. |
||||
* |
||||
* @param value new sharpness |
||||
*/ |
||||
public SharpnessFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setSharpness(float value) { |
||||
if (value < 0.0f) value = 0.0f; |
||||
if (value > 1.0f) value = 1.0f; |
||||
this.scale = value; |
||||
} |
||||
|
||||
/** |
||||
* @param value Float, between 0 and 1. 0 means no change. |
||||
* Returns the current sharpness. |
||||
* |
||||
* @see #setSharpness(float) |
||||
* @return sharpness |
||||
*/ |
||||
public void setSharpnessValue(float value) { |
||||
if (value < 0.0f) |
||||
value = 0.0f; |
||||
else if (value > 1.0f) |
||||
value = 1.0f; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getSharpness() { |
||||
return scale; |
||||
} |
||||
|
||||
this.scale = value; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setSharpness(value); |
||||
} |
||||
|
||||
public float getSharpnessValue() { |
||||
return scale; |
||||
@Override |
||||
public float getParameter1() { |
||||
return getSharpness(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String stepsizeXString = "stepsizeX = " + 1.0f / mPreviewingViewWidth + ";\n"; |
||||
String stepsizeYString = "stepsizeY = " + 1.0f / mPreviewingViewHeight + ";\n"; |
||||
String scaleString = "scale = " + scale + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " float scale;\n" |
||||
+ " float stepsizeX;\n" |
||||
+ " float stepsizeY;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ stepsizeXString |
||||
+ stepsizeYString |
||||
+ scaleString |
||||
+ " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" |
||||
+ " vec2 coord;\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " coord.x = vTextureCoord.x - 0.5 * stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x - stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y - 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " coord.x = vTextureCoord.x + stepsizeX;\n" |
||||
+ " coord.y = vTextureCoord.y + 0.5 * stepsizeY;\n" |
||||
+ " nbr_color += texture2D(sTexture, coord).rgb - color.rgb;\n" |
||||
+ " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
return shader; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(scaleLocation, "scale"); |
||||
stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); |
||||
GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); |
||||
stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); |
||||
GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
scaleLocation = -1; |
||||
stepSizeXLocation = -1; |
||||
stepSizeYLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(scaleLocation, scale); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); |
||||
GlUtils.checkError("glUniform1f"); |
||||
GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,61 +1,102 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
/** |
||||
* Adjusts color temperature of the preview. |
||||
* Adjusts color temperature. |
||||
*/ |
||||
public class TemperatureFilter extends Filter { |
||||
private float scale = 0f; |
||||
public class TemperatureFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " vec3 new_color = color.rgb;\n" |
||||
+ " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n" |
||||
+ " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n" |
||||
+ " if (scale > 0.0) { \n" |
||||
+ " new_color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n" |
||||
+ " }\n" |
||||
+ " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n" |
||||
+ " if (max_value > 1.0) { \n" |
||||
+ " new_color /= max_value;\n" |
||||
+ " } \n" |
||||
+ " gl_FragColor = vec4(new_color, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private float scale = 1F; // -1...1
|
||||
private int scaleLocation = -1; |
||||
|
||||
public TemperatureFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the new temperature value: |
||||
* -1.0: cool colors |
||||
* 0.0: no change |
||||
* 1.0: warm colors |
||||
* |
||||
* @param value new value |
||||
*/ |
||||
public TemperatureFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setTemperature(float value) { |
||||
if (value < -1F) value = -1F; |
||||
if (value > 1F) value = 1F; |
||||
this.scale = value; |
||||
} |
||||
|
||||
/** |
||||
* @param scale Float, between 0 and 1, with 0 indicating cool, and 1 |
||||
* indicating warm. A value of of 0.5 indicates no change. |
||||
* Returns the current temperature. |
||||
* |
||||
* @see #setTemperature(float) |
||||
* @return temperature |
||||
*/ |
||||
public void setTemperatureScale(float scale) { |
||||
if (scale < 0.0f) |
||||
scale = 0.0f; |
||||
else if (scale > 1.0f) |
||||
scale = 1.0f; |
||||
this.scale = scale; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getTemperature() { |
||||
return scale; |
||||
} |
||||
|
||||
public float getTemperatureScale() { |
||||
return scale; |
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setTemperature((2F * value - 1F)); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter1() { |
||||
return (getTemperature() + 1F) / 2F; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
String scaleString = "scale = " + (2.0f * scale - 1.0f) + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " float scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" // Parameters that were created above
|
||||
+ scaleString |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " vec3 new_color = color.rgb;\n" |
||||
+ " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n" |
||||
+ " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n" |
||||
+ " if (scale > 0.0) { \n" |
||||
+ " new_color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n" |
||||
+ " }\n" |
||||
+ " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n" |
||||
+ " if (max_value > 1.0) { \n" |
||||
+ " new_color /= max_value;\n" + " } \n" |
||||
+ " gl_FragColor = vec4(new_color, color.a);\n" + "}\n"; |
||||
|
||||
return shader; |
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(scaleLocation, "scale"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
scaleLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
GLES20.glUniform1f(scaleLocation, scale); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -1,66 +1,101 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.graphics.Color; |
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.ColorInt; |
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.OneParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
|
||||
/** |
||||
* Tints the preview with specified color.. |
||||
* Tints the frames with specified color. |
||||
*/ |
||||
public class TintFilter extends Filter { |
||||
private int mTint = 0xFFFF0000; |
||||
public class TintFilter extends BaseFilter implements OneParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform vec3 tint;\n" |
||||
+ "vec3 color_ratio;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " color_ratio[0] = " + 0.21f + ";\n" |
||||
+ " color_ratio[1] = " + 0.71f + ";\n" |
||||
+ " color_ratio[2] = " + 0.07f + ";\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float avg_color = dot(color_ratio, color.rgb);\n" |
||||
+ " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n" |
||||
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n"; |
||||
|
||||
private int tint = Color.RED; |
||||
private int tintLocation = -1; |
||||
|
||||
public TintFilter() { } |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the current tint. |
||||
* @param color current tint |
||||
*/ |
||||
public TintFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setTint(@ColorInt int color) { |
||||
this.tint = color; |
||||
} |
||||
|
||||
public void setTintColor(int color) { |
||||
this.mTint = color; |
||||
/** |
||||
* Returns the current tint. |
||||
* |
||||
* @see #setTint(int) |
||||
* @return tint |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
@ColorInt |
||||
public int getTint() { |
||||
return tint; |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter1(float value) { |
||||
// no easy way to transform 0...1 into a color.
|
||||
setTint((int) (value * Integer.MAX_VALUE)); |
||||
} |
||||
|
||||
public int getTintColor() { |
||||
return mTint; |
||||
@Override |
||||
public float getParameter1() { |
||||
return (float) getTint() / Integer.MAX_VALUE; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float color_ratio[] = {0.21f, 0.71f, 0.07f}; |
||||
String color_ratioString[] = new String[3]; |
||||
color_ratioString[0] = "color_ratio[0] = " + color_ratio[0] + ";\n"; |
||||
color_ratioString[1] = "color_ratio[1] = " + color_ratio[1] + ";\n"; |
||||
color_ratioString[2] = "color_ratio[2] = " + color_ratio[2] + ";\n"; |
||||
|
||||
float tint_color[] = {Color.red(mTint) / 255f, |
||||
Color.green(mTint) / 255f, Color.blue(mTint) / 255f}; |
||||
|
||||
String tintString[] = new String[3]; |
||||
tintString[0] = "tint[0] = " + tint_color[0] + ";\n"; |
||||
tintString[1] = "tint[1] = " + tint_color[1] + ";\n"; |
||||
tintString[2] = "tint[2] = " + tint_color[2] + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " vec3 tint;\n" |
||||
+ " vec3 color_ratio;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ color_ratioString[0] |
||||
+ color_ratioString[1] |
||||
+ color_ratioString[2] |
||||
+ tintString[0] |
||||
+ tintString[1] |
||||
+ tintString[2] |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " float avg_color = dot(color_ratio, color.rgb);\n" |
||||
+ " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n" |
||||
+ " gl_FragColor = vec4(new_color.rgb, color.a);\n" + "}\n"; |
||||
return shader; |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
tintLocation = GLES20.glGetUniformLocation(programHandle, "tint"); |
||||
GlUtils.checkLocation(tintLocation, "tint"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
tintLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float[] channels = new float[]{ |
||||
Color.red(tint) / 255f, |
||||
Color.green(tint) / 255f, |
||||
Color.blue(tint) / 255f |
||||
}; |
||||
GLES20.glUniform3fv(tintLocation, 1, channels, 0); |
||||
GlUtils.checkError("glUniform3fv"); |
||||
} |
||||
} |
||||
|
@ -1,99 +1,174 @@ |
||||
package com.otaliastudios.cameraview.filters; |
||||
|
||||
import android.opengl.GLES20; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.BaseFilter; |
||||
import com.otaliastudios.cameraview.filter.TwoParameterFilter; |
||||
import com.otaliastudios.cameraview.internal.GlUtils; |
||||
|
||||
|
||||
/** |
||||
* Applies lomo-camera style effect to your preview. |
||||
* Applies a vignette effect to input frames. |
||||
*/ |
||||
public class VignetteFilter extends Filter { |
||||
private float mScale = 0.85f; |
||||
private float mShade = 0.5f; |
||||
public class VignetteFilter extends BaseFilter implements TwoParameterFilter { |
||||
|
||||
private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ "uniform float range;\n" |
||||
+ "uniform float inv_max_dist;\n" |
||||
+ "uniform float shade;\n" |
||||
+ "uniform vec2 scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
+ " const float slope = 20.0;\n" |
||||
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " gl_FragColor = vec4(color.rgb * lumen, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
private float mScale = 0.85f; // 0...1
|
||||
private float mShade = 0.5f; // 0...1
|
||||
private int mWidth = 1; |
||||
private int mHeight = 1; |
||||
|
||||
private int mRangeLocation = -1; |
||||
private int mMaxDistLocation = -1; |
||||
private int mShadeLocation = -1; |
||||
private int mScaleLocation = -1; |
||||
|
||||
public VignetteFilter() { } |
||||
|
||||
@Override |
||||
public void setSize(int width, int height) { |
||||
super.setSize(width, height); |
||||
mWidth = width; |
||||
mHeight = height; |
||||
} |
||||
|
||||
/** |
||||
* Initialize Effect |
||||
* Sets the vignette effect scale (0.0 - 1.0). |
||||
* @param scale new scale |
||||
*/ |
||||
public VignetteFilter() { |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setVignetteScale(float scale) { |
||||
if (scale < 0.0f) scale = 0.0f; |
||||
if (scale > 1.0f) scale = 1.0f; |
||||
mScale = scale; |
||||
} |
||||
|
||||
/** |
||||
* setVignetteEffectScale |
||||
* Sets the vignette effect shade (0.0 - 1.0). |
||||
* @param shade new shade |
||||
*/ |
||||
@SuppressWarnings("WeakerAccess") |
||||
public void setVignetteShade(float shade) { |
||||
if (shade < 0.0f) shade = 0.0f; |
||||
if (shade > 1.0f) shade = 1.0f; |
||||
this.mShade = shade; |
||||
} |
||||
|
||||
/** |
||||
* Gets the current vignette scale. |
||||
* |
||||
* @param scale Float, between 0.0 and 1. 0 |
||||
* @see #setVignetteScale(float) |
||||
* @return scale |
||||
*/ |
||||
public void setVignetteEffectScale(float scale) { |
||||
if (scale < 0.0f) |
||||
scale = 0.0f; |
||||
else if (scale > 1.0f) |
||||
scale = 1.0f; |
||||
this.mScale = scale; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getVignetteScale() { |
||||
return mScale; |
||||
} |
||||
|
||||
/** |
||||
* setVignetteEffectShade |
||||
* Gets the current vignette shade. |
||||
* |
||||
* @param shade Float, between 0.0 and 1. 0 |
||||
* @see #setVignetteShade(float) |
||||
* @return shade |
||||
*/ |
||||
public void setVignetteEffectShade(float shade) { |
||||
if (shade < 0.0f) |
||||
shade = 0.0f; |
||||
else if (shade > 1.0f) |
||||
shade = 1.0f; |
||||
this.mShade = shade; |
||||
@SuppressWarnings("WeakerAccess") |
||||
public float getVignetteShade() { |
||||
return mShade; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setParameter1(float value) { |
||||
setVignetteScale(value); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter1() { |
||||
return getVignetteScale(); |
||||
} |
||||
|
||||
@Override |
||||
public void setParameter2(float value) { |
||||
setVignetteShade(value); |
||||
} |
||||
|
||||
@Override |
||||
public float getParameter2() { |
||||
return getVignetteShade(); |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public String getFragmentShader() { |
||||
float scale[] = new float[2]; |
||||
if (mPreviewingViewWidth > mPreviewingViewHeight) { |
||||
return FRAGMENT_SHADER; |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(int programHandle) { |
||||
super.onCreate(programHandle); |
||||
mRangeLocation = GLES20.glGetUniformLocation(programHandle, "range"); |
||||
GlUtils.checkLocation(mRangeLocation, "range"); |
||||
mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); |
||||
GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); |
||||
mShadeLocation = GLES20.glGetUniformLocation(programHandle, "shade"); |
||||
GlUtils.checkLocation(mShadeLocation, "shade"); |
||||
mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); |
||||
GlUtils.checkLocation(mScaleLocation, "scale"); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroy() { |
||||
super.onDestroy(); |
||||
mRangeLocation = -1; |
||||
mMaxDistLocation = -1; |
||||
mShadeLocation = -1; |
||||
mScaleLocation = -1; |
||||
} |
||||
|
||||
@Override |
||||
protected void onPreDraw(float[] transformMatrix) { |
||||
super.onPreDraw(transformMatrix); |
||||
float[] scale = new float[2]; |
||||
if (mWidth > mHeight) { |
||||
scale[0] = 1f; |
||||
scale[1] = ((float) mPreviewingViewHeight) / mPreviewingViewWidth; |
||||
scale[1] = ((float) mHeight) / mWidth; |
||||
} else { |
||||
scale[0] = ((float) mPreviewingViewWidth) / mPreviewingViewHeight; |
||||
scale[0] = ((float) mWidth) / mHeight; |
||||
scale[1] = 1f; |
||||
} |
||||
float max_dist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] |
||||
* scale[1])) * 0.5f; |
||||
GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); |
||||
GlUtils.checkError("glUniform2fv"); |
||||
|
||||
String scaleString[] = new String[2]; |
||||
float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; |
||||
GLES20.glUniform1f(mMaxDistLocation, 1F / maxDist); |
||||
GlUtils.checkError("glUniform1f"); |
||||
|
||||
scaleString[0] = "scale[0] = " + scale[0] + ";\n"; |
||||
scaleString[1] = "scale[1] = " + scale[1] + ";\n"; |
||||
String inv_max_distString = "inv_max_dist = " + 1.0f / max_dist + ";\n"; |
||||
String shadeString = "shade = " + mShade + ";\n"; |
||||
GLES20.glUniform1f(mShadeLocation, mShade); |
||||
GlUtils.checkError("glUniform1f"); |
||||
|
||||
// The 'range' is between 1.3 to 0.6. When scale is zero then range is
|
||||
// 1.3
|
||||
// The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3
|
||||
// which means no vignette at all because the luminousity difference is
|
||||
// less than 1/256 and will cause nothing.
|
||||
String rangeString = "range = " |
||||
+ (1.30f - (float) Math.sqrt(mScale) * 0.7f) + ";\n"; |
||||
|
||||
String shader = "#extension GL_OES_EGL_image_external : require\n" |
||||
+ "precision mediump float;\n" |
||||
+ "uniform samplerExternalOES sTexture;\n" |
||||
+ " float range;\n" |
||||
+ " float inv_max_dist;\n" |
||||
+ " float shade;\n" |
||||
+ " vec2 scale;\n" |
||||
+ "varying vec2 vTextureCoord;\n" |
||||
+ "void main() {\n" |
||||
// Parameters that were created above
|
||||
+ scaleString[0] |
||||
+ scaleString[1] |
||||
+ inv_max_distString |
||||
+ shadeString |
||||
+ rangeString |
||||
+ " const float slope = 20.0;\n" |
||||
+ " vec2 coord = vTextureCoord - vec2(0.5, 0.5);\n" |
||||
+ " float dist = length(coord * scale);\n" |
||||
+ " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n" |
||||
+ " vec4 color = texture2D(sTexture, vTextureCoord);\n" |
||||
+ " gl_FragColor = vec4(color.rgb * lumen, color.a);\n" |
||||
+ "}\n"; |
||||
|
||||
return shader; |
||||
|
||||
float range = (1.30f - (float) Math.sqrt(mScale) * 0.7f); |
||||
GLES20.glUniform1f(mRangeLocation, range); |
||||
GlUtils.checkError("glUniform1f"); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,39 @@ |
||||
package com.otaliastudios.cameraview.preview; |
||||
|
||||
|
||||
import android.content.Context; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
|
||||
import com.otaliastudios.cameraview.filter.Filter; |
||||
|
||||
|
||||
/** |
||||
* A preview that support GL filters defined through the {@link Filter} interface. |
||||
* |
||||
* The preview has the responsibility of calling {@link Filter#setSize(int, int)} |
||||
* whenever the preview size changes and as soon as the filter is applied. |
||||
*/ |
||||
public abstract class FilterCameraPreview<T extends View, Output> extends CameraPreview<T, Output> { |
||||
|
||||
@SuppressWarnings("WeakerAccess") |
||||
public FilterCameraPreview(@NonNull Context context, @NonNull ViewGroup parent) { |
||||
super(context, parent); |
||||
} |
||||
|
||||
/** |
||||
* Sets a new filter. |
||||
* @param filter new filter |
||||
*/ |
||||
public abstract void setFilter(@NonNull Filter filter); |
||||
|
||||
/** |
||||
* Returns the currently used filter. |
||||
* @return currently used filter |
||||
*/ |
||||
@SuppressWarnings("unused") |
||||
@NonNull |
||||
public abstract Filter getCurrentFilter(); |
||||
} |
@ -1,4 +1,27 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="cameraview_default_autofocus_marker">com.otaliastudios.cameraview.markers.DefaultAutoFocusMarker</string> |
||||
|
||||
<string name="cameraview_filter_none">com.otaliastudios.cameraview.filter.NoFilter</string> |
||||
<string name="cameraview_filter_autofix">com.otaliastudios.cameraview.filters.AutoFixFilter</string> |
||||
<string name="cameraview_filter_black_and_white">com.otaliastudios.cameraview.filters.BlackAndWhiteFilter</string> |
||||
<string name="cameraview_filter_brightness">com.otaliastudios.cameraview.filters.BrightnessFilter</string> |
||||
<string name="cameraview_filter_contrast">com.otaliastudios.cameraview.filters.ContrastFilter</string> |
||||
<string name="cameraview_filter_cross_process">com.otaliastudios.cameraview.filters.CrossProcessFilter</string> |
||||
<string name="cameraview_filter_documentary">com.otaliastudios.cameraview.filters.DocumentaryFilter</string> |
||||
<string name="cameraview_filter_duotone">com.otaliastudios.cameraview.filters.DuotoneFilter</string> |
||||
<string name="cameraview_filter_fill_light">com.otaliastudios.cameraview.filters.FillLightFilter</string> |
||||
<string name="cameraview_filter_gamma">com.otaliastudios.cameraview.filters.GammaFilter</string> |
||||
<string name="cameraview_filter_grain">com.otaliastudios.cameraview.filters.GrainFilter</string> |
||||
<string name="cameraview_filter_grayscale">com.otaliastudios.cameraview.filters.GrayscaleFilter</string> |
||||
<string name="cameraview_filter_hue">com.otaliastudios.cameraview.filters.HueFilter</string> |
||||
<string name="cameraview_filter_invert_colors">com.otaliastudios.cameraview.filters.InvertColorsFilter</string> |
||||
<string name="cameraview_filter_lomoish">com.otaliastudios.cameraview.filters.LomoishFilter</string> |
||||
<string name="cameraview_filter_posterize">com.otaliastudios.cameraview.filters.PosterizeFilter</string> |
||||
<string name="cameraview_filter_saturation">com.otaliastudios.cameraview.filters.SaturationFilter</string> |
||||
<string name="cameraview_filter_sepia">com.otaliastudios.cameraview.filters.SepiaFilter</string> |
||||
<string name="cameraview_filter_sharpness">com.otaliastudios.cameraview.filters.SharpnessFilter</string> |
||||
<string name="cameraview_filter_temperature">com.otaliastudios.cameraview.filters.TemperatureFilter</string> |
||||
<string name="cameraview_filter_tint">com.otaliastudios.cameraview.filters.TintFilter</string> |
||||
<string name="cameraview_filter_vignette">com.otaliastudios.cameraview.filters.VignetteFilter</string> |
||||
</resources> |
@ -0,0 +1,102 @@ |
||||
--- |
||||
layout: page |
||||
title: "Real-time Filters" |
||||
subtitle: "Apply filters to preview and snapshots" |
||||
description: "Apply filters to preview and snapshots" |
||||
category: docs |
||||
order: 11 |
||||
date: 2019-08-06 17:10:17 |
||||
disqus: 1 |
||||
--- |
||||
|
||||
Starting from version `2.1.0`, CameraView experimentally supports real-time filters that can modify |
||||
the camera frames before they are shown and recorded. Just like [overlays](watermarks-and-overlays.html), |
||||
these filters are applied to the preview and to any [picture or video snapshots](capturing-media.html). |
||||
|
||||
Conditions: |
||||
|
||||
- you must set the experimental flag: `app:cameraExperimental="true"` |
||||
- you must use `Preview.GL_SURFACE` as a preview |
||||
|
||||
### Simple usage |
||||
|
||||
```xml |
||||
<com.otaliastudios.cameraview.CameraView |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
app:cameraFilter="@string/cameraview_filter_none"/> |
||||
``` |
||||
|
||||
Real-time filters are applied at creation time, through the `app:cameraFilter` XML attribute, |
||||
or anytime during the camera lifecycle using `cameraView.setFilter()`. |
||||
|
||||
We offers a reasonable amount of filters through the `Filters` class, for example: |
||||
|
||||
```java |
||||
cameraView.setFilter(Filters.BLACK_AND_WHITE.newInstance()); |
||||
cameraView.setFilter(Filters.VIGNETTE.newInstance()); |
||||
cameraView.setFilter(Filters.SEPIA.newInstance()); |
||||
``` |
||||
|
||||
All of the filters stored in the `Filters` class have an XML string resource that you can use |
||||
to quickly setup the camera view. For example, `Filters.BLACK_AND_WHITE` can be set by using |
||||
`app:cameraFilter="@string/cameraview_filter_black_and_white`. |
||||
|
||||
The default filter is called `NoFilter` (`Filters.NONE`) and can be used to clear up any other |
||||
filter that was previously set and return back to normal. |
||||
|
||||
|Filter class|Filters value|XML resource value| |
||||
|------------|-------------|---------------------| |
||||
|`NoFilter`|`Filters.NONE`|`@string/cameraview_filter_none`| |
||||
|`AutoFixFilter`|`Filters.AUTO_FIX`|`@string/cameraview_filter_autofix`| |
||||
|`BlackAndWhiteFilter`|`Filters.BLACK_AND_WHITE`|`@string/cameraview_filter_black_and_white`| |
||||
|`BrightnessFilter`|`Filters.BRIGHTNESS`|`@string/cameraview_filter_brightness`| |
||||
|`ContrastFilter`|`Filters.CONTRAST`|`@string/cameraview_filter_contrast`| |
||||
|`CrossProcessFilter`|`Filters.CROSS_PROCESS`|`@string/cameraview_filter_cross_process`| |
||||
|`DocumentaryFilter`|`Filters.DOCUMENTARY`|`@string/cameraview_filter_documentary`| |
||||
|`DuotoneFilter`|`Filters.DUOTONE`|`@string/cameraview_filter_duotone`| |
||||
|`FillLightFilter`|`Filters.FILL_LIGHT`|`@string/cameraview_filter_fill_light`| |
||||
|`GammaFilter`|`Filters.GAMMA`|`@string/cameraview_filter_gamma`| |
||||
|`GrainFilter`|`Filters.GRAIN`|`@string/cameraview_filter_grain`| |
||||
|`GrayscaleFilter`|`Filters.GRAYSCALE`|`@string/cameraview_filter_grayscale`| |
||||
|`HueFilter`|`Filters.HUE`|`@string/cameraview_filter_hue`| |
||||
|`InvertColorsFilter`|`Filters.INVERT_COLORS`|`@string/cameraview_filter_invert_colors`| |
||||
|`LomoishFilter`|`Filters.LOMOISH`|`@string/cameraview_filter_lomoish`| |
||||
|`PosterizeFilter`|`Filters.POSTERIZE`|`@string/cameraview_filter_posterize`| |
||||
|`SaturationFilter`|`Filters.SATURATION`|`@string/cameraview_filter_saturation`| |
||||
|`SepiaFilter`|`Filters.SEPIA`|`@string/cameraview_filter_sepia`| |
||||
|`SharpnessFilter`|`Filters.SHARPNESS`|`@string/cameraview_filter_sharpness`| |
||||
|`TemperatureFilter`|`Filters.TEMPERATURE`|`@string/cameraview_filter_temperature`| |
||||
|`TintFilter`|`Filters.TINT`|`@string/cameraview_filter_tint`| |
||||
|`VignetteFilter`|`Filters.VIGNETTE`|`@string/cameraview_filter_vignette`| |
||||
|
||||
Most of these filters accept input parameters to tune them. For example, `DuotoneFilter` will |
||||
accept two colors to apply the duotone effect. |
||||
|
||||
```java |
||||
duotoneFilter.setFirstColor(Color.RED); |
||||
duotoneFilter.setSecondColor(Color.GREEN); |
||||
``` |
||||
|
||||
You can change these values by acting on the filter object, before or after passing it to `CameraView`. |
||||
Whenever something is changed, the updated values will be visible immediately in the next frame. |
||||
|
||||
### Advanced usage |
||||
|
||||
Advanced users with OpenGL experience can create their own filters by implementing the `Filter` interface |
||||
and passing in a fragment shader and a vertex shader that will be used for drawing. |
||||
|
||||
We recommend: |
||||
|
||||
- Subclassing `BaseFilter` instead of implementing `Filter`, since that takes care of most of the work |
||||
- If accepting parameters, implementing `OneParameterFilter` or `TwoParameterFilter` as well |
||||
|
||||
Most of all, the best way of learning is by looking at the current filters implementations in the |
||||
`com.otaliastudios.cameraview.filters` package. |
||||
|
||||
### Related APIs |
||||
|
||||
|Method|Description| |
||||
|------|-----------| |
||||
|`setFilter(Filter)`|Sets a new real-time filter.| |
||||
|`getFilter()`|Returns the current real-time filter.| |
Loading…
Reference in new issue