Android 相机库CameraView源码解析 (三) : 滤镜相关类说明

2023-12-04 08:04

本文主要是介绍Android 相机库CameraView源码解析 (三) : 滤镜相关类说明,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 前言

这段时间,在使用 natario1/CameraView 来实现带滤镜的预览拍照录像功能。
由于CameraView封装的比较到位,在项目前期,的确为我们节省了不少时间。
但随着项目持续深入,对于CameraView的使用进入深水区,逐渐出现满足不了我们需求的情况。
Github中的issues中,有些BUG作者一直没有修复。

那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。
上篇文章,我们对拍照的流程有了大致的了解,这篇文章,我们来看下滤镜相关的类,为后面带滤镜拍照的源码解析做下铺垫。

以下源码解析基于CameraView 2.7.2

implementation("com.otaliastudios:cameraview:2.7.2")

为了在博客上更好的展示,本文贴出的代码进行了部分精简

在这里插入图片描述

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 APIGlTextureProgramcopy的时候,会根据OneParameterFilterTwoParameterFilter接口,复制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 设置尺寸并绘制

在合适的机会设置尺寸并绘制,绘制里面有三个方法onPreDrawonDrawonPostDraw,内部都是调用的GlTextureProgram对应的onPreDrawonDrawonPostDraw,而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,如果实现了OneParameterFilterTwoParameterFilter接口,还会给设置相关的参数。

比如亮度滤镜的亮度值,就需要实现OneParameterFilterTwoParameterFilter接口,从而使设置的亮度值,赋值到新的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 预设的滤镜大全

预设的滤镜有以下这些
在这里插入图片描述

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.0Fprivate 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;}@Overridepublic void setParameter1(float value) {// parameter is 0...1, brightness is 1...2.setBrightness(value + 1);}@Overridepublic float getParameter1() {// parameter is 0...1, brightness is 1...2.return getBrightness() - 1F;}@NonNull@Overridepublic String getFragmentShader() {return FRAGMENT_SHADER;}@Overridepublic void onCreate(int programHandle) {super.onCreate(programHandle);brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness");Egloo.checkGlProgramLocation(brightnessLocation, "brightness");}@Overridepublic void onDestroy() {super.onDestroy();brightnessLocation = -1;}@Overrideprotected 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 ConstantConditionsGLES20.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博客
Android 相机库CameraView源码解析 (四) : 带滤镜拍照-CSDN博客
Android 相机库CameraView源码解析 (五) : 保存滤镜效果-CSDN博客

这篇关于Android 相机库CameraView源码解析 (三) : 滤镜相关类说明的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/452753

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get