滤镜介绍

目前市面上的滤镜有很多,但整体归类也就几样,都是在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

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

Android OpenGLES如何给相机添加滤镜详解的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部