Android Paint之 setXfermode PorterDuffXfermode 讲解

来源:互联网 发布:世界地图销售网络下载 编辑:程序博客网 时间:2024/06/05 23:50

原文地址 点击打开链接

首先我们还是来看看关于这个方法的说明:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * Set or clear the xfermode object. - 设置或清除xfermode对象;  
  3.  * Pass null to clear any previous xfermode. - 传递null以清除任何以前的xfermode。  
  4.  * As a convenience, the parameter passed is also returned. - 为方便起见,也返回传递的参数。  
  5.  *  
  6.  * @return         xfermode  
  7.  */  
  8. public Xfermode setXfermode(Xfermode xfermode) {  
  9.     int xfermodeNative = 0;  
  10.     if (xfermode != null)  
  11.         xfermodeNative = xfermode.native_instance;  
  12.     native_setXfermode(mNativePaint, xfermodeNative);  
  13.     mXfermode = xfermode;  
  14.     return xfermode;  
  15. }  

这个方法传进一个 Xfermode 对象,而打开 Xfermode 发现里面没有提供任何可用的构造函数或方法,ctrl +T 看到它有三个子类:




前两个子类 AvoidXfermode 和 PixelXorXfermode 大家可以看到都已经被划上了斜线,下面就简单提及一下,咱们的重点在 PorterDuffXfermode :


PorterDuffXfermode

这个类是setXfermode 方法的核心,也是图层混合模式里的核武器,通过它再加上我们的想象力,就能解决图形图像绘制里的很多问题,首先,还是看下方法说明:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * Create an xfermode that uses the specified porter-duff mode.  
  3.  *  
  4.  * @param mode           The porter-duff mode that is applied  
  5.  */  
  6. public PorterDuffXfermode(PorterDuff.Mode mode) {  
  7.     this.mode = mode;  
  8.     native_instance = nativeCreateXfermode(mode.nativeInt);  
  9. }  

使用 PorterDuff 模式创建一个图层混合模式,如此说来,我们的重心就转移到了 PorterDuff 身上;

PorterDuff 是啥,居然没法翻译,我靠,百思不得其姐,如果对图形图像学有所了解,肯定会知道,PorterDuff 实则是两个人名的的组合Thomas Porter 和 Tom Duff ,PorterDuff则是用于描述数字图像合成的基本手法,通过组合使用 Porter-Duff 操作,可完成任意 2D图像的合成;

