若是曾经查看过系统UI的源码, 会发现其中使用了一些渲染效果,例如将图片加上黑白、怀旧的效果,生活中常用的逆天美颜相机,其中的原理就是使用了滤镜效果、颜色通道过滤。若还要深究其原理组成,便涉及到了高等数学里的矩阵变换,也就是Android 中的颜色矩阵!此篇文章便来一探究竟如何实现滤镜和其原理组成。

(关于矩阵这一块,无需过度深究数学部分,此处为了充分理解渲染效果,只需了解大概原理,利用其API完成简单滤镜效果。)

其实滤镜效果就是对图像进行一定的过滤加工处理。例如PS软件中常见的滤镜效果:模糊、锐化、素描等等,以上功能便涉及到滤镜效果的矩阵。使用Paint设置滤镜效果,可分为以下两个方面:

  • Alpha滤镜处理
  • 颜色RGB的滤镜处理

以上两个方面正好对应Paint的两个重点API,分别是以下:

  • setMaskFilter(MaskFilter filter) 是基于整个画面来进行过滤。
  • setColorFilter(ColorFilter filter)是对每个像素的颜色进行过滤。

此篇文章分为以上两个方面来详细解析滤镜、颜色过滤的奥秘。


Android 高级UI解密 (一) :实例详解Paint 与 高级渲染



一. Alpha滤镜处理

Alpha就是对透明度的处理,涉及到MaskFilter这个类,它是一个抽象类:

MaskFilter是在绘制Alpha通道遮罩之前执行转换的对象的基类。 MaskFilter的子类可以通过Paint的setMaskFilter方法设置到画笔中。 而模糊遮罩滤镜BlurMaskFilter和浮雕遮罩滤镜EmbossMaskFilter是实现MaskFilter的子类。

1. 模糊遮罩滤镜BlurMaskFilter

见名识意,此滤镜类似于一种模糊效果。以构造方法中指定的半径模糊其边缘,另外还可指定模糊的风格,模糊其内部、外部、边框或者本身。

//构造方法BlurMaskFilter (float radius, BlurMaskFilter.Blur style)

参数说明:

  • radius:模糊区域半径;
  • style:模糊的格式 (BlurMaskFilter.Blur 类型)
    • INNER:模糊内部边框,外部不变;
    • NORMAL:模糊内外边框;
    • OUTER:内部不变,模糊外部;
    • SOLID:在边界内部画实体,模糊外面;

注意:注意以上四种类型的解释差异,模糊内部和模糊内部边框是不同的!

  • 内外部边框相关:INNER只是简单模糊其内部边框,图片外部呈现淡白色;而NORMAL是直接模糊内外边框,图片外部已经呈现图片边缘的背景色;
  • 内外部相关:OUTER效果比较奇葩,图片内部空白,外部模糊成图片边缘的背景色;SOLID则是保持内部实体,外部模糊成图片边缘的背景色。

代码测试后的效果图如下:

2. 浮雕遮罩滤镜(EmbossMaskFilter)

EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)

参数说明:

  • direction:指定光源方向的3个标量[x,y,z]的数组;
  • ambient:指定环境光量强度[0,1];
  • specular:指定镜面反射系数(例如8);
  • blurRadius:指定模糊半径(例如3);
        mPaint.setMaskFilter(new EmbossMaskFilter(new float[]{400,100,100}, 0.5f, 60, 80));        canvas.drawBitmap(bitmap, 400, 100, mPaint);

注意:查看EmbossMaskFilter类的构造方法源码,发现其真正创建对象是调用了native方法,因此这也表明google在android 的graphics包中准备了一系列的滤镜,也需要传入相应的参数,而其中参数的运算是非常复杂的,涉及到矩阵运算

需要强调的是“矩阵运算”并非只是简单的公式计算,试想一块手机屏幕所含的像素点有多少,假设是1080P,若一张图片覆盖整个屏幕,需要处理每一个像素点,工作量是很大的,为了计算效率而采用了native方法,交由它来完成。




二. 颜色RGB的滤镜处理

滤镜的所有处理效果都是通过颜色矩阵的变换实现的,例如生活中常见的美颜相机,它实现的一些特效:高光、复古、黑白等滤镜。那么首先来了解何为矩阵?其中涉及到多阶矩阵,这里以二阶矩阵为例进行讲解。

1. 矩阵简析

(1)定义

(2)矩阵乘法

矩阵其实就相当于一个二维数组,而重点则在于矩阵之间的计算,特别是乘法计算与后续滤镜计算有关,乘法运算如下:

矩阵的乘法计算步骤如下:

  • 将第一个矩阵A的第一行,与第二个矩阵B的第一列的数字分别相乘,得到的结果相加,最终的值做为结果矩阵的第(1,1)位置的值(即第一行,第一列)。
  • 同样,A矩阵的第一行与B矩阵的第二列的数字分别相乘然后相加,结果做为结果矩阵第(1,2)位置的值(即第一行第二列)。
  • 依次类推。

注意:矩阵A乘以矩阵B和矩阵B乘以矩阵A的结果是不一样的。

示例如下:


2. 色彩信息的矩阵表示

鲁迅曾经说过(并没有):矩阵运算对于Android像素处理的意义极大!

以上在重点强调矩阵中的乘法运算后,接下来将解密其奥妙。颜色的组成为ARGB,这里先不讨论Alpha透明度,以RGB为主。举个例子:美颜相机中的图片美白原理就是将红色、绿色、蓝色进行位移,可以获得不同的效果,而其中的计算则可以借助矩阵完成。

(1)四阶表示

ARGB的四阶表达式如下:

如果想将色彩(0,255,0,255)更改为半透明时,可以使用下面的的矩阵运算来表示:

其实颜色变换就是将矩阵看成一套数学模型,便于计算ARGB值。

(2)五阶矩阵

任何一个颜色都是三色素(红绿蓝)构成的,也就是RGB。例如黄色是由红色和绿色形成。

考虑下面这两个变换需求:

  • 红色分量值更改为原来的2倍;
  • 绿色分量增加100;

若要实现以上变换,四阶矩阵的乘法无法实现。根据以上ARGB四阶矩阵的运算规则,只能进行乘法运算,而无法进行加法运算,因此在四阶色彩变换矩阵上增加一个“哑元坐标”,来实现所列的矩阵运算,也就是“五阶矩阵”。过程如下图:

第一个矩阵中前四列中任然代表ARGB,而第五列则是分量值,即绿色需要加的100,200 = 1*100+100


3. 实例

(1)需求

通过矩阵变换讲一个图片、颜色块,过滤掉其中的红色、绿色,只留下蓝色。

(2)代码

查看以下代码,绘制出以下两个图形进行对比:

  • 第一个矩形设置其ARGB颜色,整体偏红色;
  • 重点是第二个矩形的颜色过滤器设置:创建其ColorMatrix 矩形变换对象,其中根据上部分公式讲解,结合过滤红色、绿色的需求。因此第一行第一列为R值为0,第二行第二列为G值为0,第三行第三列为B值为1,第四行第四列为A值为1,最后一列是分量值,皆为0。
        //=====颜色RGB的滤镜处理===        mPaint.setColor(Color.argb(255,200,100,100));        canvas.drawRect(200, 200, 400, 400, mPaint);        canvas.translate(400,0);        //五阶矩阵,R、G为0,A、B为1,第五列为分量,不需要进行平移为0        ColorMatrix matrix = new ColorMatrix(new float[]{            0,0,0,0,0,            0,0,0,0,0,            0,0,1,0,0,            0,0,0,1,0,        });        //设置颜色过滤器        mPaint.setColorFilter(new ColorMatrixColorFilter(matrix));        canvas.drawRect(200, 200, 400, 400, mPaint);

(3)效果展示

