欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android OpenGLES如何给相机添加滤镜详解

程序员文章站 2022-04-09 13:41:09
滤镜介绍 目前市面上的滤镜有很多,但整体归类也就几样,都是在fragment shader中进行处理。目前滤镜最常用的就是 lut滤镜以及调整rgb曲线的滤镜了。其他...

滤镜介绍

目前市面上的滤镜有很多,但整体归类也就几样,都是在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、统一变量和纹理绑定需要做成动态构建的过程而已。

效果如下:

Android OpenGLES如何给相机添加滤镜详解
动态滤镜效果

该效果是通过解压asset目录下的压缩包资源来实现的。你只需要提供包含shader 、纹理资源、以及json的压缩包即可更改滤镜。

详细实现过程,可参考本人的开源项目:
caincamera

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。