听起来好像很屌炸天的样子,接下来我们一起来逐个看下这些模式:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class PorterDuff {  
  2.   
  3.   // these value must match their native equivalents. See SkPorterDuff.h  
  4.   public enum Mode {  
  5.       /** [0, 0] */  
  6.       CLEAR       (0),  
  7.       /** [Sa, Sc] */  
  8.       SRC         (1),  
  9.       /** [Da, Dc] */  
  10.       DST         (2),  
  11.       /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */  
  12.       SRC_OVER    (3),  
  13.       /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */  
  14.       DST_OVER    (4),  
  15.       /** [Sa * Da, Sc * Da] */  
  16.       SRC_IN      (5),  
  17.       /** [Sa * Da, Sa * Dc] */  
  18.       DST_IN      (6),  
  19.       /** [Sa * (1 - Da), Sc * (1 - Da)] */  
  20.       SRC_OUT     (7),  
  21.       /** [Da * (1 - Sa), Dc * (1 - Sa)] */  
  22.       DST_OUT     (8),  
  23.       /** [Da, Sc * Da + (1 - Sa) * Dc] */  
  24.       SRC_ATOP    (9),  
  25.       /** [Sa, Sa * Dc + Sc * (1 - Da)] */  
  26.       DST_ATOP    (10),  
  27.       /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */  
  28.       XOR         (11),  
  29.       /** [Sa + Da - Sa*Da,  
  30.            Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */  
  31.       DARKEN      (12),  
  32.       /** [Sa + Da - Sa*Da,  
  33.            Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */  
  34.       LIGHTEN     (13),  
  35.       /** [Sa * Da, Sc * Dc] */  
  36.       MULTIPLY    (14),  
  37.       /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */  
  38.       SCREEN      (15),  
  39.       /** Saturate(S + D) */  
  40.       ADD         (16),  
  41.       OVERLAY     (17);  
  42.   
  43.       Mode(int nativeInt) {  
  44.           this.nativeInt = nativeInt;  
  45.       }  
  46.   
  47.       /**  
  48.        * @hide  
  49.        */  
  50.       public final int nativeInt;  
  51.   }  


可以看到里面通过枚举enum定义了18种混合模式,并且每种模式都写出了对应的计算方法:

其中 Sa 代表source alpha ,即源 alpha 值 ,Da 代表 Destination alpha ,即 目标alpha值 ,Sc 代表 source color ,即源色值 ,Dc 代表 Destination color ,即目标色值,并且这所有的计算都以像素为单位,在某一种混合模式下,对每一个像素的alpha 和 color 通过对应算法进行运算,得出新的像素值,进行展示;

接下来写个view 逐个对以上模式进行测试:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class PorterDuffXfermodeView extends View {  
  2.   
  3.     private Paint mPaint;  
  4.     private Bitmap mBottomBitmap, mTopBitmap;  
  5.     private Rect mBottomSrcRect, mBottomDestRect;  
  6.     private Rect mTopSrcRect, mTopDestRect;  
  7.   
  8.     private Xfermode mPorterDuffXfermode;  
  9.     // 图层混合模式  
  10.     private PorterDuff.Mode mPorterDuffMode;  
  11.     // 总宽高  
  12.     private int mTotalWidth, mTotalHeight;  
  13.     private Resources mResources;  
  14.   
  15.     public PorterDuffXfermodeView(Context context) {  
  16.         super(context);  
  17.         mResources = getResources();  
  18.         initBitmap();  
  19.         initPaint();  
  20.         initXfermode();  
  21.     }  
  22.   
  23.     // 初始化bitmap  
  24.     private void initBitmap() {  
  25.         mBottomBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.blue)).getBitmap();  
  26.         mTopBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.red)).getBitmap();  
  27.     }  
  28.   
  29.     // 初始化混合模式  
  30.     private void initXfermode() {  
  31.         mPorterDuffMode = PorterDuff.Mode.DST;  
  32.         mPorterDuffXfermode = new PorterDuffXfermode(mPorterDuffMode);  
  33.     }  
  34.   
  35.     private void initPaint() {  
  36.         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  37.   
  38.     }  
  39.   
  40.     @Override  
  41.     protected void onDraw(Canvas canvas) {  
  42.         super.onDraw(canvas);  
  43.   
  44.         // 背景铺白  
  45.         canvas.drawColor(Color.WHITE);  
  46.         // 保存为单独的层  
  47.         int saveCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mPaint,  
  48.                 Canvas.ALL_SAVE_FLAG);  
  49.         // 绘制目标图  
  50.         canvas.drawBitmap(mBottomBitmap, mBottomSrcRect, mBottomDestRect, mPaint);  
  51.         // 设置混合模式  
  52.         mPaint.setXfermode(mPorterDuffXfermode);  
  53.         // 绘制源图  
  54.         canvas.drawBitmap(mTopBitmap, mTopSrcRect, mTopDestRect, mPaint);  
  55.         mPaint.setXfermode(null);  
  56.         canvas.restoreToCount(saveCount);  
  57.     }  
  58.   
  59.     @Override  
  60.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  61.         super.onSizeChanged(w, h, oldw, oldh);  
  62.         mTotalWidth = w;  
  63.         mTotalHeight = h;  
  64.         int halfHeight = h / 2;  
  65.   
  66.         mBottomSrcRect = new Rect(0, 0, mBottomBitmap.getWidth(), mBottomBitmap.getHeight());  
  67.         // 矩形只画屏幕一半  
  68.         mBottomDestRect = new Rect(0, 0, mTotalWidth, halfHeight);  
  69.   
  70.         mTopSrcRect = new Rect(0, 0, mTopBitmap.getWidth(), mTopBitmap.getHeight());  
  71.         mTopDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  76.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  77.     }  
  78.   
  79. }  

原图如下:



蓝色上下分别为完全不透明和半透区,红色左右为半透和完全不透明区,这样绘制时可以让所有的情况进行交叉,便于分析,先看下默认绘制效果:



(1). CLEAR 

清除模式[0,0],即最终所有点的像素的alpha 和color 都为 0,所以画出来的效果只有白色背景:



(2). SRC -[Sa , Sc]

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图,不管任何时候,一定要做一个有目标的人,目标在前!

这个时候绘制的情形是只有屏幕上的红色圆;


