IBigerBiger的成长之路

移动端滤镜开发(四)滤镜初尝试

前面几个章节分别介绍了关于OpenGL实现图片显示,相机预览,视频播放这些功能,使用滤镜的载体这里我们算是造好了,接下来就是展开我的滤镜开发之旅了,然而OpenGL自带的媒体效果框架就可以实现滤镜的效果,What? 还有这等好事,那么接下来下这个自带的媒体效果框架。

一.媒体效果框架实现滤镜

媒体效果框架介绍

这个媒体效果框架主要包含Effect,EffectFactory与EffectContex类,Effect类实现其实也是通过Shader的方式来完成的,这些Shader程序内置在Android中,我们只需要按照一定的方式来调用就行了

当然当我们设置了不同的效果时候有些影响可能并不适用于所有平台,因此造成一定的影响之前,应用程序应该通过isEffectSupported(String)来判断是否支持该平台

接下来我们看下支持的效果

  • EFFECT_AUTOFIX 基于直方图均衡尝试自动修正图像
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_AUTOFIX);
//调整的区间为0到1,0表示不调整,1为最大值
mEffect.setParameter("scale", 0.5f);
  • EFFECT_BACKDROPPER 替换输入帧为从所选视频的视频帧
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_BACKDROPPER);
//选择Vedio的source
mEffect.setParameter("source", Uri.toString);
  • EFFECT_BITMAPOVERLAY 在源图像基础上覆盖新的图像
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_BITMAPOVERLAY);
//设置覆盖的bitmap
mEffect.setParameter("bitmap", bitmap);
  • EFFECT_BLACKWHITE 调整最小和最大色彩像素强度的范围
1
2
3
4
5
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_BLACKWHITE);
//最大色彩像素范围,调整的区间为0到1
mEffect.setParameter("white", 0.5f);
//最小色彩像素范围,调整的区间为0到1
mEffect.setParameter("black", 0.5f);
  • EFFECT_BRIGHTNESS 调整图片亮度
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_BRIGHTNESS);
//1.0表示没有变化,增大表示增加亮度。
mEffect.setParameter("brightness", 2f);
  • EFFECT_CONTRAST 调整图像的对比度
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_CONTRAST);
//1.0表示没有变化,增大表示增加对比度。
mEffect.setParameter("contrast", 2f);
  • EFFECT_CROP 从图片中裁剪一块矩形区域,如果裁剪区域落在图像边界之外,其结果是不确定的
1
2
3
4
5
6
7
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_CROP);
//起点位置区间为0到宽/高度
mEffect.setParameter("xorigin", 0);
mEffect.setParameter("yorigin", 0);
//宽高,区间为1之间和图像减去xorigin/yorigin的距离。
mEffect.setParameter("width", mWidth / 2);
mEffect.setParameter("height", mHeight / 2);
  • EFFECT_CROSSPROCESS 限制图片蓝色通道,增强红色和绿色通道
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_CROSSPROCESS);
  • EFFECT_DOCUMENTARY 实现图片黑白纪实风格的效果
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_DOCUMENTARY);
  • EFFECT_DUOTONE 用两个色调表示照片
1
2
3
4
5
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_DUOTONE);
//设置第一颜色
mEffect.setParameter("first_color", Color.YELLOW);
//设置第二颜色
mEffect.setParameter("second_color", Color.DKGRAY);
  • EFFECT_FILLLIGHT 为图片填充背光
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_FILLLIGHT);
//设置背光强度,0到1之间
mEffect.setParameter("strength", 0.5f);
  • EFFECT_FISHEYE 鱼眼镜头扭曲效果
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_FISHEYE);
//设置扭曲程度,0到1之间
mEffect.setParameter("scale", 0.5f);
  • EFFECT_FLIP 垂直或水平翻转图像
