Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 - 日理万妓 2024-04-25 20:24 84阅读 0赞 ### 1. 前言 ### 这段时间,在使用 [natario1/CameraView][natario1_CameraView] 来实现带滤镜的`预览`、`拍照`、`录像`功能。 由于`CameraView`封装的比较到位,在项目前期,的确为我们节省了不少时间。 但随着项目持续深入,对于`CameraView`的使用进入深水区,逐渐出现满足不了我们需求的情况。 `Github`中的`issues`中,有些`BUG`作者一直没有修复。 那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。 [上篇文章][Link 1],我们对拍照的流程有了大致的了解,这篇文章,我们来看下滤镜相关的类,为后面带滤镜拍照的源码解析做下铺垫。 以下源码解析基于`CameraView 2.7.2` implementation("com.otaliastudios:cameraview:2.7.2") > 为了在博客上更好的展示,本文贴出的代码进行了部分精简 ![在这里插入图片描述][c92f5c122cb6428ba2fa01c2f4421049.png] ### 2. 如何设置滤镜 ### 在`CameraView`中,通过`setFilter(Filter filter)`来设置滤镜。 //初始化亮度滤镜 val brightnessFilter = BrightnessFilter() //设置亮度值 brightnessFilter.setBrightness(1.5F) //设置滤镜 cameraView.setFilter(brightnessFilter) ### 3. Filter ### `Filter`是一个接口,定义了`获取顶点着色器`、`获取片元着色器`、`当初始化时`、`当销毁时`、`当绘制时`、`设置尺寸`、`拷贝滤镜` public interface Filter { /** * 获取顶点着色器 */ String getVertexShader(); /** * 获取片元着色器 */ String getFragmentShader(); /** * 初始化时调用 */ void onCreate(int programHandle); /** * 销毁时调用 * */ void onDestroy(); /** * 当绘制的时候 */ void draw(long timestampUs, float[] transformMatrix); /** * 设置尺寸 */ void setSize(int width, int height); /** * 复制滤镜 */ Filter copy(); } ### 4. BaseFilter ### `BaseFilter`是一个抽象类,实现了`Filter`接口,`BaseFilter`实现了默认的顶点着色器和片元着色器,在`onCreate`的时候,创建了具体执行`OpenGL API`的`GlTextureProgram`,`copy`的时候,会根据`OneParameterFilter`和`TwoParameterFilter`接口,复制`Filter`。 public abstract class BaseFilter implements Filter { //...省略了具体代码... } 接下来来看`BaseFilter`的具体代码 #### 4.1 默认的顶点着色器和片元着色器 #### 实现了默认的顶点着色器和片元着色器 protected final static String DEFAULT_VERTEX_POSITION_NAME = "aPosition"; protected final static String DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME = "aTextureCoord"; protected final static String DEFAULT_VERTEX_MVP_MATRIX_NAME = "uMVPMatrix"; protected final static String DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME = "uTexMatrix"; protected final static String DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME = "vTextureCoord"; 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" + " "+fragmentTextureCoordinateName+" = ("+vertexTransformMatrixName+" * " + vertexTextureCoordinateName+").xy;\n" + "}\n"; } 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"; } #### 4.2 创建GlTextureProgram #### `GlTextureProgram`是对`OpenGL`纹理绘制的具体实现,这里传入了`顶点着色器和片元着色器`等,创建了`GlTextureProgram` @Override public void onCreate(int programHandle) { program = new GlTextureProgram(programHandle, vertexPositionName, vertexModelViewProjectionMatrixName, vertexTextureCoordinateName, vertexTransformMatrixName); programDrawable = new GlRect(); } #### 4.3 设置尺寸并绘制 #### 在合适的机会设置尺寸并绘制,绘制里面有三个方法`onPreDraw`、`onDraw`、`onPostDraw`,内部都是调用的`GlTextureProgram`对应的`onPreDraw`、`onDraw`、`onPostDraw`,而`GlTextureProgram`里面,我们现在只需要知道是`OpenGL API`具体的方法就行了。 @Override public void setSize(int width, int height) { size = new Size(width, height); } @Override public void draw(long timestampUs, @NonNull float[] transformMatrix) { onPreDraw(timestampUs, transformMatrix); onDraw(timestampUs); onPostDraw(timestampUs); } protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { program.setTextureTransform(transformMatrix); program.onPreDraw(programDrawable, programDrawable.getModelMatrix()); } protected void onDraw(long timestampUs) { program.onDraw(programDrawable); } protected void onPostDraw(long timestampUs) { program.onPostDraw(programDrawable); } #### 4.4 拷贝滤镜 #### `copy`方法,内部调用了`getClass().newInstance()`来反射得到一个新的`BaseFilter`,并赋值了`Size`,如果实现了`OneParameterFilter`或`TwoParameterFilter`接口,还会给设置相关的参数。 > 比如亮度滤镜的亮度值,就需要实现`OneParameterFilter`或`TwoParameterFilter`接口,从而使设置的亮度值,赋值到新的`BaseFilter`中 @NonNull @Override public final BaseFilter copy() { BaseFilter copy = onCopy(); if (size != null) { copy.setSize(size.getWidth(), size.getHeight()); } if (this instanceof OneParameterFilter) { ((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1()); } if (this instanceof TwoParameterFilter) { ((TwoParameterFilter) copy).setParameter2(((TwoParameterFilter) this).getParameter2()); } return copy; } @NonNull protected BaseFilter onCopy() { try { return getClass().newInstance(); } catch (IllegalAccessException e) { throw new RuntimeException("Filters should have a public no-arguments constructor.", e); } catch (InstantiationException e) { throw new RuntimeException("Filters should have a public no-arguments constructor.", e); } } 那么我们就会有疑问了,`copy`方法在什么情况下会使用呢 ? 根据源码,可以看到在带滤镜拍照相关的`SnapshotGlPictureRecorder`类中,会用到`copy`方法。 protected void onRendererFilterChanged(@NonNull Filter filter) { mTextureDrawer.setFilter(filter.copy()); } 就是预览和拍照用的`BaseFilter`其实不是同一个`Fitler`,而是会先`copy`一份,再去拍照。 因为为了预览流畅,预览和拍照其实用的不是同一个`Surface`(后面会讲),原来的`Fitler`已经被预览使用了,所以需要`Copy`一份,再给拍照使用。 ### 5. 预置的滤镜 ### `CameraView`预置了一些常见的滤镜,可以直接拿来使用。 #### 5.1 预设的滤镜大全 #### 预设的滤镜有以下这些 ![在这里插入图片描述][f56a815c56cd4d55b3b0b757e9068e1c.png] #### 5.2 亮度滤镜 #### 比如`BrightnessFilter`是调节亮度的滤镜,其代码如下 可以看到,里面传入了相关的`GLSL`代码,并在`onPreDraw`设置了亮度值。 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 "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+";\n" + "void main() {\n" + " vec4 color = texture2D(sTexture, "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+");\n" + " gl_FragColor = brightness * color;\n" + "}\n"; private float brightness = 2.0f; // 1.0F...2.0F private int brightnessLocation = -1; public BrightnessFilter() { } /** * Sets the brightness adjustment. * 1.0: normal brightness. * 2.0: high brightness. * * @param brightness brightness. */ @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; } /** * Returns the current brightness. * * @see #setBrightness(float) * @return brightness */ @SuppressWarnings({ "unused", "WeakerAccess"}) public float getBrightness() { return brightness; } @Override public void setParameter1(float value) { // parameter is 0...1, brightness is 1...2. setBrightness(value + 1); } @Override public float getParameter1() { // parameter is 0...1, brightness is 1...2. return getBrightness() - 1F; } @NonNull @Override public String getFragmentShader() { return FRAGMENT_SHADER; } @Override public void onCreate(int programHandle) { super.onCreate(programHandle); brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness"); Egloo.checkGlProgramLocation(brightnessLocation, "brightness"); } @Override public void onDestroy() { super.onDestroy(); brightnessLocation = -1; } @Override protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(brightnessLocation, brightness); Egloo.checkGlError("glUniform1f"); } } ### 6. MultiFilter ### 单个滤镜的调用直接调用某个滤镜就可以了,但如果是多个滤镜进行叠加,那么就需要用到`MultiFilter`,通过`addFilter()`来叠加多个滤镜。 public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilter { //...省略了具体代码... } #### 6.1 添加滤镜 #### 将添加的滤镜存储在`filters`列表中 final List<Filter> filters = new ArrayList<>(); public void addFilter(@NonNull Filter filter) { if (filter instanceof MultiFilter) { MultiFilter multiFilter = (MultiFilter) filter; for (Filter multiChild : multiFilter.filters) { addFilter(multiChild); } return; } synchronized (lock) { if (!filters.contains(filter)) { filters.add(filter); states.put(filter, new State()); } } } #### 6.2 绘制滤镜 #### 遍历`filters`列表,并调用一系列`OpenGL`的方法,逐个绘制滤镜,上一个滤镜绘制好后,下一个滤镜在上一个滤镜的基础上再绘制,从而最终达到滤镜叠加的效果。 @Override public void draw(long timestampUs, @NonNull float[] transformMatrix) { synchronized (lock) { for (int i = 0; i < filters.size(); i++) { boolean isFirst = i == 0; boolean isLast = i == filters.size() - 1; Filter filter = filters.get(i); State state = states.get(filter); maybeSetSize(filter); maybeCreateProgram(filter, isFirst, isLast); maybeCreateFramebuffer(filter, isFirst, isLast); //noinspection ConstantConditions GLES20.glUseProgram(state.programHandle); // Define the output framebuffer. // Each filter outputs into its own framebuffer object, except the // last filter, which outputs into the default framebuffer. if (!isLast) { state.outputFramebuffer.bind(); GLES20.glClearColor(0, 0, 0, 0); } else { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); } // Perform the actual drawing. // The first filter should apply all the transformations. Then, // since they are applied, we should use a no-op matrix. if (isFirst) { filter.draw(timestampUs, transformMatrix); } else { filter.draw(timestampUs, Egloo.IDENTITY_MATRIX); } // Set the input for the next cycle: // It is the framebuffer texture from this cycle. If this is the last // filter, reset this value just to cleanup. if (!isLast) { state.outputTexture.bind(); } else { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); } GLES20.glUseProgram(0); } } } ### 7. 其他 ### #### 7.1 CameraView源码解析系列 #### [Android 相机库CameraView源码解析 (一) : 预览-CSDN博客][Android _CameraView_ _ _ _-CSDN] [Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客][Link 1] [Android 相机库CameraView源码解析 (三) : 滤镜相关类说明-CSDN博客][Android _CameraView_ _ _ _-CSDN 1] [Android 相机库CameraView源码解析 (四) : 带滤镜拍照-CSDN博客][Android _CameraView_ _ _ _-CSDN 2] [Android 相机库CameraView源码解析 (五) : 保存滤镜效果-CSDN博客][Android _CameraView_ _ _ _-CSDN 3] [natario1_CameraView]: https://github.com/natario1/CameraView [Link 1]: https://blog.csdn.net/EthanCo/article/details/134545086 [c92f5c122cb6428ba2fa01c2f4421049.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/4e1a65f402f14709907f3bdbf3724de2.png [f56a815c56cd4d55b3b0b757e9068e1c.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/25/c343db28b01740a58758814132952e83.png [Android _CameraView_ _ _ _-CSDN]: https://blog.csdn.net/EthanCo/article/details/134511622 [Android _CameraView_ _ _ _-CSDN 1]: https://blog.csdn.net/EthanCo/article/details/134517249 [Android _CameraView_ _ _ _-CSDN 2]: https://blog.csdn.net/EthanCo/article/details/134517154 [Android _CameraView_ _ _ _-CSDN 3]: https://blog.csdn.net/EthanCo/article/details/134691849
相关 Android 相机库CameraView源码解析 (四) : 带滤镜预览 Android 相机库CameraView源码解析 (四) : 带滤镜预览 太过爱你忘了你带给我的痛/ 2024年04月25日 20:24/ 0 赞/ 80 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 解决相机库CameraView多滤镜拍照错乱的BUG (二) : 解决BUG 我不是女神ヾ/ 2024年04月25日 20:24/ 0 赞/ 78 阅读
相关 解决相机库CameraView多滤镜拍照错乱的BUG (一) : 复现BUG 解决相机库CameraView多滤镜拍照错乱的BUG 女爷i/ 2024年04月25日 20:24/ 0 赞/ 87 阅读
相关 为什么相机库CameraView预览和拍照的效果不一致 ? 从源码解析 : 为什么CameraView预览和拍照的效果会不一致呢 ? 我就是我/ 2024年04月25日 20:24/ 0 赞/ 86 阅读
相关 Android 相机库CameraView源码解析 (六) : 保存滤镜效果 Android 相机库CameraView源码解析 : 保存滤镜效果部分 约定不等于承诺〃/ 2024年04月25日 20:24/ 0 赞/ 90 阅读
相关 Android 相机库CameraView源码解析 (二) : 拍照 Android 相机库CameraView源码解析 : 拍照部分 ╰+哭是因爲堅強的太久メ/ 2024年04月25日 20:24/ 0 赞/ 77 阅读
相关 Android 相机库CameraView源码解析 (三) : 滤镜相关类说明 Android 相机库CameraView源码解析 : 滤镜相关类说明 - 日理万妓/ 2024年04月25日 20:24/ 0 赞/ 85 阅读
相关 Android 相机库CameraView源码解析 (一) : 预览 Android 相机库CameraView源码解析 : 预览部分 青旅半醒/ 2024年04月25日 20:23/ 0 赞/ 73 阅读
相关 Android 相机库CameraView源码解析 (五) : 带滤镜拍照 Android 相机库CameraView源码解析 : 带滤镜拍照 我不是女神ヾ/ 2024年04月25日 20:23/ 0 赞/ 101 阅读
相关 android opengl滤镜,Android OpenGL ES滤镜开发设计 一、引入 按照正常的Android OpenGL开发,一般只需引入两个“主角”: GLSurfaceView 和 Renderer 。在拍摄这种各种挂件和特效纵横的场景下 柔情只为你懂/ 2022年10月05日 13:57/ 0 赞/ 368 阅读
还没有评论,来说两句吧...