(3). DST -[ Da, Dc ]

同上类比,只保留目标图像的 alpha 和 color,所以绘制出来的只有目标图,也就是屏幕上的的蓝色图;



(4).SRC_OVER - [ Sa +(1-Sa) * Da , Rc = Sc +( 1- Sa ) * Dc]

在目标图片顶部绘制源图像,从命名上也可以看出来就是把源图像绘制在上方;



(5). DST_OVER - [Sa + ( 1 - Sa ) * Da ,Rc = Dc + ( 1 - Da ) * Sc ]

可以和 SRC_OVER 进行类比,将目标图像绘制在上方,这时候就会看到先绘制的蓝色盖在了红色圆的上面;


(6). SRC_IN - [ Sa * Da , Sc * Da ]

在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响;


(7). DST_IN - [ Sa * Da , Sa * Dc ]

可以和SRC_IN 进行类比,在两者相交的地方绘制目标图像,并且绘制的效果会受到源图像对应地方透明度的影响;


(8). SRC_OUT - [ Sa * ( 1 - Da ) , Sc * ( 1 - Da ) ]

从字面上可以理解为 在不相交的地方绘制 源图像,那么我们来看看效果是不是这样;


这个效果似乎和上面说的不大一样,这个时候我们回归本源[ Sa * ( 1 - Da ) , Sc * ( 1 - Da ) ] ,从公式里可以看到 对应处的 color 是Sc * ( 1 - Da ) ,如果相交处的目标色的alpha是完全不透明的,这时候源图像会完全被过滤掉,否则会受到相交处目标色 alpha 影响,呈现出对应色值,如果还有问题,大家可以对比上图和普通叠加图,再参考下上面公式理解;

所以该模式总结一下应该是:在不相交的地方绘制源图像,相交处根据目标alpha进行过滤,目标色完全不透明时则完全过滤,完全透明则不过滤;

(9). DST_OUT - [ Da * ( 1 - Sa ) , Dc * ( 1 - Sa ) ]

同样,可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤;


(10). SRC_ATOP - [ Da , Sc * Da + ( 1 - Sa ) * Dc ]

源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响;



(11). DST_ATOP  - [ Sa , Sa * Dc + Sc * ( 1 - Da ) ]

源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像,并且相交处的效果会受到源图像和目标图像alpha的影响;




(12). XOR - [ Sa + Da - 2 * Sa * Da, Sc * ( 1 - Da ) + ( 1 - Sa ) * Dc ]

在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和色值影响,按上面公式进行计算,如果都完全不透明则相交处完全不绘制;




(13). DARKEN - [ Sa + Da - Sa * Da , Sc * ( 1 - Da ) + Dc * ( 1 - Sa ) + min(Sc , Dc) ]

该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;

从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响;




同样的,这样的混合效果可以直接在PS里进行简单模拟,创建三个一样的图层,选择对应的混合模式,对于效果表示是一致的:




(14). LIGHTEN - [ Sa + Da - Sa * Da , Sc * ( 1 -Da ) + Dc * ( 1 - Sa ) + max ( Sc , Dc ) ]

可以和 DARKEN 对比起来看,DARKEN 的目的是变暗,LIGHTEN 的目的则是变亮,如果在均完全不透明的情况下 ,色值取源色值和目标色值中的较大值,否则按上面算法进行计算;



(15). MULTIPLY - [ Sa * Da , Sc * Dc ]

正片叠底,即查看每个通道中的颜色信息,并将基色与混合色复合。结果色总是较暗的颜色。任何颜色与黑色复合产生黑色。任何颜色与白色复合保持不变。当用黑色或白色以外的颜色绘画时,绘画工具绘制的连续描边产生逐渐变暗的颜色。




(16). SCREEN - [ Sa + Da - Sa * Da , Sc + Dc - Sc * Dc ]

滤色,滤色模式与我们所用的显示屏原理相同,所以也有版本把它翻译成“屏幕”;

简单的说就是保留两个图层中较白的部分,较暗的部分被遮盖;

当一层使用了滤色(屏幕)模式时,图层中纯黑的部分变成完全透明,纯白部分完全不透明,其他的颜色根据颜色级别产生半透明的效果;


(17). ADD - [ Saturate( S+ D ) ]

 饱和度叠加
(18). OVERLAY - 叠加
像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留;
以上就是android 提供的所有的混合模式,有了这,只要是图像与图像任何形式混合能创造的效果,我们就都能够进行实现,这时候我们再看看混合模式的示意图


