android高级UI之Paint滤镜
在上一次https://www.cnblogs.com/webor2006/p/12178704.html中对于Paint的三大高级用法【渲染,滤镜,Xfermode】之一的渲染进行了学习,这次来学习滤镜这种技法,依旧参考博主https://www.jianshu.com/p/4cbeffbf2f19进行学习~~
啥是滤镜呢?如果有玩过单反的人肯定都非常之熟悉,像如果遇到阴天也想要拍出天蓝蓝一片的效果就可以借助于在镜头前面加一个滤镜片达到一种预其的效果,如下:
也就是在原图像上增加一点其它的颜色达到另一种不同的效果,具体滤镜的效果可以感受一下如下对比图:
从上面的效果图能感受到滤镜就是对原图像进行色彩调整达到我们所预期的效果,如今像拍照app一般都会有相关滤镜的效果,俗称“美颜”,那么需要对图像的色彩进行调整操作,我们就需要知道如下几个概念:图像构成、颜色通道、颜色模式、颜色矩阵。
图像构成:
关于这块从博主的描述中可以发现非常核心的信息:
其实像之前学习JVM中的class字节文件时也能感受到这一点,对于二进制文件都是有一定的规则的,咱们用之前学习JVM的“010 Editor”软件对一个png的图片简单瞅一下它的二进制信息:
而关于png格式具体包含哪些信息看一下博主贴出来的,简单了解下既可,对于咱们学习Paint滤镜而言不用掌握这么细:
PNG的文件结构 对于一个PNG文件来说,其文件头总是由位固定的字节来描述的: 十进制数 137 80 78 71 13 10 26 10 十六进制数 89 50 4E 47 0D 0A 1A 0A 其中第一个字节0x89超出了ASCII字符的范围,这是为了避免某些软件将 PNG文件当做文本文件来处理。文件中剩余的部分由3个以上的PNG的数据块(Chunk)按照特定的顺序组成,因此,一个标准的PNG文件结构应该如下: PNG文件标志 PNG数据块 …… PNG数据块 PNG数据块(Chunk) PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。 下表就是PNG中数据块的类别,其中,关键数据块部分我们使用深色背景加以区分。 为了简单起见,我们假设在我们使用的PNG文件中,这4个数据块按以上先后顺序进行存储,并且都只出现一次。 数据块结构 PNG文件中,每个数据块由4个部分组成,如下: 名称 字节数 说明 Length (长度) 4字节 指定数据块中数据域的长度,其长度不超过(231-1)字节 Chunk Type Code (数据块类型码) 4字节 数据块类型码由ASCII字母(A-Z和a-z)组成 Chunk Data (数据块数据) 可变长度 存储按照Chunk Type Code指定的数据 CRC (循环冗余检测) 4字节 存储用来检测是否有错误的循环冗余码
图片除了png格式之后,不还有jpeg,bmp等等各种不同的类型么?咱们再来看一个jpeg格式的图片的二进制形式:
也就是对于不同的图片格式,其里面的数据规则是不一样的,所以到了cpu那其解析数据规则也不一样,而根据之前咱们分析JVM的class字节码的经验来看,其实对于一个二进制图像文件一般都是“标志+图像信息+图像数据”来构成的,像之前咱们分析Class它也是有一定结构的,回忆一下:
这里又再次证明知识的相通性,任何知识都不是独立存在的,所以对于工作中暂时用不到的技能去学习其实并不是浪费生命,在未来的某个时刻终会派上用场的。
而了解上述图像的构成,对于咱们这次要学的滤镜有啥关系么?有!!!看上面标红的“图像信息”,对于一张图像它包含有两个非常重要的信息:颜色通道、颜色模式,它们就跟我们要实现的滤镜效果有很大的关系了,所以下面对这俩进行了解。
颜色通道,颜色模式:
- 颜色通道【重点!!!】:
上面这段博主的说明,可以发现颜色通道就是用来专门记录图片颜色信息的,而对于我们要实现的滤镜效果其实就是对原图纯颜色进行改变。 - 颜色模式:
颜色模式,其实比较好理解,在当时学习Glide架构时https://www.cnblogs.com/webor2006/p/12313876.html,其中有个说明:
这其实就是颜色的模式,这里看一下博主的说明:
而Android的颜色模式采用的是RGBA。
那了解了颜色通道和颜色模式之后,咱们滤镜效果的实现就是在原来的颜色模式下,对它的颜色通道的数值进行更改达到我们想要的不同效果的目的,而对颜色通道数值的更改是通过颜色过滤达成的,那怎么就能通过颜色的过滤达到更改原图的颜色通道数值的变化从而产生一种滤镜的效果呢?还得
颜色矩阵:
这块就比较抽象了,但是要想理解滤镜的原理这块必须得了解:
用数组来表示这个颜色矩阵的话就类似于下面这样:
谈到矩阵可能就想到数学了,对于学渣的早就忘得一干二净了,对这概念还是有点瑟瑟发抖的,但是!!!这里其实用不了拥有高深的数据功底也能容易能理解的,先来回忆一下啥叫矩阵,百度走起:
嗯,那对于上面用EXCEL所看到的颜色矩阵其实它是用一个四阶矩阵来表示的,如下:
哦,原来Excel表格中变成四阶矩阵形态变成这样了,其中发现这个矩阵有个规律:1-1、2-2、3-3、4-4的位置上分别表示RGBA,而其它位置全为0,那有啥讲究么?其实默认情况下矩库处的RGBA默认是1,也就是:
其中为1的可以理解成颜色的系数,如果是1则表示原来的RGBA的值不会改变,而如果将其更改则相应的颜色通道就会进行相应的改变,如何理解,下面举例说明一下,比较好理解:
此时的颜色通道就会变化为:
①、R通道不变;
②、G通道颜色降低一半,比如原来G的值为255,此时在颜色矩阵的影响下,该值会变为255 * 0.5 = 127.5,很明显颜色就会变暗;
③、B通道颜色增加0.5倍,比如原来B的值为100,此时在颜色矩阵的影响下,该值会变为100 * 1.5 = 150,很明显颜色就会增亮;
④、A通道颜色降低一半,也就是透明度降低一半;
也就是整个颜色通道值的改变过程其实可以用下图来表示:
这里一定要记住,原图的颜色矩阵信息是一个一阶矩阵:
好,接下来有个问题导致上面的这个矩阵会发生调整,啥问题呢?
如果我想G通道在原来的值基础之上增加100,而不是在原来用一个倍数进行增加,那目前这个四阶矩阵很明显实现不了了,因为这个100我加在哪都没用:
所以,此时不得不再增加一列来应对这种增加固定值的方式,这一列称之为“哑元坐标”,也就是变成了一个4*5的矩阵了:
最终的计算就会变为:
而对于上面的计算其实采用的是矩阵运算,所以下面再来对矩阵运算做一个了解。
矩阵运算:
先来上一个博主贴出来的矩阵数学运算公式:
而对于上面公式的理解,结合颜色矩阵的运算可以理解成这样:
颜色分量:指原图的颜色通道信息。
颜色矩阵:既对其原图的颜色通道产生变化的矩阵。
那最终的颜色通道的值可以表示为:
也就是原图的颜色通道值跟颜色矩阵进行相乘得到,那对于具体每个颜色通道最终的计算细节是这样的:
是不是可以发现:
第一行决定红色;
第二行决定绿色;
第三行决定蓝色;
第四行决定了透明度;
第五列是颜色的偏移量;
为了进一步巩固对于矩阵算法的认识,下面再来看三个例子:
例子一:
如果把这个矩阵作用于各颜色分量的话,R=A*C,计算后会发现,各个颜色分量实际上没有任何的改变(R'=R G'=G B'=B A'=A)。
例子二:
矩阵计算后会发现红色分量增加100,绿色分量增加100,这样的效果就是图片偏黄,因为红色和绿色混合后得到黄色,黄色增加了100。
例子三:
将绿色分量乘以2变为原来的2倍。
滤镜实现:
有了前面原理的了解,那接下来可以具体实现一下滤镜效果了。关于Android中对图片来实现滤镜效果其实api比较简单的,如下:
而对于滤镜效果可以自己DIV,这里以平常比较经典的效果为例:
平移运算---加法:
这里还是以小姐姐图片为例:
package com.paintgradient.test.paintfilter; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.paintgradient.test.R; public class FilterView extends View { Paint paint; Bitmap bitmap; public FilterView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init() { paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.RED); bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy2); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); setLayerType(View.LAYER_TYPE_SOFTWARE, null); RectF rectF = new RectF(200, 100, bitmap.getWidth() + 200, bitmap.getHeight()); paint.reset();//先将它还原,以防干扰了原图的输出 paint.setColor(Color.RED); //绘制原图 canvas.drawBitmap(bitmap, null, rectF, paint); // 平移运算---加法 ColorMatrix colorMartrix = new ColorMatrix(new float[]{ 1, 0, 0, 0, 0, 0, 1, 0, 0, 100, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, }); paint.setColorFilter(new ColorMatrixColorFilter(colorMartrix)); RectF rectF2 = new RectF(200, 100 + bitmap.getHeight(), bitmap.getWidth() + 200, bitmap.getHeight() * 2); //基于原图实现滤镜 canvas.drawBitmap(bitmap, null, rectF2, paint); } }
代码比较好理解,先绘制一张原图,然后再绘制一张添加了滤镜效果的图,目前这个矩阵是将Green+100,所以说整个照片会偏绿,如下:
反相效果 -- 底片效果:
底片效果的原理其实很简单,整个颜色的最大值不是255么,然后其实就是用这个255-原来通道的颜色值并作为当前颜色通道的值既可,具体矩阵改动如下:
效果:
图片美颜---乘法 -- 颜色增强:
让每一个颜色通道增加0.2倍,所以整个图片就显得比较亮了,效果如下:
明显小姐姐变得白皙了~~
黑白照片:
直接先上代码:
其去色原理就是只要把R G B 三通道的色彩信息设置成一样的值,那么图像就会变成灰色,比如:
而为了保证图像亮度不变,同一个通道里的R+G+B =1:
所以总结来说就是将我们的三通道变为单通道的灰度模式,这块有些抽象,有关色彩美学的一些知识,了解既可。
发色效果---(比如绿色和蓝色交换):
这是种啥效果呢?比如正常矩阵不是这样的嘛:
此时咱们将绿色通道和蓝色通道的这两值进行交换:
交换成:
看效果变成啥了:
原图是黄色脸蛋红色衣服,将通道交换之后反过来了,红色脸蛋黄色衣服。。
复古效果:
最后再来看一种效果:
发黄的老照片,具体为啥要这样设置,我也木知,重点学会Paint滤镜的原理既可。