Android PorterDuffXfermode使用及工作原理详解

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/127270584
本文出自【赵彦军的博客】

概述

android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode。在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果。当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null)

PorterDuffXfermode 这个类中的 PorterDuff 是两个人名,这两个人在1984年一起写了一篇名为《Compositing Digital Images》的论文,点击可查看该论文。我们知道,一个像素是由RGBA四个分量组成的,该论文就论述了如何实现不同数字图像的像素之间是如何进行混合的,该论文提出了多种像素混合的模式。如果做过图像处理开发,会对其比较了解,该技术也和OpenGL中的Alpha混合技术异曲同工。

PorterDuffXfermode支持以下十几种像素颜色的混合模式,分别为:CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。

我们下面会分析几个代码片段研究PorterDuffXfermode使用及工作原理详解。

官方文字:https://developer.android.com/reference/android/graphics/PorterDuff.Mode

PorterDuffXfermode 原理

Android 使用 Canvas 在 View 上绘制图形 ,所绘制的图形中的像素称作源像素(source,简称src),所绘制的矩形在Canvas中对应位置的矩形内的像素称作目标像素(destination,简称dst)。源像素的ARGB四个分量会和Canvas上同一位置处的目标像素的ARGB四个分量按照 Xfermode定义的规则进行计算,形成最终的ARGB值,然后用该最终的ARGB值更新目标像素的ARGB值。

准备

准备自定义 MyView , 并且设置背景色为 蓝色

    <com.zyj.demo.MyView
        android:background="#FF3700B3"
        android:layout_gravity="center"
        android:layout_width="300dp"
        android:layout_height="300dp" />

MyView

public class MyView extends View {
    
    

    private Paint mPaint;

    public MyView(Context context) {
    
    
        super(context);
        initPaint();
    }

    public MyView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        initPaint();
    }

    public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    
    
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    private void initPaint() {
    
    
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        mPaint.setColor(Color.RED);
        canvas.drawCircle(centerX, centerY, radius, mPaint);
    }
}

效果图如下

在这里插入图片描述

PorterDuff.Mode.CLEAR

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        mPaint.setColor(Color.RED);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setXfermode(null);
    }

该规则比较简单粗暴,直接要求目标像素的ARGB四个分量全置为0,即(0,0,0,0),即透明色。

由于Activity本身屏幕的背景时黑色的,所以此处就显示了一个黑色的圆形。

在这里插入图片描述

在这里插入图片描述

PorterDuff.Mode.ADD

将源像素 和 目标像素 进行叠加,生成新的像素值。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        mPaint.setColor(Color.RED);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setXfermode(null);
    }

在这里插入图片描述

目标像素是蓝色 , 源像素是红色,进行叠加 混合像素是粉红色。

PorterDuff.Mode.SRC

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,上图中就是红色圆形。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        mPaint.setColor(Color.RED);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setXfermode(null);
    }

效果图

在这里插入图片描述

扩展,既然 SRC 模式是保留源像素,那么我们可以绘制一个透明色,来达到 CLEAR 的效果。如下;

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;
        
        //透明色
        mPaint.setColor(Color.TRANSPARENT);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setXfermode(null);
    }

在这里插入图片描述
因为只保留透明色,activity 是黑色的,所以最终的颜色值是 黑色

PorterDuff.Mode.SRC_OVER

默认的 Android 构图方式是 PorterDuff.Mode.SRC_OVER,相当于在目标图像上绘制源图像/颜色。

在这里插入图片描述
这种模式下,绘制的图像会覆盖目标像素上。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        mPaint.setColor(Color.RED);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        mPaint.setXfermode(null);
    }

在这里插入图片描述

PorterDuff.Mode.SRC

只保留源图像的 alpha 和 color ,所以绘制出来只有源图,上图中就是红色圆形。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);
    }

在这里插入图片描述

扩展,既然 SRC 模式是保留源像素,那么我们可以绘制一个透明色,来达到 CLEAR 的效果。如下;

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.TRANSPARENT);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);
    }

在这里插入图片描述

PorterDuff.Mode.SRC_IN