前面我们用AvoidXfermode实现的图标变色小栗子也同样可以使用PorterDuffXfermode进行实现,大家可以自己尝试;


最后,我们利用混合模式完成两个小栗子:

一、还算比较流行的一种 loading 形式:

先看下效果:


这个效果如果不知道图像的混合模式,做的话可能会采用2张图,对上面的图进行裁切,但自从有了混合模式,这一切都将变的简单;

我们只需要如下几步:

1.绘制目标图;

2.设置混合模式;

3.绘制带颜色的Rect;

4.不断改变Rect的区域;

代码如下:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class PoterDuffLoadingView extends View {  
  2.   
  3.     private Resources mResources;  
  4.     private Paint mBitPaint;  
  5.     private Bitmap mBitmap;  
  6.   
  7.     private int mTotalWidth, mTotalHeight;  
  8.     private int mBitWidth, mBitHeight;  
  9.     private Rect mSrcRect, mDestRect;  
  10.     private PorterDuffXfermode mXfermode;  
  11.   
  12.     private Rect mDynamicRect;  
  13.     private int mCurrentTop;  
  14.     private int mStart, mEnd;  
  15.   
  16.     public PoterDuffLoadingView(Context context) {  
  17.         super(context);  
  18.         mResources = getResources();  
  19.         initBitmap();  
  20.         initPaint();  
  21.         initXfermode();  
  22.     }  
  23.   
  24.     private void initXfermode() {  
  25.         // 叠加处绘制源图  
  26.         mXfermode = new PorterDuffXfermode(Mode.SRC_IN);  
  27.     }  
  28.   
  29.     private void initPaint() {  
  30.         // 初始化paint  
  31.         mBitPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  32.         mBitPaint.setFilterBitmap(true);  
  33.         mBitPaint.setDither(true);  
  34.         mBitPaint.setColor(Color.RED);  
  35.     }  
  36.   
  37.     private void initBitmap() {  
  38.         // 初始化bitmap  
  39.         mBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.ga_studio))  
  40.                 .getBitmap();  
  41.         mBitWidth = mBitmap.getWidth();  
  42.         mBitHeight = mBitmap.getHeight();  
  43.     }  
  44.   
  45.     @Override  
  46.     protected void onDraw(Canvas canvas) {  
  47.         super.onDraw(canvas);  
  48.         // 存为新图层  
  49.         int saveLayerCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mBitPaint,  
  50.                 Canvas.ALL_SAVE_FLAG);  
  51.         // 绘制目标图  
  52.         canvas.drawBitmap(mBitmap, mSrcRect, mDestRect, mBitPaint);  
  53.         // 设置混合模式  
  54.         mBitPaint.setXfermode(mXfermode);  
  55.         // 绘制源图形  
  56.         canvas.drawRect(mDynamicRect, mBitPaint);  
  57.         // 清除混合模式  
  58.         mBitPaint.setXfermode(null);  
  59.         // 恢复保存的图层;  
  60.         canvas.restoreToCount(saveLayerCount);  
  61.   
  62.         // 改变Rect区域,真实情况下时提供接口传入进度,计算高度  
  63.         mCurrentTop -8;  
  64.         if (mCurrentTop <= mEnd) {  
  65.             mCurrentTop = mStart;  
  66.         }  
  67.         mDynamicRect.top = mCurrentTop;  
  68.         postInvalidate();  
  69.     }  
  70.   
  71.     @Override  
  72.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  73.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  74.     }  
  75.   
  76.     @Override  
  77.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  78.         super.onSizeChanged(w, h, oldw, oldh);  
  79.         mTotalWidth = w;  
  80.         mTotalHeight = h;  
  81.         mSrcRect = new Rect(0, 0, mBitWidth, mBitHeight);  
  82.         // 让左边和上边有些距离  
  83.         int left = (int) TypedValue.complexToDimension(20, mResources.getDisplayMetrics());  
  84.         mDestRect = new Rect(left, left, left + mBitWidth, left + mBitHeight);  
  85.         mStart = left + mBitHeight;  
  86.         mCurrentTop = mStart;  
  87.         mEnd = left;  
  88.         mDynamicRect = new Rect(left, mStart, left + mBitWidth, left + mBitHeight);  
  89.     }  
  90. }  
0 0