第八章、混合模式
混合模式之AvoidXfermode
混合模式是Paint绘图中最难的部分,它能够将两张图片无缝结合,实现类似PS中两张图的融合效果。
在Android中,混合模式主要是通过Paint类中的Xfermode setXfermode(Xfermode xfermode)
函数来实现的,它的参数Xfermode是一个空类,我们主要靠它的子类来实现不同的功能。
派生自Xfermode的子类有AvoideXfermode、PixelXorXfermode、PorterDuffXfermode。
由于PixelXorXfermode在API 16中已经过时,并且它只是一个针对像素的简单异或运算,返回的值Alpha值始终等于255,所以对操作颜色混合不是特别有效,加之平时几乎不怎么使用,所以这里不讲解。
Xfermode除了PorterDuffXfermode是部分支持硬件加速的,其他全部完全不支持硬件加速
所以在使用Xfermode时,为了保险起见,先做两件事情:
- 禁用硬件加速
- 使用离屏绘制
关于离屏绘制后面再讲,我们只需要知道把绘制的核心代码放在canvas.save()和canvas.resotre()函数之间即可。
AvoidXfermode
AvoidXfermode在 API 16中被弃用了,但目前还没有能替代它的方法,所以在API 16以上的平台还是支持该类的。
其构造函数如下:
参数介绍:
- opColor:一个十六进制的AARRGGBB形式的颜色值
- tolerance:表示容差,这个概念我们后面再西江
- mode:取值为Mode.TARGET和Mode.AVOID。前者表示将指定颜色替换掉,后者表示将Mode.TARGET的相反区域颜色替换掉。
示例一 替换颜色:
这里以Mode.TARGET为例,来替换一些颜色。
先准备一个控件并将它初始化,将paint设置为红色,表示之后他将用红色去取代别的颜色
在绘图时,使用离屏绘制,将绘图代码全部放在canvas.save()和canvas.restore()函数之间
将图片大小改为控件的一半,并将其画出来,然后为paint设置AvoidXfermode,这里将目标颜色改成WHITE,容差改成100,Mode是TARGET,就是将颜色为White或者与其值(十进制颜色值)相差100的颜色改成红色,然后用该paint通过drawRect画出矩形,和bitmap贴合。容差最小差0(和该颜色同色,最大255,和指定颜色是两种完全不同的颜色,比如#000000和#ffffff)
融合出来的图片就是 图片上几乎为白色的颜色
效果…效果…这个时候就尴尬啦!
哇- -! 我的V7 28.0.0包下既然没有AvoidXfermode的类…貌似已经被抛弃了?
只能靠你们猜想下或者看下该blog的效果惹:
Android图像处理——Paint之Xfermode
示例二、融合两张图片:
如上面所说的,既然可以将图片中的白色在容差100下替换成红色,那我们能不能不要把它替换成纯色,而是替换成一张图片呢?
答案是可以的:
上面就可以做到啦。
但是我还是AvoidXfermode包,所以这里就不展示的。
AvoidXfermode绘制原理
咋AvoidXfermode前后分别绘制了一张图片,在绘制第二张图片时,如果没有设置AvoidXfermode,则就是普通的绘图方式,直接将绘制的图形覆盖Canvas对应位置原有的像素。如果设置了Xfermode,就会按照Xfermode具体的规则来更新Canvas中对应位置的像素。
对于AvoidXfermode而言,这个规则就是先把目标区域中的颜色值清空,然后再替换成目标颜色。
混合模式之PorterDuffXfermode
构造函数如下:
它只有一个参数PorterDuff.Mode,表示混合模式,枚举值有18个那么多:
上图中的Sa表示Source Alpha,表示资源的Alpha通道,Sc的全称为Source color,表示源图像的颜色,Da表示Destination alpha,表示目标图像的alpha通道,Destination color表示目标图像的颜色。
在每个公式中,都会分成两部分[…,…],前半部分计算Alpha通道,后半部分计算处理后的颜色值。
其中上面的算法涉及到两个概念:目标图像(DST)和原图像(SRC)
一般我们都会让他们相交,产生两个区域,区域一为原图像和目标图像的相交区域,区域二为原图像与空白像素的相交区域。
这两个区域很重要,之后会经常用到。
这里举一个例子,我们画一个圆形和一个矩形,然后相交,其中,圆形为目标图像,矩形为原图像。
首先画一个圆形和矩形:
画出一个颜色为黄色的圆形和一个蓝色的矩形,接下来让他们混合,混合Mode为SRC_IN:
将圆形在控件中心位置画出,而矩形则以圆心为左上角画出。
效果如下:
我们知道SRC_IN的计算公式为[Sa * Da, Sc * Da] ,该公式中结果的透明值和颜色值都是由 Sa和Sc 来乘以 Da计算的。
当目标图像为空白像素时,计算结果也将是空白像素。 所以区域一的相交部分显示的是源图像,而对于区域二的不相交部分,此时目标图像的透明度是0,源图像不显示。
颜色叠加相关模式
接下来我们将逐个看一下各种模式 的含义及用法。
这一部分所涉及的模式都是针对色彩变换的几种模式,有Mode.ADD(饱和度相加)、Mode.LIGHTEN(变亮)、Mode.DARKEN(变暗)、Mode.MUTIPLY(正片叠底)、Mode.OVERLAY(叠加)、Mode.SCREEN(滤色)。
1、Mode.ADD(饱和度相加)
对应的算法:Saturate(S + D)
简单来说就是就是对SRC与DST两张图片相交区域的饱和度进行相加。
将上例中的SRC_IN改成ADD,效果如下:
可以看到当两个饱和度为100的相交后会变成白色(这里不明显还以为是透明,下面我把背景颜色改一下),不相交的地方等就是饱和度100+0,所以还是自己原来的颜色。2、Mode.LIGHTEN(变亮)
对应的算法:[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]
效果如下(这里把背景颜色改成了绿色):
当只有两个颜色相交时,才会有颜色的变化。
这就和我们在PS中,有两个图片,一个是没什么光影的物品图片,一个是有一个只有亮光的图片,P在一起后,这个物品就好像被灯照亮了一般。3、Mode.DARKEN(变暗)
对应算法:[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
效果如下:
没什么好说的。4、Mode.MULTIPLY(正片叠底)
对应的算法:[Sa * Da, Sc * Dc]
这个算法简单一点,可以研究一下, 其中最终的Alpha值时 Sa*Da,就是源图像的Alpha值乘以目标图像的Alpha值,那么区域二的alpha就是0,效果如下:
5、Mode.OVERLAY(叠加)
官方没有给出算法,效果如下:
应该是相交的部分进行了叠加。非相交区域都还在。6、Mode.SCREEN(滤色)
对应的算法:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]
效果如下:
就是对颜色进行了过滤吧。
到这里,6中混合模式就讲完了,下面说下总结:
- 这几种模式都是PhotoShop中存在的模式,是通过计算改变相交区域的颜色值的
- 除了Mode.MULTIPLY会在目标图像透明时,将结果对应区域设置为透明,其他图像都不受目标图像的影响,即区域二保持原样。
- 在考虑混合模式时,一般只考虑两种:
1、像 区域一 一样的两个不透明区域的混合
2、像 区域二 一样的完全透明区域的混合。
对于与半透明区域的混合,实战中一般是用不到的。
PorterDuffXfermode之源图像模式
下面介绍混合模式中以源图像显示为主的模式,如果在实战中遇到相交,并且想显示源图像,则需要考虑下面的几个Mode:
Mode.SRC, Mode.SRC_IN, Mode.OUT, Mode.SRC_OVER, Mode.SRC_ATOP
7、Mode.SRC
对应的算法:[Sa, Sc]
简单粗暴的显示SRC图像:
8、Mode.SRC_IN
对应的算法:[Sa * Da, Sc * Da]
该公式中,结果只的透明度和颜色有Sa、Sc分别乘Da,就是如果有相交的地方,则显示源文件的颜色。而如果目标像素为空白像素时,结果也为空白。
可以看到SRC和SRC_IN的区别是:
(1)SRC下源图像会在覆盖目标图像的同时,自己没相交的地方也会出现
(2)SRC_IN在相交区域内(区域一)显示源图像,而在未相交的区域显示目标图像或者透明
根据SRC_IN,我们可以实现很多效果,比如目标图像是个圆形,源图像是个矩形头像,这样一混合,就能成为圆形的头像!
因为SRC_IN是根据目标图像的透明值来计算结果的透明和颜色的,所以当目标的透明值在0-255间时,结果值时会变小的,我们可以根据SRC_IN的特性来实现倒影效果。(因为没有找到透明的图片,这里就用文字阐述了)
- 首先拿到源图像、目标图像(这是一个遮罩图),用Matrix矩阵将源图像进行翻转(matrix.setScale(1f,-1f))得到翻转后的源图像
- 先绘制源图像,然后画布向下移,以翻转后的源图像做为源图像,遮罩图作为目标图像,进行SRC_IN混合,就可以得到倒影图。
9、Mode.SRC_OUT
对应的算法为:[Sa * (1 - Da), Sc * (1 - Da)]
从公式Sa*(1-Da)看出,当目标图像完全不透明时,结果算出来会使透明的。
所以SRC_OUT的特性可以理解为:以目标图像的透明度的补值来调节源图像的透明度和饱和度。
示例一:橡皮檫效果:
我们可以根据手势轨迹来对图片擦除,很明显,要擦除的图片是源图像,而手势轨迹所在的图像是目标图像。当手指在滑动时,应该产生不透明的目标图像,和源图像SRC_OUT结合,源图像就会产生擦除效果。
首先,先初始化控件:
然后监听手势滑动,下面对滑动轨迹也是用了贝济埃曲线去计算:
然后就是绘制了,我们先把目标图像(手势滑动)给画在画布上,然后当进行滑动的时候,用drawPath去绘制目标图像的具体形状,再用源图像和滑动的图像做SRC_OUT的混合。
效果如下:
示例二 刮刮乐效果
因为刮刮乐其实就是橡皮檫效果演变而来的,所以这里再做一个刮刮乐的效果。
其实很简单,就是在onDraw最开始的时候,在最底层画一个bitmap
效果如下:
10、Mode.SRC_OVER
对应的算法: [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
公式可以看出,如果源图像的alpha为100时,就完全显示源图像
效果如下:
11、Mode.SRC_ATOP
对应的算法为:[Da, Sc * Da + (1 - Sa) * Dc]
可以看到结果Alpha值为Da,也就是目标图像,颜色是当如果源图像alpha为100%时的Sc*Da
大概就是显示目标图像中的源图像。这又和SRC_IN的效果相同了,其实对比公式,可以知道,在透明度100%和0%时,SRC_IN和SRC_ATOP的效果的确是一样的。
目标图像模式与其他模式
上面说的是源图像模式,就是在混合时,以源图像的显示为主,那么这一节就是将以目标图像的显示为主。
涉及的mode为 Mode.DST、Mode.DST_IN、Mode.DST_OUT、Mode.DST_OVER、Mode.DST_ATOP
12、Mode.DST
对应的算法:[Da, Dc]
完全显示目标图像:
13、Mode.DST_IN
对应的算法:[Sa * Da, Sa * Dc]
当源图像的alpha为0时,完全不显示,显示的颜色为源图像的alpha乘以目标图像的颜色。
效果如下:
14、Mode.DST_OUT
计算公式如下:[Da * (1 - Sa), Dc * (1 - Sa)]
可以看出,如果当源图像的透明度为100%,则区域一完全不显示,效果如下:
15、Mode.DST_OVER
对应的算法:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]
这里顾名思义(OVER就是覆盖的意思咯),效果如下:
16、Mode.DST_ATOP
对应的算法为:[Da, Sc * Da + (1 - Sa) * Dc]
在SRC中 一般而言SRC_IN可以和SRC_ATOP通用的,DST_IN和DST_ATOP也是这样。
一般而言,DST_ATOP所产生的的效果图在源图像的透明度不是0或者100%的时候,会比DST_IN模式更亮。
其他模式
17、Mode.CLEAR
对应的算法为: [0,0]
也就是说 直接将源图像的像素给清空。
18、Mode.XOR
这个不用看公式都知道是异或啦
到这里Xfermode的大致的运用就讲到这里啦,通过混合图像能实现一些遮罩等类似于PS的效果。
DST和SRC模式其实都是互通的,你把DST和SRC的模式调一下,再把源图像和目标图像换一下也是一样的效果。