关于Paint 的 setXfermode(Xfermode xfermode) 的理解

来源:互联网 发布:手机归属地数据库下载 编辑:程序博客网 时间:2024/04/28 05:54

相信很多人在使用Paint的setXfermode(Xfermode xfermode)时,也会有很多问题,比如哪些是源图,哪些是目标图,Canvas是怎样绘制的等等。通过参考一些资料,终于明白了Canvas、Bitmap及图层的关系。

一、Canvas与Bitmap

在讲解Paint 的setXfermode时,先来说一下Canvas的相关知识。
1、Canvas的获取

Canvas常用的获取方式有两种,一种我们通过重写View.onDraw方法,View中的Canvas对象会被当做参数传递过来,我们操作这个Canvas,效果会直接的显示在View中。另一种是通过new 一个Canvas对象,方法如下:

   Bitmap bitmap = Bitmap.createBitmap(100,100, Bitmap.Config.ARGB_8888);        Canvas canvas = new Canvas(bitmap);        //或者       // Canvas canvas = new Canvas();      //  canvas.setBitmap(bitmap);

总之,Canvas需要一个非空的Bitmap(画布),用来呈现Canvas绘制的视图。
2、Bitmap

每一个Bitmap都是一个画布,是用来呈现视图的。我们通过Bitmap.createBitmap(…)时,创建的是一个透明的画布,所有的图像都是画在bitmap上的。我们知道每一次调用canvas.drawxxx函数时,都会生成一个专用的透明图层来画这个图形,画完以后,就盖在了画布上。所以如果我们连续调用五个draw函数,那么就会生成五个透明图层,画完之后依次盖在画布上显示。画布有两种,第一种是view的原始画布,是通过onDraw(Canvas canvas)函数传进来的,其中参数中的canvas就对应的是view的原始画布,控件的背景就是画在这个画布上的!另一种是人造画布,通过saveLayer()、new Canvas(bitmap)这些方法来人为新建一个画布。尤其是saveLayer(),一旦调用saveLayer()新建一个画布以后,以后的所有draw函数所画的图像都是画在这个画布上的,只有当调用restore()、resoreToCount()函数以后,才会返回到原始画布上绘制。
二、setXfermode(Xfermode xfermode)

PorterDuffXfermode是 Xfermode的一个子类,也是一个唯一没有过时的子类,功能很强大。该类有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),mode模式及效果图如下
这里写图片描述
下面以一个例子来说明它的使用
首先创建源图 srcBitmap,目标图dstBitmap

    //定义一个绘制矩形的Bitmap    private Bitmap makeSrc(int w, int h) {          Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFF66AAFF);        c.drawRect(0, 0, w, h, p);        return bm;    }    //定义一个绘制圆形Bitmap    private Bitmap makeDst(int w, int h) {         Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);        Canvas c = new Canvas(bm);        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);        p.setColor(0xFFFFCC44);        c.drawOval(new RectF(0, 0, w, h), p);        return bm;    }

绘制好srcBitmap 和 dstBitmap 后,接下来,让我们去见证一下奇迹。

  @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawColor(Color.GREEN);//将背景设置为绿色        int sc = canvas.saveLayer(0, 0, screenW, screenH, null, Canvas.MATRIX_SAVE_FLAG |                Canvas.CLIP_SAVE_FLAG |                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |                Canvas.FULL_COLOR_LAYER_SAVE_FLAG |                Canvas.CLIP_TO_LAYER_SAVE_FLAG);//创建一个新的Bitmap,在新的画布上演示图形混合后的效果        canvas.drawBitmap(dstBitmap, 0,                0, mPaint);            mPaint.setXfermode(pdXfermode); //设置Paint的Xfermode        canvas.drawBitmap(srcBitmap, width / 2,                height / 2, mPaint);        mPaint.setXfermode(null);        canvas.restoreToCount(sc);// 还原画布,将新创建的画布合并到原画布上    }

运行的效果是这样的
这里写图片描述

那如果把saveLayer去掉,代码变成如下,会有什么要的效果呢

 canvas.drawColor(Color.GREEN);//将背景设置为绿色 canvas.drawBitmap(dstBitmap, 0, 0, mPaint); mPaint.setXfermode(pdXfermode); //设置Paint的Xfermode canvas.drawBitmap(srcBitmap, width / 2, height / 2, mPaint); mPaint.setXfermode(null);

运行的效果是这样的
这里写图片描述
怎么会变成这样呢,难道setXfermode没起作用,弄的我是一脸懵逼。在讲解原因之前先来说一下saveLayer的绘图流程。

1. 有saveLayer时
当调用saveLayer时,会创建一个透明的bitmap,以后调用的Canvas.drawXXX(),都是在新的bitmap上进行绘制。除了黄色的圆,其他的都是透明的。在绘制源图时,会根据目标图的透明度进行计算,目标图与源图相交时,由于黄色圆之外的都是空像素,所以源图与目标图相交之外的地方就不会显示了。
xFermode合成过程如下
这里写图片描述
2. 无saveLayer时
当第二次去掉saveLayer代码时,所有的绘图操作都放在了原始View的Canvas所对应的Bitmap上了。我们先把整个画布给染成了绿色,然后再画上了一个圆形,所以在应用xfermode来画源图像的时候,目标图像当前Bitmap上的所有图像了,也就是整个绿色的屏幕和一个圆形了。所以这时候源图像的相交区域是没有透明像素的,这也就不难解释出现的问题了。
xFermode合成过程如下
这里写图片描述

三、另一种实现方式

前面已经说了,既然saveLayer会创建一个新的bitmap,那么如果通过Bitmap.createBitmap(…)会实现上面的效果吗。代码如下
首先创建一个Canvas

mBitmap = Bitmap.createBitmap(screenW, screenH, Bitmap.Config.ARGB_8888);mCanvas = new Canvas(mBitmap);

这就创建了一个新的透明的画布,然后将它传递给Canvas。

 @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.drawColor(Color.GREEN);//将背景设置为绿色        mCanvas.drawBitmap(dstBitmap, 0, 0, mPaint);        mPaint.setXfermode(pdXfermode); //设置Paint的Xfermode        mCanvas.drawBitmap(srcBitmap, width / 2, height / 2, mPaint);        mPaint.setXfermode(null);        canvas.drawBitmap(mBitmap,0,0,null);    }

在绘制目标图和源图时是在新创建的mBitmap上,最后在将mBitmap绘制到原View的画布上,绘制效果如下,同样实现了saveLayer的功能。
这里写图片描述

注:以上的总结参考了http://www.2cto.com/kf/201605/505751.html ,然后加了一些自己的理解,也算是对自己的一个总结。如果有跑偏的地方,也希望能给出指正。

1 0
原创粉丝点击