在android SDK Paint类中有一个很重要的方法setXfermode(源码如上),这个方法用于设置图像的过渡模式,所谓过渡是指图像的饱和度、颜色值等参数的计算结果的图像表现。在SDK中Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,前两个类在API 16被遗弃了,而且不是本文的主题内容,所以这里不作介绍。PorterDuffXfermode类主要用于图形合成时的图像过渡模式计算,其概念来自于1984年在ACM SIGGRAPH计算机图形学出版物上发表了“Compositing digital images(合成数字图像)”的Tomas Porter和Tom Duff,合成图像的概念极大地推动了图形图像学的发展,PorterDuffXfermode类名就来源于这俩人的名字组合PorterDuff。
利用ProterBuff.Mode我们可以完成任意2D图像测操作, 比如涂鸦画板应用中的橡皮擦效果,绘制各种自定义的进度,等等很强大的效果,下面请看具体的介绍:
1. Xfermode:
Xfermode有三个子类 :
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素异或操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
要应用转换模式,可以使用setXferMode方法,如下所示:
AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, 10, AvoidXfermode.Mode. AVOID);
borderPen.setXfermode(avoid);
2.PorterDuff:
首先看一下效果图(来自ApiDemos/Graphics/XferModes)
从上面我们可以看到PorterDuff.Mode为枚举类,一共有16个枚举值:
public enum Mode {
/** [0, 0] */
CLEAR (0),
/** [Sa, Sc] */
SRC (1),
/** [Da, Dc] */
DST (2),
/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
SRC_OVER (3),
/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sc * Da] */
SRC_IN (5),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT (7),
/** [Da * (1 - Sa), Dc * (1 - Sa)] */
DST_OUT (8),
/** [Da, Sc * Da + (1 - Sa) * Dc] */
SRC_ATOP (9),
/** [Sa, Sa * Dc + Sc * (1 - Da)] */
DST_ATOP (10),
/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
XOR (11),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
DARKEN (12),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
LIGHTEN (13),
/** [Sa * Da, Sc * Dc] */
MULTIPLY (14),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
SCREEN (15),
/** Saturate(S + D) */
ADD (16),
OVERLAY (17);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
上面代码中每种模式的注释都说明了该模式的alpha通道和颜色值的计算方式,要理解各个模式的计算方式需要先弄明白公式中各个元素的具体含义:
Sa:全称为Source alpha,表示源图的Alpha通道;
Sc:全称为Source color,表示源图的颜色;
Da:全称为Destination alpha,表示目标图的Alpha通道;
Dc:全称为Destination color,表示目标图的颜色.
当Alpha通道的值为1时,图像完全可见;当Alpha通道值为0时,图像完全不可见;当Alpha通道的值介于0和1之间时,图像只有一部分可见。Alpha通道描述的是图像的形状,而不是透明度。
以SCREEN的计算方式为例:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],“[……]”里分为两部分,其中“,”前的部分“Sa + Da - Sa * Da”计算的值代表SCREEN模式的Alpha通道,而“,”后的部分“Sc + Dc - Sc * Dc”计算SCREEN模式的颜色值,图形混合后的图片依靠这个矢量来计算ARGB的值。关于图像Alpha合成的知识详见维基百科Alpha compositing。
1.PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC
显示上层绘制图片
3.PorterDuff.Mode.DST
显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR
异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色
/**
* Created by Alex Pang on 2016/8/20.
* 自定义View,使用PorterDuff.Mode验证图像合成效果
*/
public class PorterDuffXfermodeView extends View {
private Paint mPaint;
private Bitmap dstBmp, srcBmp;
private RectF dstRect, srcRect;
private Xfermode mXfermode;
private PorterDuff.Mode mPorterDuffMode = PorterDuff.Mode.MULTIPLY;
public PorterDuffXfermodeView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);
dstBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.destination);
srcBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.source);
mXfermode = new PorterDuffXfermode(mPorterDuffMode);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//背景色设为白色,方便比较效果
canvas.drawColor(Color.WHITE);
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
int saveCount = canvas.saveLayer(srcRect, mPaint, Canvas.ALL_SAVE_FLAG);
//绘制目标图
canvas.drawBitmap(dstBmp, null, dstRect, mPaint);
//设置混合模式
mPaint.setXfermode(mXfermode);
//绘制源图
canvas.drawBitmap(srcBmp, null, srcRect, mPaint);
//清除混合模式
mPaint.setXfermode(null);
//还原画布
canvas.restoreToCount(saveCount);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int width = w <= h ? w : h;
int centerX = w/2;
int centerY = h/2;
int quarterWidth = width /4;
srcRect = new RectF(centerX-quarterWidth, centerY-quarterWidth, centerX+quarterWidth, centerY+quarterWidth);
dstRect = new RectF(centerX-quarterWidth, centerY-quarterWidth, centerX+quarterWidth, centerY+quarterWidth);
}
}
其中用到的图片资源分别如下,destination沿图片左上到右下的对角线将图片分成两部分,左下部分是完全不透明的,右上部分是半透明的;source沿图片左下到右上的对角线将图片分成两部分,左上部分是半透明的,右下部分是完全不透明的。图中有颜色的区域外都是完全透明的,这样当两张图片合成时可以让各种情况交叉呈现,便于分析理解。
清除模式,[0, 0],即图像中所有像素点的alpha和颜色值均为0,Demo中的实际效果就是白色背景,图略。
[Sa, Sc],只保留源图像的 alpha 和 color ,所以绘制出来只有源图,如source。有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图。
[Da, Dc],只保留了目标图像的alpha和color值,所以绘制出来的只有目标图,如destination。
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc],在目标图像上层绘制源图像
[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],与SRC_OVER相反,此模式是目标图像被绘制在源图像的上方
[Sa * Da, Sc * Da],在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响
[Sa * Da, Sa * Dc],可以和SRC_IN 进行类比,在两者相交的地方绘制目标图像,并且绘制的效果会受到源图像对应地方透明度的影响
[Sa * (1 - Da), Sc * (1 - Da)],从字面上可以理解为在不相交的地方绘制源图像,那么我们来看看效果是不是这样,如下图。实际上color 是 Sc * ( 1 - Da ) ,表示如果相交处的目标色的alpha是完全不透明的,这时候源图像会完全被过滤掉,否则会受到相交处目标色 alpha 影响,呈现出对应色值。
[Da * (1 - Sa), Dc * (1 - Sa)],可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤
[Da, Sc * Da + (1 - Sa) * Dc],源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响
[Sa, Sa * Dc + Sc * (1 - Da)],源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像,并且相交处的效果会受到源图像和目标图像alpha的影响
[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc],在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和颜色值影响,按公式进行计算,如果都完全不透明则相交处完全不绘制
[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)],该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;
从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响。
[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],可以和 DARKEN 对比起来看,DARKEN 的目的是变暗,LIGHTEN 的目的则是变亮,如果在均完全不透明的情况下,色值取源色值和目标色值中的较大值,否则按上面算法进行计算。
[Sa * Da, Sc * Dc],正片叠底,即查看每个通道中的颜色信息,并将基色与混合色复合。结果色总是较暗的颜色,任何颜色与黑色复合产生黑色,任何颜色与白色复合保持不变,当用黑色或白色以外的颜色绘画时,绘画工具绘制的连续描边产生逐渐变暗的颜色。
[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],滤色,滤色模式与我们所用的显示屏原理相同,所以也有版本把它翻译成屏幕;简单的说就是保留两个图层中较白的部分,较暗的部分被遮盖;当一层使用了滤色(屏幕)模式时,图层中纯黑的部分变成完全透明,纯白部分完全不透明,其他的颜色根据颜色级别产生半透明的效果。
Saturate(S + D),饱和度叠加
像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留
相关的文章:
各个击破搞明白PorterDuff.Mode