根据以上对比图实践成功,将第一个矩形中的颜色(#C86464)过滤,仅留下蓝色(#000064)。若纯色块对比不明显,难以理解“过滤”的概念,直接使用图片对比,将以上代码中的drawRect改成drawBitmap即可,效果如下:

注意:其滤镜的原理还是在于设置的颜色过滤器——矩阵变换,同理可只过滤掉红色、绿色、蓝色或任意组合,都可由矩阵变换完成。其中不仅可以修改ARGB值(乘法),同样可以修改五阶矩阵中代表分量值的第五列(加法),不同的修改方式可以形成各式滤镜效果。例如美图秀秀中的各种滤镜其原理是如此,内部包含大量的滤镜模板(库)。


4. 实践滤镜效果——色彩运算

以上简单的举个例子实践了矩阵变换,下面来总结归纳其矩阵运算,无非是以下两种:

  • 色彩的平移运算(加法运算)
  • 色彩的缩放运算(乘法运算)

以下代码实践5种滤镜效果来熟悉运用矩阵运算。

(1)反相效果 —— 曝光

常见的照相机中的曝光也就是矩阵运算中的反向,即设原先的ARGB值为100,200,250,用最大值255减去原来的值,结果为155,55,5,就是“曝光”。

矩阵运算解析:其余代码同上个代码示例相同,这里主要是矩阵运算方面的变化:反向效果涉及到用255减去原值,因此直接结合乘法与加法(分量值),可实现该结果!

代码示例如下:

        //曝光效果        ColorMatrix matrix = new ColorMatrix(new float[]{            -1,0,0,0,255,            0,-1,0,0,255,            0,0,-1,0,255,            0,0,0,1,0,        });

图片展示效果如下:

(2)美白效果 —– 颜色增强

矩阵运算解析:首先需要知道1f是图像原色,即不改变图像滤镜。若要增强颜色达到一种美白的效果,只需要将RGB值稍加增大即可。

代码如下:

        //美白效果        ColorMatrix matrix = new ColorMatrix(new float[]{            1.2f,0,0,0,0,            0,1.2f,0,0,0,            0,0,1.2f,0,0,            0,0,0,1.2f,0,        });

图片展示效果如下:

(3)黑白效果

去色原理:只要把RGB三通道的色彩信息设置成一样,即R=G=B,那么图像就变成了灰色,并且为了保证图像亮度不变,同一个通道中的 R+G+B=1。

例如 0.213+0.715+0.072 = 1,三个数字是根据色彩光波频率及色彩心理学计算出来的。(人对色彩的感知是融合色彩显示和视觉心理成分的,例如你盯着一个纯色快看一段时间,再看向别的事物,此时你看的事物都是自带滤镜的)

矩阵运算解析:根据以上三个值设置RGB即可得到黑白图像的效果,但是需要将五阶矩阵中代表RGB的前四阶中代表各列中每一行的值都设置,A无需考虑,最后以列分量也无需考虑。

代码如下:

        //黑白图片        ColorMatrix matrix = new ColorMatrix(new float[]{                0.213f, 0.715f,0.072f,0,0,                0.213f, 0.715f,0.072f,0,0,                0.213f, 0.715f,0.072f,0,0,                0,      0,      0,    1f,0,        });

图片展示效果如下:

(4)色彩反射效果

何为反射效果?例如将图像中红色的成分替换成绿色的成分,绿色的成分替换成红色的。

        //原始效果        ColorMatrix matrix = new ColorMatrix(new float[]{            1f,0,0,0,0,            0,1f,0,0,0,            0,0,1f,0,0,            0,0,0,1f,0,        });

矩阵运算解析:以上是图像原始效果,即ARGB皆为1F,即使设置了此颜色过滤,图像并无任何改变。与此对比,要实现色彩反射效果,比如红色和绿色交换—-把第一行和第二行交换即可。

代码如下:

        //发色效果(比如红色和绿色交换----把第一行和第二行交换)        ColorMatrix matrix = new ColorMatrix(new float[]{            0,1f,0,0,0,            1f,0,0,0,0,            0,0,1f,0,0,            0,0,0,1f,0,        });

图片展示效果如下:

(5)复古效果

矩阵运算解析:这是美颜相机中常见的一款滤镜形式,矩阵中有特定的算法模板。

代码如下:

        //复古风格        ColorMatrix matrix = new ColorMatrix(new float[]{                1/2f,1/2f,1/2f,0,0,                1/3f,1/3f,1/3f,0,0,                1/4f,1/4f,1/4f,0,0,                0,0,0,1f,0,            });

图片展示效果如下:




三. ColorMatrix 相关详解

上一点讲解了多个有关矩阵变换的例子,对实践矩阵运算和其产生的效果稍有理解,如果涉及到美容相机或者图像处理的一些效果需求,绝大可能会用到矩阵运算,而矩阵运算肯定涉及到ColorMatrix,此部分内容来详细解析ColorMatrix

ColorMatrix就是4x5矩阵,用于转换位图的RGB颜色和Alpha分量。 该矩阵可以作为单个数组传递,并按以下方式处理:

  [ a, b, c, d, e,    f, g, h, i, j,    k, l, m, n, o,    p, q, r, s, t ]

当应用于颜色[R,G,B,A]时,每个值的范围是[0, 255],生成的颜色计算如下:

   R’ = a*R + b*G + c*B + d*A + e;   G’ = f*R + g*G + h*B + i*A + j;   B’ = k*R + l*G + m*B + n*A + o;   A’ = p*R + q*G + r*B + s*A + t;

1、构造方法

(1)用指定的值数组创建一个新的Colormatrix

        ColorMatrix matrix = new ColorMatrix(new float[]{});

代码示例:

ColorMatrix matrix = new ColorMatrix(new float[]{                1.2f,0,0,0,0,                0,1.2f,0,0,0,                0,0,1.2f,0,0,                0,0,0,1.2f,0,        });

(2)创建一个新的Colormatrix,后续设值

        ColorMatrix matrix = new ColorMatrix();        float[] scr = {...};        matrix.set(src)

2.设置色彩的缩放函数(矩阵的乘法运算)

setScale(float rScale, float gScale, float bScale, float aScale)

API作用:设置此颜色矩阵按指定的值进行缩放。
参数:分别是设置R、G、B、A相乘的值。

注意:在上一部分的第四点中已经介绍过矩阵变换中两种重要运算 —— 乘法和加法,并且在以上示例中都是直接修改 4*5 数组矩阵。Colormatrix提供的此API可以轻易设置R、G、B、A需要相乘的值。另外追踪其源码实现也很简单,就是根据参数设置值与数组中对应的R、G、B、A相乘。


3.设置饱和度(矩阵的加法运算)

setSaturation(float sat)

API作用:设置矩阵以影响颜色的饱和度。
参数: sat参数指映射到灰色的值。 1代表原色,0代表灰色,>1则增加饱和度。

API源码

    public void setSaturation(float sat) {        reset();        float[] m = mArray;        final float invSat = 1 - sat;        final float R = 0.213f * invSat;        final float G = 0.715f * invSat;        final float B = 0.072f * invSat;        m[0] = R + sat; m[1] = G;       m[2] = B;        m[5] = R;       m[6] = G + sat; m[7] = B;        m[10] = R;      m[11] = G;      m[12] = B + sat;    }

源码剖析

有详细查看上一部分内容的读者,你会发现源码中这个三个特殊值很熟悉,它就是在讲解滤镜中黑白效果中有提到的去色原理:R+G+B=1从而图片呈现灰色,同时考虑到色彩光波频率及色彩心理学,计算得出最佳值RGB最佳值:0.213+0.715+0.072 = 1。因此在此基础之上,根据参数设置的值来修改RGB值,达到图像饱和度变化!

实例

下面实现一个简单的demo,在onDraw方法中设置图像的颜色过滤器,设置饱和度为0,在onTouchEvent方法中监听点击处理,每次触控其饱和度以0.3f 增加,查看图像变化效果:

(代码文末提供,在此不赘述)

效果分析

查看以上效果符合预期情况,最初设置参数为0,因此图像呈现出灰色,随着不断点击,饱和度依次增加,即RGB值逐渐增加,颜色恢复成原色,接着点击,参数值大于1,图像明显过饱和。


4.色彩旋转函数

setRotate(int axis, float degrees)

API作用:通过指定的值设置颜色轴上的旋转。
参数: axis代表旋转轴,0红色轴,1绿色,2蓝色;degrees代表旋转的度数。

注意: 类似于上图中的3D效果,例如这里指定围绕B轴选装,则B值不变,R、G值会随之改变,而且一圈360度旋转完会再次恢复到原始颜色图像。

实例

下面实现一个简单的demo,在onDraw方法中设置图像的颜色过滤器,设置围绕R轴旋转,在onTouchEvent方法中监听点击处理,每次触控其旋转度数以20f增加,查看图像变化效果:

(代码文末提供,在此不赘述)

效果分析

查看以上效果,根据设置是围绕R轴旋转,随着度数增加,图像渐渐呈现红色,最终又慢慢恢复成图像原色。既然将此颜色过滤器指定围绕R轴,随着旋转角度增加,达到某一个临界点,图像会逐渐过滤掉所有颜色,只剩下红色。继续增加,颜色接着改变,直至旋转到360度恢复成原图像。


5. ColorFilter的子类

ColorFilter类:一个颜色过滤器可以和Paint一起使用来修改用这个颜料绘制的每个像素的颜色。从它的名字也可知,为绘制设置颜色过滤。颜色过滤就是为绘制的内容设置统一的过滤规则。

Paint.setColorFilter(ColorFilter filter)

一般是通过Paint画笔设置其颜色过滤器,由于ColorFilter是抽象类,使用的是它的三个子类,如下:

(1)ColorMatrixColorFilter 色彩矩阵的颜色顾虑器

类作用:通过4x5彩色矩阵转换颜色的彩色滤镜。 这个滤镜可以用来改变像素的饱和度,从YUV转换到RGB等。

//构造函数new ColorMatrixColorFilter(ColorMatrix matrix);

构造方法参数:就是**ColorMatrix**4x5矩阵,用于转换位图的RGB颜色和Alpha分量。

注意:本篇文章第二大部分颜色RGB的滤镜处理,使用的都是ColorMatrixColorFilter 色彩矩阵的颜色顾虑器,在此无需赘述。

(2) LightingColorFilter 光照颜色过滤器(过滤颜色和增强色彩)

类作用:可用于模拟简单照明效果的滤色器。 一个LightingColorFilter由两个参数定义,一个用于将源颜色(称为colorMultiply)和一个用于添加到源颜色(称为colorAdd)的颜色相乘。 这个彩色滤光片保持不变的alpha通道。

给定源颜色RGB,由此得出R’G’B’颜色:

 R' = R * colorMultiply.R + colorAdd.R G' = G * colorMultiply.G + colorAdd.G B' = B * colorMultiply.B + colorAdd.B
//构造函数new LightingColorFilter(int mul, int add);

构造方法参数: mul是与源RGB相乘的值;add是与源RGB相加的分量值。

注意:此方法就是结合了矩阵运算中的乘法和加法,更加简化。需要注意的是参数类型虽为int值,但规定为颜色值,即16进制的值,例如0x00ff00,说白了就是范围 [0,255]区间的颜色值。

实例

这里做一个简单的测试,设置LightingColorFilter 光照颜色过滤器的两个参数分别为0x00ff00,0x000000。0x00ff00就是一个绿色值(原谅色~),会与RGB源值相乘,而这里相加的值设置为0,不做修改。因此设置该光照颜色过滤器后,图像整体应该呈现出绿色。

        ......        mPaint.setColorFilter(new LightingColorFilter(0x00ff00, 0x000000));        canvas.drawBitmap(bitmap, null, new RectF(200, 200, 400, 400*bitmap.getHeight()/bitmap.getWidth()), mPaint);

效果如下:

效果分析

图片效果与理想效果相符,因此根据此API可轻易完成矩阵变换中的乘法和加法运算,实现需求效果。

(3) PorterDuffColorFilter 图形混合滤镜(图形学理论)

类作用:一种彩色滤光片,可用于使用单色和特定的Porter-Duff复合模式为源像素着色。 就是使用一个指定的颜色和一种指定的 PorterDuff.Mode 来与绘制对象进行合成。

//构造函数new PorterDuffColorFilter(int color, PorterDuff.Mode mode); 

构造方法参数: color 参数是指定的颜色;mode 参数是指定的 Mode,即PorterDuff.Mode

注意: PorterDuffColorFilterComposeShader两者都使用到了PorterDuff.Mode,其中一个是颜色过滤器,一个是着色器,而且PorterDuffColorFilter颜色过滤器只能指定一种颜色作为源,而不是一个 Bitmap。




文章小结

此篇文章的主要是研究Paint的两个重点API:从Alpha滤镜处理、颜色RGB的滤镜处理两个方面拓展开,其中涉及到了高数知识——矩阵运算,此篇为了研究颜色过滤原理稍作介绍,并实践展示了几个滤镜效果,学会API实际运用。

此篇文章是有关于有关Paint的高级使用,结合上一篇Paint的基本使用,Paint相关知识重点暂时介绍到这里,下一篇文章将开始归纳Canvas画布相关内容。


(代码整理中,后续会提供)

若有错误,欢迎指教~

更多相关文章

  1. Android(安卓)使用shape来优化界面效果
  2. Android(安卓)ColorMatrix类图像颜色处理-黑白老照片、泛黄旧照
  3. Android图形---OpenGL(三)
  4. 一起学android之给图片添加涂鸦(文字)(37)
  5. Android(安卓)修改状态栏颜色
  6. ProgressBar 颜色的设置
  7. Android中设置TextView的颜色setTextColor
  8. 使用系统资源的引用总结以及收到短信后给一个notification提示
  9. Android中关于Bitmap的裁剪缩放和创建

随机推荐

  1. Android(安卓)screen size and densities
  2. googlemap学习
  3. 2013.12.05(6)——— android ViewPagerInd
  4. Android视频播放器横竖屏自动切换
  5. android调试中怎样使用gcc提供的工具
  6. Android(安卓)Dialog全屏
  7. Android(安卓)Opengl
  8. 获取系统版本内核版本信息
  9. Android开发-DesignDemo-AndroidStudio(
  10. android设置默认短信应用(非弹框)