专栏首页Android原创Android OpenGL ES滤镜开发设计
原创

Android OpenGL ES滤镜开发设计

一、引入

按照正常的Android OpenGL开发,一般只需引入两个“主角”:GLSurfaceViewRenderer。在拍摄这种各种挂件和特效纵横的场景下,Renderer会变得臃肿和不堪重负,而且不够灵活地去替换各种挂机和特效。正如Activity引入Fragment,同理,这里我们的Renderer引入Filter

二、定义

Filter定义为一个小型的Renderer,一个Renderer可以有多个Filter,可以增删FilterFilter间可以互相叠加特效。

三、代码设计

1. IFilter

这里定义一个滤镜需要实现的接口。

public interface IFilter {
    void create(); // Renderer#onSurfaceCreated时调用
    void changeSize(int width, int height);// Renderer#onSurfaceChanged时调用
    void draw();// Renderer#onDrawFrame时调用
}

在这个接口的基础上,按照OpenGL常用的代码实现过程,抽出一个抽象类来。

public abstract class AbsFilter implements IFilter{
    @Override
    public final void create(){
        onCreateProgram();
        onCreate(mProgram);
    }
    @Override
    public final void changeSize(int width, int height){
        onSizeChange(width, height);
    }
    @Override
    public final void draw() {
        onClear();
        onUseProgram();
        onSetExpandData();
        onBindTexture();
        onDraw();
    }
    protected void onCreateProgram() {
		// loadShader && createProgram 减少代码阅读压力,此处简写
    }
    protected void onUseProgram() {
        GLES20.glUseProgram(mProgram);
    }
    protected abstract void onCreate(int program);
    protected abstract String getVertexShaderCode();  // 子类实现,返回vertex shader 代码段内容。
    protected abstract String getFragmentShaderCode();  // 子类实现,返回fragment shader 代码段内容。
    protected abstract void onSizeChange(int width, int height);
    protected abstract void onClear(); // 颜色清屏
    protected abstract void onSetExpandData(); // 传给shader一些其他的参数
    protected abstract void onBindTexture(); // 绑定texture
    protected abstract void onDraw(); // 绘制
}

2. IFilterGroup

除了Filter,还需要一个FilterGroup来管理这些个Filter

public interface IFilterGroup {
    void addFilter(IFilter filter);
    void removeFilter(IFilter filter);
    boolean containsFilter(IFilter filter);
    void clear();
}

这时,其实有人会这么说,干嘛要用FilterGroup,直接用List来不就可以了吗?

我们先看下这个IFilterGroup的实现,以此来解答这个疑问。

public class FilterGroup implements IFilterGroup, IFilter{
    private int mWidth;
    private int mHeight;

    private Queue<IFilter> mFilterQueue = new ConcurrentLinkedQueue<IFilter>(); // glThread和MainThread都会操作这个,所以用ConcurrentLinkedQueue
    private List<IFilter> mFilters = new CopyOnWriteArrayList<IFilter>(); // 有遍历和多线程操作,防止抛异常

    @Override
    public void create() {
        for (IFilter filter : mFilters) {
            filter.create();
        }
    }

    @Override
    public void changeSize(int width, int height) {
        this.mWidth = width;
        this.mHeight = height;
        updateFilter();
        for (IFilter filter : mFilters) {
            filter.changeSize(width, height);
        }
    }

    @Override
    public void draw() {
        updateFilter();
        for (IFilter filter : mFilters) {
            filter.draw();
        }
    }

    @Override
    public void addFilter(IFilter filter) {   this.mFilterQueue.add(filter); }

    @Override
    public void removeFilter(IFilter filter) {	// 减少阅读压力,简单代码,此处省略
	}
    @Override
    public boolean containsFilter(IFilter filter) {	// 减少阅读压力,简单代码,此处省略
	}
    @Override
    public void clear() {	// 减少阅读压力,简单代码,此处省略
	}

    private void updateFilter() {
        IFilter f;
        while ((f=mFilterQueue.poll())!=null){
            f.create();
            f.changeSize(mWidth,mHeight);
            mFilters.add(f);
        }
    }
}

