Paint---PorterDuffXfermode

API—PorterDuffXfermode | Android 开发者
文档内容很少,我们可以看到他的构造方法:

/** * Create an xfermode that uses the specified porter-duff mode. * * @param mode The porter-duff mode that is applied */  
public PorterDuffXfermode(PorterDuff.Mode mode) {  
    this.mode = mode;  
    native_instance = nativeCreateXfermode(mode.nativeInt);  
}

参数只有一个:PorterDuff.Mode mode

PS:PorterDuff 实则是两个人名的的组合Thomas Porter 和 Tom Duff
,PorterDuff则是用于描述数字图像合成的基本手法,通过组合使用Porter-Duff 操作,可完成任意 2D图像的合成;

Android系统一共提供了18种混排模式,在模拟器的ApiDemos/Graphics/XferModes,有张效果图:
Paint---PorterDuffXfermode_第1张图片

这张图可以很形象的说明图片各种混排模式下的效果。这里两个图层:先绘制的图是目标图(DST),后绘制的图是源图(SRC)!其中Src代表原图,Dst代表目标图,两张图片使用不同的混排方式后,得到的图像是如上图所示的。PorterDuff.Mode也提供了18种混排模式已经算法,其中比上图新增了ADD和OVERLAY两种模式 :


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

其中
Sa全称为Source alpha表示源图的Alpha通道,alpha 值;
Sc全称为Source color表示源图的颜色,源色值;
Da全称为Destination alpha表示目标图的Alpha通道,目标alpha值;
Dc全称为Destination color表示目标图的颜色,目标色值。
并且这所有的计算都以像素为单位,在某一种混合模式下,对每一个像素的alpha 和 color 通过对应算法进行运算,得出新的像素值,进行展示。
[…,..]前半部分计算的是结果图像的Alpha通道值,“,”后半部分计算的是结果图像的颜色值。图像混排后是依靠这两个值来重新计算ARGB值的,具体计算算法,抱歉,我也不知道,不过不要紧,不了解计算算法也不影响我们程序员写程序的。

代码如下:

public class PorterDuffXfermodeView extends View {  

    private Paint mPaint;  
    private Bitmap mBottomBitmap, mTopBitmap;  
    private Rect mBottomSrcRect, mBottomDestRect;  
    private Rect mTopSrcRect, mTopDestRect;  

    private Xfermode mPorterDuffXfermode;  
    // 图层混合模式 
    private PorterDuff.Mode mPorterDuffMode;  
    // 总宽高 
    private int mTotalWidth, mTotalHeight;  
    private Resources mResources;  

    public PorterDuffXfermodeView(Context context) {  
        super(context);  
        mResources = getResources();  
        initBitmap();  
        initPaint();  
        initXfermode();  
    }  

    public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        mResources = getResources();  
        initBitmap();  
        initPaint();  
        initXfermode();
    }

    // 初始化bitmap 
    private void initBitmap() {  
        mBottomBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.blue)).getBitmap();  
        mTopBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.red)).getBitmap();  
    }  

    // 初始化混合模式 
    private void initXfermode() {  
        mPorterDuffMode = PorterDuff.Mode.XOR;  
        mPorterDuffXfermode = new PorterDuffXfermode(mPorterDuffMode);  
    }  

    private void initPaint() {  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  

    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        super.onDraw(canvas);  

        // 背景铺白 
        canvas.drawColor(Color.WHITE);  
        // 保存为单独的层 
        int saveCount = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, mPaint,  
                Canvas.ALL_SAVE_FLAG);  
        // 绘制目标图 
        canvas.drawBitmap(mBottomBitmap, mBottomSrcRect, mBottomDestRect, mPaint);  
        // 设置混合模式 
        mPaint.setXfermode(mPorterDuffXfermode);  
        // 绘制源图 
        canvas.drawBitmap(mTopBitmap, mTopSrcRect, mTopDestRect, 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);  
        mTotalWidth = w;  
        mTotalHeight = h;  
        int halfHeight = h / 2;  

        mBottomSrcRect = new Rect(0, 0, mBottomBitmap.getWidth(), mBottomBitmap.getHeight());  
        // 矩形只画屏幕一半 
        mBottomDestRect = new Rect(0, 0, mTotalWidth, halfHeight);  

        mTopSrcRect = new Rect(0, 0, mTopBitmap.getWidth(), mTopBitmap.getHeight());  
        mTopDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);  
    }  

    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
    }  
    public void setMode(PorterDuff.Mode mode){
        mPorterDuffMode =mode;  
        mPorterDuffXfermode = new PorterDuffXfermode(mPorterDuffMode); 
        postInvalidate();
    }

}  

原图如下:

Paint---PorterDuffXfermode_第2张图片Paint---PorterDuffXfermode_第3张图片

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

(1). CLEAR

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

(2). SRC -[Sa , Sc]

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图,不管任何时候,一定要做一个有目标的人,目标在前!
这个时候绘制的情形是只有屏幕上的红色圆;
Paint---PorterDuffXfermode_第5张图片

(3). DST -[ Da, Dc ]

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

(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 ]

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

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

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

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

从字面上可以理解为 在不相交的地方绘制 源图像,那么我们来看看效果是不是这样;
Paint---PorterDuffXfermode_第9张图片
这个效果似乎和上面说的不大一样,这个时候我们回归本源[ 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进行过滤,完全不透明处则完全过滤,完全透明则不过滤;
Paint---PorterDuffXfermode_第10张图片

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

源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响;
Paint---PorterDuffXfermode_第11张图片

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

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

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

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

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

该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;
从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响;
Paint---PorterDuffXfermode_第13张图片

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

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

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

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

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

滤色,滤色模式与我们所用的显示屏原理相同,所以也有版本把它翻译成“屏幕”;
简单的说就是保留两个图层中较白的部分,较暗的部分被遮盖;
当一层使用了滤色(屏幕)模式时,图层中纯黑的部分变成完全透明,纯白部分完全不透明,其他的颜色根据颜色级别产生半透明的效果;

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

饱和度叠加

(18). OVERLAY - 叠加

像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留;
源码下载

引用:
Android Paint之 setXfermode PorterDuffXfermode 讲解 - Ajian_studio - 博客频道 - CSDN.NET
Android图像处理——Paint之Xfermode,androidxfermode_Android教程 | 帮客之家
8.3.4 Paint API之—— Xfermode与PorterDuff详解(一) | 菜鸟教程

你可能感兴趣的:(android,绘图,paint)