Android OpenGLES如何给相机添加滤镜详解
滤镜介绍
目前市面上的滤镜有很多,但整体归类也就几样,都是在fragment shader中进行处理。目前滤镜最常用的就是 lut滤镜以及调整rgb曲线的滤镜了。其他的类型变更大同小异。
动态滤镜的构建
为了实现动态下载的滤镜,我们接下来实现一套滤镜的json参数,主要包括滤镜类型、滤镜名称、vertex shader、fragment shader 文件、统一变量列表、与统一变量绑定的纹理图片、默认滤镜强度、是否带纹理宽高偏移量、音乐路径、音乐是否循环播放等参数。
json 以及各个字段的介绍如下:
{ "filterlist": [{ "type": "filter", // 表明滤镜类型,目前filter是只普通滤镜,后续还会加入其它类型的滤镜 "name": "amaro", // 滤镜名称 "vertexshader": "", // vertex shader 文件名 "fragmentshader": "fragment.glsl", // fragment shader 文件名 "uniformlist":["blowouttexture", "overlaytexture", "maptexture"], // 统一变量 "uniformdata": { // 与统一变量绑定的纹理图片 "blowouttexture": "blowout.png", "overlaytexture": "overlay.png", "maptexture": "map.png" }, "strength": 1.0, // 默认滤镜强度 0.0 ~ 1.0之间 "texeloffset": 0, // 是否需要支持宽高偏移值,即需要传递 1.0f/width, 1.0f/height到shader中 "audiopath": "", // 音乐路径 "audiolooping": 1 // 是否循环播放音乐 }] }
有了json 之后,我们需要解码得到滤镜参数对象,解码如下:
/** * 解码滤镜数据 * @param folderpath * @return */ public static dynamiccolor decodefilterdata(string folderpath) throws ioexception, jsonexception { file file = new file(folderpath, "json"); string filterjson = fileutils.converttostring(new fileinputstream(file)); jsonobject jsonobject = new jsonobject(filterjson); dynamiccolor dynamiccolor = new dynamiccolor(); dynamiccolor.unzippath = folderpath; if (dynamiccolor.filterlist == null) { dynamiccolor.filterlist = new arraylist<>(); } jsonarray filterlist = jsonobject.getjsonarray("filterlist"); for (int filterindex = 0; filterindex < filterlist.length(); filterindex++) { dynamiccolordata filterdata = new dynamiccolordata(); jsonobject jsondata = filterlist.getjsonobject(filterindex); string type = jsondata.getstring("type"); // todo 目前滤镜只做普通的filter,其他复杂的滤镜类型后续在做处理 if ("filter".equals(type)) { filterdata.name = jsondata.getstring("name"); filterdata.vertexshader = jsondata.getstring("vertexshader"); filterdata.fragmentshader = jsondata.getstring("fragmentshader"); // 获取统一变量字段 jsonarray uniformlist = jsondata.getjsonarray("uniformlist"); for (int uniformindex = 0; uniformindex < uniformlist.length(); uniformindex++) { string uniform = uniformlist.getstring(uniformindex); filterdata.uniformlist.add(uniform); } // 获取统一变量字段绑定的图片资源 jsonobject uniformdata = jsondata.getjsonobject("uniformdata"); if (uniformdata != null) { iterator<string> dataiterator = uniformdata.keys(); while (dataiterator.hasnext()) { string key = dataiterator.next(); string value = uniformdata.getstring(key); filterdata.uniformdatalist.add(new dynamiccolordata.uniformdata(key, value)); } } filterdata.strength = (float) jsondata.getdouble("strength"); filterdata.texeloffset = (jsondata.getint("texeloffset") == 1); filterdata.audiopath = jsondata.getstring("audiopath"); filterdata.audiolooping = (jsondata.getint("audiolooping") == 1); } dynamiccolor.filterlist.add(filterdata); } return dynamiccolor; }
滤镜的实现
在解码得到滤镜参数之后,我们接下来实现动态滤镜渲染过程。为了方便构建滤镜,我们创建一个滤镜资源加载器,代码如下:
/** * 滤镜资源加载器 */ public class dynamiccolorloader { private static final string tag = "dynamiccolorloader"; // 滤镜所在的文件夹 private string mfolderpath; // 动态滤镜数据 private dynamiccolordata mcolordata; // 资源索引加载器 private resourcedatacodec mresourcecodec; // 动态滤镜 private final weakreference<dynamiccolorbasefilter> mweakfilter; // 统一变量列表 private hashmap<string, integer> muniformhandlelist = new hashmap<>(); // 纹理列表 private int[] mtexturelist; // 句柄 private int mtexelwidthoffsethandle = openglutils.gl_not_init; private int mtexelheightoffsethandle = openglutils.gl_not_init; private int mstrengthhandle = openglutils.gl_not_init; private float mstrength = 1.0f; private float mtexelwidthoffset = 1.0f; private float mtexelheightoffset = 1.0f; public dynamiccolorloader(dynamiccolorbasefilter filter, dynamiccolordata colordata, string folderpath) { mweakfilter = new weakreference<>(filter); mfolderpath = folderpath.startswith("file://") ? folderpath.substring("file://".length()) : folderpath; mcolordata = colordata; mstrength = (colordata == null) ? 1.0f : colordata.strength; pair pair = resourcecodec.getresourcefile(mfolderpath); if (pair != null) { mresourcecodec = new resourcedatacodec(mfolderpath + "/" + (string) pair.first, mfolderpath + "/" + pair.second); } if (mresourcecodec != null) { try { mresourcecodec.init(); } catch (ioexception e) { log.e(tag, "dynamiccolorloader: ", e); mresourcecodec = null; } } if (!textutils.isempty(mcolordata.audiopath)) { if (mweakfilter.get() != null) { mweakfilter.get().setaudiopath(uri.parse(mfolderpath + "/" + mcolordata.audiopath)); mweakfilter.get().setlooping(mcolordata.audiolooping); } } loadcolortexture(); } /** * 加载纹理 */ private void loadcolortexture() { if (mcolordata.uniformdatalist == null || mcolordata.uniformdatalist.size() <= 0) { return; } mtexturelist = new int[mcolordata.uniformdatalist.size()]; for (int dataindex = 0; dataindex < mcolordata.uniformdatalist.size(); dataindex++) { bitmap bitmap = null; if (mresourcecodec != null) { bitmap = mresourcecodec.loadbitmap(mcolordata.uniformdatalist.get(dataindex).value); } if (bitmap == null) { bitmap = bitmaputils.getbitmapfromfile(mfolderpath + "/" + string.format(mcolordata.uniformdatalist.get(dataindex).value)); } if (bitmap != null) { mtexturelist[dataindex] = openglutils.createtexture(bitmap); bitmap.recycle(); } else { mtexturelist[dataindex] = openglutils.gl_not_texture; } } } /** * 绑定统一变量句柄 * @param programhandle */ public void onbinduniformhandle(int programhandle) { if (programhandle == openglutils.gl_not_init || mcolordata == null) { return; } mstrengthhandle = gles30.glgetuniformlocation(programhandle, "strength"); if (mcolordata.texeloffset) { mtexelwidthoffsethandle = gles30.glgetuniformlocation(programhandle, "texelwidthoffset"); mtexelheightoffsethandle = gles30.glgetuniformlocation(programhandle, "texelheightoffset"); } else { mtexelwidthoffsethandle = openglutils.gl_not_init; mtexelheightoffsethandle = openglutils.gl_not_init; } for (int uniformindex = 0; uniformindex < mcolordata.uniformlist.size(); uniformindex++) { string uniformstring = mcolordata.uniformlist.get(uniformindex); int handle = gles30.glgetuniformlocation(programhandle, uniformstring); muniformhandlelist.put(uniformstring, handle); } } /** * 输入纹理大小 * @param width * @param height */ public void oninputsizechange(int width, int height) { mtexelwidthoffset = 1.0f / width; mtexelheightoffset = 1.0f / height; } /** * 绑定滤镜纹理,只需要绑定一次就行,不用重复绑定,减少开销 */ public void ondrawframebegin() { if (mstrengthhandle != openglutils.gl_not_init) { gles30.gluniform1f(mstrengthhandle, mstrength); } if (mtexelwidthoffsethandle != openglutils.gl_not_init) { gles30.gluniform1f(mtexelwidthoffsethandle, mtexelwidthoffset); } if (mtexelheightoffsethandle != openglutils.gl_not_init) { gles30.gluniform1f(mtexelheightoffsethandle, mtexelheightoffset); } if (mtexturelist == null || mcolordata == null) { return; } // 逐个绑定纹理 for (int dataindex = 0; dataindex < mcolordata.uniformdatalist.size(); dataindex++) { for (int uniformindex = 0; uniformindex < muniformhandlelist.size(); uniformindex++) { // 如果统一变量存在,则直接绑定纹理 integer handle = muniformhandlelist.get(mcolordata.uniformdatalist.get(dataindex).uniform); if (handle != null && mtexturelist[dataindex] != openglutils.gl_not_texture) { openglutils.bindtexture(handle, mtexturelist[dataindex], dataindex + 1); } } } } /** * 释放资源 */ public void release() { if (mtexturelist != null && mtexturelist.length > 0) { gles30.gldeletetextures(mtexturelist.length, mtexturelist, 0); mtexturelist = null; } if (mweakfilter.get() != null) { mweakfilter.clear(); } } /** * 设置强度 * @param strength */ public void setstrength(float strength) { mstrength = strength; } }
然后我们构建一个dynamiccolorfilter的基类,方便后续添加其他类型的滤镜,代码如下:
public class dynamiccolorbasefilter extends glimageaudiofilter { // 颜色滤镜参数 protected dynamiccolordata mdynamiccolordata; protected dynamiccolorloader mdynamiccolorloader; public dynamiccolorbasefilter(context context, dynamiccolordata dynamiccolordata, string unzippath) { super(context, (dynamiccolordata == null || textutils.isempty(dynamiccolordata.vertexshader)) ? vertex_shader : getshaderstring(context, unzippath, dynamiccolordata.vertexshader), (dynamiccolordata == null || textutils.isempty(dynamiccolordata.fragmentshader)) ? fragment_shader_2d : getshaderstring(context, unzippath, dynamiccolordata.fragmentshader)); mdynamiccolordata = dynamiccolordata; mdynamiccolorloader = new dynamiccolorloader(this, mdynamiccolordata, unzippath); mdynamiccolorloader.onbinduniformhandle(mprogramhandle); } @override public void oninputsizechanged(int width, int height) { super.oninputsizechanged(width, height); if (mdynamiccolorloader != null) { mdynamiccolorloader.oninputsizechange(width, height); } } @override public void ondrawframebegin() { super.ondrawframebegin(); if (mdynamiccolorloader != null) { mdynamiccolorloader.ondrawframebegin(); } } @override public void release() { super.release(); if (mdynamiccolorloader != null) { mdynamiccolorloader.release(); } } /** * 设置强度,调节滤镜的轻重程度 * @param strength */ public void setstrength(float strength) { if (mdynamiccolorloader != null) { mdynamiccolorloader.setstrength(strength); } } /** * 根据解压路径和shader名称读取shader的字符串内容 * @param unzippath * @param shadername * @return */ protected static string getshaderstring(context context, string unzippath, string shadername) { if (textutils.isempty(unzippath) || textutils.isempty(shadername)) { throw new illegalargumentexception("shader is empty!"); } string path = unzippath + "/" + shadername; if (path.startswith("assets://")) { return openglutils.getshaderfromassets(context, path.substring("assets://".length())); } else if (path.startswith("file://")) { return openglutils.getshaderfromfile(path.substring("file://".length())); } return openglutils.getshaderfromfile(path); } }
接下来我们构建动态滤镜组,因为动态滤镜有可能有多个滤镜组合而成。代码如下:
public class glimagedynamiccolorfilter extends glimagegroupfilter { public glimagedynamiccolorfilter(context context, dynamiccolor dynamiccolor) { super(context); // 判断数据是否存在 if (dynamiccolor == null || dynamiccolor.filterlist == null || textutils.isempty(dynamiccolor.unzippath)) { return; } // 添加滤镜 for (int i = 0; i < dynamiccolor.filterlist.size(); i++) { mfilters.add(new dynamiccolorfilter(context, dynamiccolor.filterlist.get(i), dynamiccolor.unzippath)); } } /** * 设置滤镜强度 * @param strength */ public void setstrength(float strength) { for (int i = 0; i < mfilters.size(); i++) { if (mfilters.get(i) != null && mfilters.get(i) instanceof dynamiccolorbasefilter) { ((dynamiccolorbasefilter) mfilters.get(i)).setstrength(strength); } } } }
总结
基本的动态滤镜实现起来比较简单,总的来说就是简单的json参数、shader、统一变量和纹理绑定需要做成动态构建的过程而已。
效果如下:
动态滤镜效果
该效果是通过解压asset目录下的压缩包资源来实现的。你只需要提供包含shader 、纹理资源、以及json的压缩包即可更改滤镜。
详细实现过程,可参考本人的开源项目:
caincamera
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
上一篇: Android自定义View的使用及其原理知识点总结
下一篇: vue实现行列转换的一种方法