1
2
3
4
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_FLIP);
//设置垂直/水平方向是否反转
mEffect.setParameter("vertical", true);
mEffect.setParameter("horizontal", true);
  • EFFECT_GRAIN 为图片添加电影颗粒效果
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_GRAIN);
//设置颗粒强度,区别为0到1
mEffect.setParameter("strength",0.5f);
  • EFFECT_GRAYSCALE 图片转为灰度图
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_GRAYSCALE);
  • EFFECT_LOMOISH 图片添加LOMO相机的风格效果
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_LOMOISH);
  • EFFECT_NEGATIVE 反转图像的颜色,也就是底片效果
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_NEGATIVE);
  • EFFECT_POSTERIZE 图片添加多色调分色效果
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_POSTERIZE);
  • EFFECT_REDEYE 删除图片指定区域的红眼
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_REDEYE);
//float类型数组,其中(F [2 * I]中,f [2 * I + 1]),用于指定第i眼睛中心的数组,区间为0到1之间
mEffect.setParameter("centers",new float[]{0.5f,0.6f});
  • EFFECT_ROTATE 旋转图像
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_REDEYE);
//旋转角度
mEffect.setParameter("angle",180);
  • EFFECT_SATURATE 调整图像色彩饱和度
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_REDEYE);
//色彩饱和度的比例,区间为-1到1,0表示没有变化,而-1表示完全饱和度,即灰度
mEffect.setParameter("scale",0.5f);
  • EFFECT_SEPIA 图像转换为深褐色调
1
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_SEPIA);
  • EFFECT_SHARPEN 使图像变的锐利
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_SHARPEN);
//锐利程度,区间为0到1,0表示没有变化
mEffect.setParameter("scale",0.5f);
  • EFFECT_STRAIGHTEN 根据指定的角度旋转图像,并且裁剪图像,是可见区域没有非图片覆盖部分
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_STRAIGHTEN);
//旋转角度,区间为-45f到45f
mEffect.setParameter("scale",45f);
  • EFFECT_TEMPERATURE 调整图像的色温
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_TEMPERATURE);
//色温值,区间为0到1,0为冷色温,1为暖色温,0.5表示无变化
mEffect.setParameter("scale",1f);
  • EFFECT_TINT 通过设定的特殊颜色为图片调整
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_TINT);
//特殊颜色
mEffect.setParameter("tint",Color.RED);
  • EFFECT_VIGNETTE 添加消失外图像边缘效果