在两者相交的地方绘制源图像,不相交的地方,丢弃源像素。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);
   
        canvas.restoreToCount(id);
    }
}

在这里插入图片描述
添加 PorterDuff.Mode.SRC_IN 后

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);
        mPaint.setXfermode(null);

        canvas.restoreToCount(id);
    }

效果

在这里插入图片描述

PorterDuff.Mode.CLEAR

添加 PorterDuff.Mode.CLEAR , 相交的地方变成透明,不相交的地方,丢弃源像素。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);
        mPaint.setXfermode(null);

        canvas.restoreToCount(id);
    }

在这里插入图片描述

PorterDuff.Mode.DST_IN

相交的地方,保留目标像素,不相交的地方,丢弃源像素。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);
        mPaint.setXfermode(null);

        canvas.restoreToCount(id);
    }

在这里插入图片描述

PorterDuff.Mode.XOR

相交的地方,同时放弃源像素 和 目标像素,也就是说相交的地方变成透明色。不相交的地方,保留源像素。

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);
        mPaint.setXfermode(null);

        canvas.restoreToCount(id);
    }

在这里插入图片描述
分析:相交的地方,放弃黄色、放弃红色,显示透明,所以看到的是蓝色背景。不相交的地方,绘制红色源像素。

PorterDuff.Mode.DST

丢弃所有源像素,而目标像素保持不变

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);
    }

在这里插入图片描述
因为丢弃了红色矩形,所以显示出来的是黄色的圆形

PorterDuff.Mode.DST_IN

相交的地方,保留目标像素,丢弃所有源像素。

我在测试的时候,发现 DST_IN 显示的效果和 DST 一样

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);
    }

在这里插入图片描述

PorterDuff.Mode.DST_OUT

相交的地方,同时丢弃源像素和目标像素,即显示透明。未相交的地方,丢弃源像素,保留目标像素

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        Rect rect = new Rect(centerX, centerY, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);
    }

在这里插入图片描述

实战1-蓄水池效果

先看效果图:

在这里插入图片描述

public class MyView extends View {
    
    

    private Paint mPaint;

    public MyView(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
        initPaint();
    }

    private void initPaint() {
    
    
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    int top = -1;

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;
        int radius = getWidth() / 3;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        //绘制空心圆
        mPaint.setColor(Color.YELLOW);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(30);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        //绘制矩形
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        if (top == -1) {
    
    
            top = getHeight();
        } else {
    
    
            top = top - 2;
            if (top <= 0) {
    
    
                top = 0;
            }
        }
        Rect rect = new Rect(0, top, getWidth(), getHeight());
        canvas.drawRect(rect, mPaint);

        mPaint.setXfermode(null);
        canvas.restoreToCount(id);

        if (top > 0) {
    
    
            invalidate();
        }
    }

实战2-美女小猫

先看效果图
在这里插入图片描述
首先准备两张图片,一个小猫,一个美女

请添加图片描述
请添加图片描述
代码如下

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

        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;

        int id = canvas.saveLayer(0, 0, getWidth(), getHeight(), mPaint, Canvas.ALL_SAVE_FLAG);

        // 绘制猫
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cat);
        bitmap = Bitmap.createScaledBitmap(bitmap, centerX, centerY, true);
        canvas.drawBitmap(bitmap, centerX - bitmap.getWidth() / 2, centerY - bitmap.getHeight() / 2, null);

        //绘制美女
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        Bitmap starBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.meilv);
        starBitmap = Bitmap.createScaledBitmap(starBitmap, centerX, centerY, true);
        canvas.drawBitmap(starBitmap, centerX - starBitmap.getWidth() / 2, centerY - starBitmap.getHeight() / 2, mPaint);

        mPaint.setXfermode(null);

        canvas.restoreToCount(id);
    }

这种效果我们用了 PorterDuff.Mode.SRC_IN 。特性是:相交的部分,绘制源像素,不相交的地方,丢弃源像素。

实战3-新手引导挖孔Dialog

github: https://github.com/zyj1609wz/AppGuideDialog

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zhaoyanjun6/article/details/127270584