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

2023-12-05

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 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 预设的滤镜大全

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

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博客
Android 相机库CameraView源码解析 (四) : 带滤镜拍照-CSDN博客
Android 相机库CameraView源码解析 (五) : 保存滤镜效果-CSDN博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • GCM 无法唤醒设备

    我正在开发 GCM 应用程序 当设备不空闲时 即按电源按钮休眠 一切正常 但是 当我在设备空闲时发送消息时 设备不会唤醒 我做了以下事情 已验证服务器应用程序中的delay while idle未设置为true 每条消息使用不同的折叠键 多
  • Android版本App更新代码

    我读到如果我们想更新Google Play中的应用程序 版本代码应该高于以前的apk文件 我有一个版本代码为 20 且版本名称为 1 0 的应用程序 那么要更新app 应该如何增加版本号呢 应该增加10吗 或者仅仅 1 就足够了 即版本代码
  • 使用 setText 更改文本后如何更新屏幕?

    现在我正在使用以下代码来更新TextView txtMain setText new text 该代码执行后 屏幕不会更新新文本 有没有办法可以强制文本立即更新 该代码执行后 屏幕不会更新新文本 一旦您将控制权归还给 Android 就应该
  • 片段内容下方是否存在持久性 BottomSheet?

    Using a 持久底表 https material google com components bottom sheets html bottom sheets persistent bottom sheets 在一个协调器布局 htt
  • 如何使用数据绑定将点击侦听器设置为 LinearLayout

    我目前正在尝试将点击侦听器设置为LinearLayout查看在 xml使用数据绑定的布局文件 我已经设法让它在其他视图上很好地工作 比如Button or TextView 但由于某种原因 它不能与LinearLayout 这是我尝试的基本
  • 如何使用 runOnUiThread 而不出现“无法对非静态方法进行静态引用”编译器错误

    我有一个主课 ClientPlayer extends Activity 和一项服务 LotteryServer extends Service implements Runnable 当尝试在此服务的 run 方法中使用 RunOnUiT
  • 如何调试“com.android.okhttp”

    在android kitkat中 URLConnection的实现已经被OkHttp取代 如何调试呢 OkHttp 位于此目录中 external okhttp android main java com squareup okhttp 当
  • 在android中从JSON生成listview

    我对 Android 完全陌生 目前正在尝试从从我的服务器中提取的 JSON 数组生成列表视图 我已经阅读了很多教程 但没有运气 有一种独特的方法可以做到这一点 请您指出一些适合开始的资源 我读过了this http www josecgo
  • Cordova + android:无法从应用程序打开拨号盘或邮件意图

    我有一个奇怪的问题 我无法从应用程序中打开带有预定义号码或邮件意图的拨号盘 我正在使用 netbeans 8 0 1 创建 cordova 应用程序 我的 Cordova 版本是 4 0 0 我按照步骤创建了一个应用程序 并选择了 Hell
  • RecyclerView 在聊天屏幕中的 notificationDataSetChanged 上滚动到顶部

    我正在尝试使用 recyclerView 创建消息传递类型的屏幕 该屏幕将从底部开始 并在用户到达聊天顶端时加载更多数据 但我面临着这个奇怪的问题 我的 recyclerView 在调用 notificationDataSetChanged
  • 在 Xamarin 中隐藏软键盘

    如何隐藏软键盘以便在聚焦时显示Entry在 Xamarin forms 便携式表单项目中 我假设我们必须为此编写特定于平台的渲染器 但以下内容不起作用 我创建自己的条目子类 public class MyExtendedEntry Entr
  • react-native android fontFamily 不生效

    问题一 我在index android js的欢迎样式中添加了fontFamily 但没有效果 fontFamily 真的可以在 Android 上使用吗 欢迎 字体大小 20 fontFamily roboto thin 文本对齐 居中
  • Android 为什么这不会抛出错误的线程异常?

    我的印象是视图只能从主线程操作 但是 为什么这不会崩溃 public class MainActivity extends Activity TextView tv Override protected void onCreate Bund
  • Android SHA1 发布密钥库无法与 Google 地图配合使用

    我正在使用 Google Maps Android API 但遇到了一些问题 我正在使用 android studio 签署我的 apk 在 android keystore jks 创建一个 另外 我选择 发布 作为其中的类型 我已经使用
  • 在android中从SD卡上传图像到facebook

    我无法从 SD 卡上传 Facebook 上的图像 我使用了下面的代码 但它没有给我错误 但同时它没有上传图像 byte data null try FileInputStream fis new FileInputStream filep
  • Android:分配内存失败

    我正在尝试创建一个具有 2047 mb 内存的模拟器 当我运行它时 我收到此错误 2011 02 22 14 24 14 Emulator 2011 02 22 14 24 14 Emulator This application has
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • 如何解决 greenDAO 在执行 InsertOrReplace 时“不存在这样的表错误”?

    我正在使用 greenDAO 并且已成功生成所有必需的类和实体 并且我可以看到我的表已创建 但是在要替换的行上放置断点后 我收到一条错误消息 告诉我 不存在这样的表错误 try appTimeUsageDao insertOrReplace
  • Android View Canvas onDraw 未执行

    我目前正在开发一个自定义视图 它在画布上绘制一些图块 这些图块是从多个文件加载的 并将在需要时加载 它们将由 AsyncTask 加载 如果它们已经加载 它们只会被绘制在画布上 这工作正常 如果加载了这些图片 AsyncTask 就会触发v
  • Keystore getEntry 在 Android 9 上返回 NULL

    c我已对存储在 Android 密钥库中的登录密码进行了加密和解密 在 Android 9 上 我观察到应用程序在尝试解密密码时崩溃 我无法重现它 但拥有 Pixel 3 的用户是崩溃的设备之一 下面是我如何从密钥库解密密码 private

随机推荐