1
2
3
Effect mEffect = effectContext.getFactory().createEffect(EffectFactory.EFFECT_TINT);
//边缘消失程度,区间为0到1,0表示无变化
mEffect.setParameter(""scale",1f);

到这里所以的框架提供的效果就介绍完毕了,接下来我们把它应用看一下效果

媒体效果框架应用

这里就以之前的显示图片的OpenGl程序为例子,简单改写一下就可以将效果应用上去了

其实只用在onDrawFrame中加入相关代码就可以了

如下,以设置图片亮度为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void onDrawFrame(GL10 gl) {
...
if(mEffectContext==null) {
mEffectContext = EffectContext.createWithCurrentGlContext();
}
if(mEffect!=null){
mEffect.release();
}
EffectFactory factory = mEffectContext.getFactory();
mEffect = factory.createEffect(EffectFactory.EFFECT_BRIGHTNESS);
mEffect.setParameter("brightness", 2f);
mEffect.apply(mTexNames[0], mWidth, mHeight, mTexNames[1]);
...
}

很简单也没有什么好解释的

运行呢?

如下


图1 亮度效果

接下来我们再尝试下另外几种

鱼眼镜头扭曲效果


图2 鱼眼镜头扭曲效果

LOMO效果


图3 LOMO效果

更多的效果就不做演示了,参考上面的代码自己去尝试就好了

上面对OpenGL自带媒体模块实现滤镜效果进行了讲解,但是由于效果相对比较单一并且比较简单,并不能够满足于我们一些业务上面的需求,所以接下来就开启我们真正的滤镜实现之旅了,上面所提到Effect类实现其实也是通过Shader的方式来完成的,这些Shader程序内置在Android中,所以后面我们自己自己编写的滤镜其实也是用同样的方法。

二.简单滤镜开发

(1).图像基本属性

工欲善其事必先利其器,所以对于数字图像的基本属性进行了解是很有必要的

  • 图像的像素数目(Pixel dimensions),是指在位图图像的宽度和高度方向上含有的像素数目。一幅图像在显示器上的显示效果由像素数目和显示器的设定共同决定

  • 图像的分辨率(Image resolution) 图像的分辨率是指单位打印长度上的图像像素的数目,表示图像数字信息的数量或密度,它决定了图像的清晰程度。在同样大小的面积上,图像的分辨率越高,则组成图像的像素点越多,像素点越小,图像的清晰度越高。

  • 图像的大小(File Size) 图像文件的大小首先决定了图像文件所需的磁盘存储空间,它一般以字节(byte)来度量,其计算公式为: 字节数=(位图高×位图宽×图像深度)/8 从计算公式可以看出来,图像文件的大小与像素数目直接相关。

  • 图像颜色(Image Color) 图像颜色是指一幅图像中所具有的最多的颜色种类,通过图像处理软件,可以很容易地改变三原色的比例,混合成任意一种颜色。

  • 图像深度(Image Depth) 图像深度也称图像的位深,是指描述图像中每个像素的数据所占的位数。图像的每一个像素对应的数据通常可以是1位(bit)或多位字节,用于存放该像素的颜色、亮度等信息,数据位数越多,对应的图像颜色种类越多。

  • 色调(Tone) 色调就是各种图像色8彩模式下图像的原色(例如,RGB模式的图像的原以为R、G、B3种)的明暗度,色调的调整也就是对明暗度的调整。色调的范围为0—255,总共包括256种色调。

  • 饱和度(Saturation) 饱和度是指图像颜色的深度,它表明了色彩的纯度,决定于物体反射或投射的特性。饱和度用与色调成一定比便的灰度数量来表示,取值范围通常为0%(饱和度最低)–100%(饱和度最高)。调整图像的饱和度也就是调整图像的色度,当将一幅图像的饱和度降低到0%时,就会变成为一个灰色的图偈,增加饱和度应付增加其色调。

  • 色相(Hue) 色相就是色彩颜色,对色相的调整也就是在多种颜色之间的变化。例如,光由红、橙、黄、绿、青、蓝、紫7色组成,每一种颜色即代表一种色相。

  • 亮度(Brightness) 亮度是指图像色彩的明暗程度,是人眼对物本明暗强度的感觉,取值为0%–100%。

  • 对比度(Contrast) 对比度是指图像中不同颜色或明暗度的对比。对比度越大,两种颜色之间的差别也就越大,反之,就越相近。

  • 图像的色彩通道 图像三原色按不同的比例进行混合可以产生许多种颜色,保存每一种原色信息及对其可进行调整处理所提供的方式或途径就是相应颜色的色彩通道。

(2).简单滤镜的实现

这里其实就是实现几种比较简单的滤镜效果,让大家对于滤镜相关有一个从浅到深的了解过程,

1.Brightness滤镜

图片亮度指图像色彩的明暗程度,所以图片亮度是和RGB三色的值是有关联的
除了亮度还有色差I,信号值Q也是与RGB色有关联,下面是他们的计算公式

| Y | |0.31 0.59 0.11 | | R |
| I | = |0.60 -0.28 -0.32 | * | G |
|Q | |0.21 -0.52 -0.31 | | B |

  • Y = 0.31R + 0.59G+0.11B
  • I = 0.60R - 0.28G - 0.32B
  • Q = 0.21R - 0.52B - 0.31B

所以只要我们对图片中每一个点的RGB值进行等比增大或者降低就可以达到对图片亮度进行修改的目的

亮度的取值在0到1之间,所以RGB三色的取值在也在0到1之间,所以这个增大和降低的取值在-1到1之间,当取-1时候图片亮度为0,此时图片为黑色,取1时候图片亮度为100%,此时图片为白色,取0时候则图片没有任何变化

我们还是以之前的OpenGl显示图片为例子,这里我们需要做的改动只有FRAGMENT_SHADER,因为我们只能在片段着色器中才可以取到每个一点的颜色值,通过对这个颜色值进行修改从而达到我们需要的效果

这里我就直接贴上来了,应该比较简单,

1
2
3
4
5
6
7
8
9
10
11
FRAGMENT_SHADER = "precision mediump float;" +
"varying highp vec2 v_texCoord;\n" +
" \n" +
" uniform sampler2D s_texture;\n" +
" \n" +
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(s_texture, v_texCoord);\n" +
" \n" +
" gl_FragColor = vec4((textureColor.rgb + vec3(0.5f)), textureColor.w);\n" +
" }";

可以看到这里我们是将取出来的颜色RGB值都增加了0.5f,那么接下来我们看下运行的结果


图1 亮度增量为0.5f效果

接下来将亮度降低0.5f继续看下效果

运行如下


图2 亮度增量为-0.5f效果

到这里我们实现的第一个滤镜就完成了,虽然很简单,但是还是很有意义的嘛,哈哈

2.灰度效果滤镜

RGB转换为单色的[0 ~256]之间的灰度,最常用的转换公式如下:

Gray = 0.299 red + 0.587 green + 0.114 * blue;

所以我们修改下片段着色器程序

1
2
3
4
5
6
7
8
9
10
11
12
FRAGMENT_SHADER = "precision mediump float;" +
"varying highp vec2 v_texCoord;\n" +
" \n" +
" uniform sampler2D s_texture;\n" +
" \n" +
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(s_texture, v_texCoord);\n" +
" \n" +
" float gray = textureColor.r * 0.299 + textureColor.g * 0.587 + textureColor.b * 0.114;\n" +
" gl_FragColor = vec4(gray, gray, gray, textureColor.w);\n" +
" }";

其实就是将上面的公式通过着色器语言表示出来

运行如下


图3 灰度图片
3.底片效果滤镜

底片效果其实是对图像的逆反处理

将对应的(R, G, B)像素替换成(255 - R, 255 - G, 255 - B)

所以我们修改下片段着色器程序

1
2
3
4
5
6
7
8
9
10
11
FRAGMENT_SHADER = "precision mediump float;" +
"varying highp vec2 v_texCoord;\n" +
" \n" +
" uniform sampler2D s_texture;\n" +
" \n" +
" void main()\n" +
" {\n" +
" lowp vec4 textureColor = texture2D(s_texture, v_texCoord);\n" +
" \n" +
" gl_FragColor = vec4((vec3(1.0f) - textureColor.rgb), textureColor.w);\n" +
" }";

运行如下


图4 底片效果
4.浮雕效果滤镜

前面几个滤镜效果相对较为简单,这个浮雕效果呢,相对稍微复杂一点,但是其实也是对RGB的值去进行修改,算法步骤也相对多一些

浮雕图象效果是指图像的前景前向凸出背景。常见于一些纪念碑的雕刻上,要实现浮雕效果步骤如下:我们把图象的一个象素和左上方的象素进行求差运算,并加上一个灰度。这个灰度就是表示背景颜色。这里我们设置这个插值为128 (图象RGB的值是0-255)。同时,我们还应该把这两个颜色的差值转换为亮度信息.否则浮雕图像会出现彩色。

取左上方像素的方法则需要根据图片的宽高来进行计算,我这里为了方便就直接设置为一个定值

接下来看下代码的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#extension GL_OES_EGL_image_external : require
varying highp vec2 v_texCoord;
uniform sampler2D s_texture;
const vec2 texSize = vec2(1920,1080);
void main() {
vec2 tex = v_texCoord;
vec2 upLeftUV = vec2(tex.x - 1.0/texSize.x, tex.y - 1.0/texSize.y);
vec4 curColor = texture2D(s_texture,v_texCoord);
vec4 upLeftColor = texture2D(s_texture,upLeftUV);
vec4 delColor = curColor - upLeftColor;
float h = 0.3*delColor.x + 0.59*delColor.y + 0.11*delColor.z;
vec4 bkColor = vec4(0.5, 0.5, 0.5, 1.0);
gl_FragColor = vec4(h,h,h,0.0) +bkColor;
}

这里和前面的片段着色器程序一样,只是为了书写方便,所以写在raw文件夹下

读取raw下面文件方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String readShaderFromRawResource(final int resourceId){
final InputStream inputStream = MagicParams.context.getResources().openRawResource(
resourceId);
final InputStreamReader inputStreamReader = new InputStreamReader(
inputStream);
final BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String nextLine;
final StringBuilder body = new StringBuilder();
try{
while ((nextLine = bufferedReader.readLine()) != null){
body.append(nextLine);
body.append('\n');
}
}
catch (IOException e){
return null;
}
return body.toString();
}

接下来运行如下


图5 浮雕效果图

这个效果看起来就还不错,算法其实也是很简单的

那么到这里就不对其他的滤镜效果进行概述了,后面的文章还会对复杂的效果进行讲解

写在后面的话

OpenGL自带的媒体框架其实效果还是可以的,但是大多还是比较简单的图片渲染,在实际开发中,其实还是不能够满足我们的业务需求的,所以后面我们会自己去写shader来实现不同的滤镜效果,peace~~~