因为,Filter有着与Renderer相似的方法,由于Filter可支持动态添加的缘故,可能会出现RendereronSurfaceCreatedonSurfaceChanged都执行过了,Filter无法执行createchangeSize,之后执行draw

故需要有一个如同FilterGroup,有一个updateFilter方法,将添加的Filter先存放在一个队列中,等要执行RendereronDrawFrame方法时,将所有从队列取出执行前面未执行到的createchangeSize方法,后才加入到正常的FilterList中,一起去执行draw方法。

而执行updateFilter方法的时机是RendereronSurfaceChangedonDrawFrame时。

3. FilterRenderer

讲完IFilterIFilterGroup,接下来就要讲下上面两个在Renderer里的使用了。

public class FilterRenderer implements GLSurfaceView.Renderer, IFilterGroup{
    private FilterGroup mDefaultFilterGroup = new FilterGroup();
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mDefaultFilterGroup.create();
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mDefaultFilterGroup.changeSize(width, height);
    }
    @Override
    public void onDrawFrame(GL10 gl) {
        mDefaultFilterGroup.draw();
    }
    @Override
    public void addFilter(IFilter filter) {
        mDefaultFilterGroup.addFilter(filter);
    }
    @Override
    public void removeFilter(IFilter filter) {
        mDefaultFilterGroup.removeFilter(filter);
    }
    @Override
    public boolean containsFilter(IFilter filter) {
        return mDefaultFilterGroup.containsFilter(filter);
    }
    @Override
    public List<IFilter> getFilterList() {
        return mDefaultFilterGroup.getFilterList();
    }
    @Override
    public void clear() {
        mDefaultFilterGroup.clear();
    }
}

四、使用

1,. 继承AbsFilter或实现IFilter制作滤镜;

  1. 实现FilterRenderer,用FilterRenderer#addFilter添加自己写的滤镜(步骤3后面add也可以);
  2. GLSurfaceView#setRenderer

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 优雅的监听onActivityResult

    此时,我们可能会用到EventBus这种全局分发事件的方式来处理,但种感觉不够优雅。

    Clayman Twinkle
  • 写一个Gradle插件

    我们在Android Studio中创建的app项目中,build.gradle常有如下这行代码:

    Clayman Twinkle
  • 在Android中显示APNG动图

    APNG(Animated Portable Network Graphics)是一个基于PNG(Portable Network Graphics)的位图动画...

    Clayman Twinkle
  • Apache Flink Table API的Catalog

    “ Apache Flink的Table API提供了对数据注册为Table的方式, 实现把数据通过SQL的方式进行计算。Table API与SQL API实现...

    CainGao
  • 一个简单的支持MySQL和SQLite3的DB接口

    simple_db.zip 相关联代码:https://github.com/eyjian/mooon/tree/master/common_library/...

    一见
  • 架构设计中的6种常见安全误区

    【编者按】国家战略层面的重视与投入,云计算与大数据等技术的深入,“互联网+”驱动下私有云、混合云和公有云的发展,使得安全——软件安全、云计算安全、移动安全、物联...

    CSDN技术头条
  • Apache RocketMQ:简单消息示例

    通过三种方式来发送RocketMQ消息使用: 可靠的同步发送, 可靠的异步发送和单向传输。

    微风-- 轻许--
  • 折叠屏和5G渐成趋势,苹果将会走向何方?

    2019年被通信业界称为“5G试商用元年”“折叠手机元年”,然无论是华为MateX亦或是三星GalaxyFold在小编看来还都处在5G折叠屏手机的概念阶段。准确...

    微资讯
  • webview添加参数与修改请求头的user-agent实例

    最近公司项目需求,在项目中嵌入h5页面,一般原生,看着感觉跟往常一样,一个地址就完全ok了,如果是这样那就没有这个博文的必要了!

    砸漏
  • Django之全局使用request.user.username的实例详解

    补充知识:Django 中 request.user 调用用户名时出现 AnonymousUser(匿名对象)的错误

    砸漏

扫码关注云+社区

领取腾讯云代金券