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

news2025/1/17 22:54:20

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.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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1280773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【springboot】启动失败 Failed to start bean ‘webServerStartStop‘

lsof -i&#xff1a;xxx 发现端口被占用 kill掉该进程

大模型的RPA应用 | 代理流程自动化(APA),开启智能自动化新纪元

随着技术创新的持续推进&#xff0c;自动化技术已经变得至关重要&#xff0c;成为驱动企业和社会向前发展的核心动力。在自动化的里程碑中&#xff0c;机器人流程自动化&#xff08;RPA&#xff09;已经有效地将简单、重复且规则性的任务自动化。可是随着对处理更为复杂、多变且…

【学习笔记】机器学习——GAN

提出于2014年。 GAN由两个神经网络组成&#xff1a;一个试图生成看起来与训练数据相似数据的生成器&#xff0c;以及一个试图从虚假数据中分辨出真实数据的判别器。生成器和判别器在训练期间相互竞争。 对抗训练&#xff08;训练竞争性网络&#xff09;是一种重要的机器学习思想…

循环神经网络RNN及其变体LSTM、GRU

1. 背景 RNN(Recurrent Neural Networks) CNN利用输入中的空间几何结构信息&#xff1b;RNN利用输入数据的序列化特性。 2. SimpleRNN单元 传统多层感知机网络假设所有的输入数据之间相互独立&#xff0c;但这对于序列化数据是不成立的。RNN单元用隐藏状态或记忆引入这种依赖…

2024美赛数学建模资料---100%获奖资料

很好的教程了 一共二十四章 每一章都是一个模型 并且有matlab编程编码 第一章 线性规划 第二章 整数规划 第三章 非线性规划 第四章 动态规划 第五章 图与网络 第六章 排队论 第七章 对策论 第八章 层次分析法 第九章 插值与拟合 第十章 数据的统计描述和分析 第十一章…

小心处理 C++ 静态变量中的陷阱

小心处理 C 静态变量中的陷阱 函数中的 static 变量 static 变量的作用 C 中 static 关键字的最后一个用途是在函数内创建局部变量&#xff0c;这些变量在其作用域内退出和进入时保持其值。函数内的 static 变量类似于只能从该函数访问的全局变量。static 变量的一个常见用途…

JS前端逆向

前言 js逆向一直没有相关了解&#xff0c;虽然目前渗透遇见的不是很多&#xff0c;大多数遇见的要么不加密&#xff0c;要么无法实现其加密流程&#xff0c;不过最近看到了一个较为简单的站点正好能够逆向出来&#xff0c;就做了简单记录。本文旨在介绍js逆向的一些基础思路&am…

超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?

文章目录 前言Mybatis dao层两种实现方式的对比原始Dao开发原始Dao开发的弊端 基于Mapper动态代理的开发方式 Mybatis动态代理实现方式的原理解析动态代理调用链路解析先给出链路调用结果1、调用方法的开始&#xff1a;session.getMapper2、DeaultSqlSession的getMapper3、Conf…

JavaWeb 带条件的分页查询

最终效果图 注意&#xff1a;没有带条件的时候 默认的是第一页数据 条件是组合的 sql->sql的动态变换 注意第二次查询的时候回显问题 就是填完条件后显示完当页数据ok 但是我点击第二页的时候条件还存在着 此时ListSerevlet不仅要拿到页码 页容量 还要拿到三个条件参数 封装…

Linux网络之连接跟踪 conntrack

一 Linux网络之连接跟踪 conntrack k8s 有关conntrack的分析 ① 什么是连接跟踪 netfilter连接跟踪 conntrack 详述 思考&#xff1a;连接跟踪模块会对哪些协议进行跟踪?TCP、UDP、ICMP、DCCP、SCTP、GRE ② 为什么需要连接跟踪 没有连接跟踪有很多问题是不好解决的&a…

掌握视频剪辑技巧:批量置入视频封面,提升视频品质

在当今数字化时代&#xff0c;视频已成为生活的重要组成部分。无论是观看电影、电视剧、综艺节目&#xff0c;还是分享个人生活、工作成果&#xff0c;视频都以其独特的魅力吸引着大众的视线。视频封面是视频内容的缩影&#xff0c;是观众对视频的第一印象。一个好的封面能吸引…

分享一个简单的基于C语言嵌入式GUI界面切换代码

目录 前言 一、数据类型 二、页面调度 三、页面显示 四、视频展示 前言 最近在用LVGL写一个简单的UI界面&#xff0c;需要进行几个页面的切换&#xff0c;所以就自己写了一个简单页面切换代码&#xff0c;方便进行页面切换&#xff0c;同时使UI代码结构更加清晰。这个结构…

基于Java SSM框架实现实现四六级英语报名系统项目【项目源码+论文说明】

基于java的SSM框架实现四六级英语报名系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个高校四六级报名管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作…

什么是跨站脚本攻击

跨站脚本攻击 1. 定义2. 跨站脚本攻击如何工作3. 跨站脚本攻击类型4. 如何防止跨站脚本攻击 1. 定义 跨站脚本攻击&#xff08;Cross-site Scripting&#xff0c;通常称为XSS&#xff09;&#xff0c;是一种典型的Web程序漏洞利用攻击&#xff0c;在线论坛、博客、留言板等共享…

leetCode 51.皇后 + 回溯算法 + 图解 + 笔记

按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。每一种解法包…

Python读取栅格遥感影像并加以辐射校正后导出为Excel的一列数据

本文介绍基于Python语言中的gdal模块&#xff0c;读取一景.tif格式的栅格遥感影像文件&#xff0c;提取其中每一个像元的像素数值&#xff0c;对像素值加以计算&#xff08;辐射定标&#xff09;后&#xff0c;再以一列数据的形式将计算后的各像元像素数据保存在一个.csv格式文…

【力扣206】反转链表

【力扣206】反转链表 一.题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1 &#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2 &#xff1a; 输入&#xff1a;head [1,2] 输出&#x…

机器学习---pySpark代码开发

1、eclipse开发pySpark程序 在eclipse中开发pySpark程序&#xff0c;需要安装pydev插件。 1).eclipse安装python插件,安装完成后重启。 2). 在window--->preferences中找到python interpreter配置安装python的路径&#xff1a; 3).新建python项目&#xff1a; 2、pyCharm开…

Centos图形化界面封装OpenStack Centos镜像

目录 背景 环境 宿主机环境安装 创建与安装Centos7.8虚机 虚机设置 安全相关 安装ACPI服务 安装cloud-init 安装cloud-utils-growpart 停⽌虚拟机 删除个性化信息 模板化与压缩 登录与验证 背景 今天早上在Centos官网下载的CentOS-7-aarch64-GenericCloud-2003.…

3dMax拼图生成工具Puzzle2D使用教程

Puzzle2D for 3dsMax拼图生成工具使用教程 Puzzle2D简介&#xff1a; 2D拼图随机生成器&#xff08;英文&#xff1a;Puzzle2D&#xff09; &#xff0c;是一款由#沐风课堂#用MAXScript脚本语言开发的3dsMax建模小工具&#xff0c;可以随机创建2D可编辑样条线拼图图形。可批量…