PorterDuffXfermode误区总结

[TOC]android

概述

  类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode。在用Android中的Canvas进行绘图时,能够经过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照必定规则进行混合,造成新的像素值,从而更新Canvas中最终的像素颜色值,这样会建立不少有趣的效果。当使用PorterDuffXfermode时,须要将将其做为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,若是不想再使用Xfermode,那么能够执行Paint.setXfermode(null)。
  上面的概述中咱们提炼出两点:canvas

  • PorterDuffXfermode的做用是将所绘制的图形的像素与Canvas中对应位置的像素进行混合,造成新的像素。这是基本原理,请谨记;
  • 若是PorterDuffXfermode再也不使用,请调用Paint.setXfermode(null),关闭效果;

PorterDuffXfermode正确使用

  借用google官方demo中的图bash

image

不少同窗在测试过程当中发现和demo的实现有很大的出入,其实咱们仔细看官方demo,就会发现有很大不一样,我将核心代码抽取以下:ide

static 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 * 3 / 4, h * 3 / 4), p);
        return bm;
    }

// create a bitmap with a rect, used for the "src" image
static 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(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);
    return bm;
}

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

    canvas.drawColor(Color.GREEN);

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    canvas.restoreToCount(sc);
}
复制代码

代码中有几个核心的地方咱们要注意:测试

  • 调用saveLayer在一个新的Layer上进行图像的混合;

  这是由于在调用saveLayer时,会生成了一个全新的bitmap,这个bitmap的大小就是咱们指定的保存区域的大小,新生成的bitmap是全透明的,在调用saveLayer后全部的绘图操做都是在这个bitmap上进行的。ui

image

没有saveLayer的绘图流程google

  因为咱们先把整个画布给染成了绿色,而后再画上了一个圆形,因此在应用xfermode来画源图像的时候,目标图像当前Bitmap上的全部图像了,也就是整个绿色的屏幕和一个圆形了。因此这时候源图像的相交区域是没有透明像素的,透明度全是100%,这也就不难解释结果是这样的缘由了。spa

image

调用saveLayer的目的就是让你的目标图像独立的存在于一个layer上,而不受原始画布图像的干扰。这样才能进行目标图像和源图像的对应位置的像素混合。3d

  • 建立的目标图像bitmap与源图像bitmap大小是一致(其实不一致也是能够,只是计算图像坐标时更麻烦,没有必要);

注:rest

  1. 代码中w表明View的宽,h表明View的高;
  2. 咱们将canvas的背景绘制成GREEN;

PorterDuffXfermode使用误区

测试一 使用saveLayer获得的正确图像以下

image
进行SRC_IN变换,获得正确图像:
image

测试二 不使用saveLayer

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

    canvas.drawColor(Color.GREEN);

    //int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    //canvas.restoreToCount(sc);
}
复制代码

获得的图像以下:

image

PorterDuff.Mode.SRC_IN的含义是在二者相交的地方绘制源图像,由于没有使用saveLayer因此咱们目标图像是绿色的背景+黄色的圆,源图像是蓝色正方形。

  1. 目标图像bitmap与源图像btmap大小是彻底同样的;
  2. 源图像有图像区域小于源图像btmap大小的,而目标图像由于绘制了背景其有图像的区域与bitmap的实际大小是同样的;

那么这里有两个错误点:

  1. 咱们的目的是对黄色圆与蓝色方框进行图像混合,显然上面的结果是不对的,这就是图层的重要性;
  2. 根据SRC_IN的含义获得的结果应该只保留蓝色的正方形,除了蓝色的正方形外,其它区域应该是透明颜色,显然这也不符合咱们的预期;

为何获得的是黑色图像,而不是透明颜色,其实我没弄明白明白,可是咱们知道了正确使用PorterDuffXfermode的代码以下:

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

    .......

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);
    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);
    //绘制你的目标图像
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    //绘制你的源图像
    mPaint.setXfermode(null);
    canvas.restoreToCount(sc);
    
    .......
}
复制代码

测试三 你真的了解SRC_IN吗?

咱们在测试一的基础上修改一段代码

static 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(0x7fFFCC44);
    c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
    return bm;
}
复制代码

将目标图像中0xFFFFCC44改成0x7fFFCC44,也就透明度改成0.5

image
image

右边是没有修改透明度的图像,左边是修改后的;咱们发现SRC_IN的结果发生了变化。让咱们来看坎SRC_IN完整的解释:

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

细节决定成败,小小的细节颇有可能成为你平常工做中的大坑,更多PorterDuff.Mode含义,请参考

测试四 LAYER_TYPE_SOFTWARE使用

网上有很多说使用LAYER_TYPE_SOFTWARE的,咱们也来实践一下:
初始化时添加以下代码,使用软件加速

setLayerType(LAYER_TYPE_SOFTWARE, null);
复制代码

测试代码仍是使用调用了saveLayer的代码,可是使用CLEAR模式

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

    canvas.drawColor(Color.GREEN);

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    canvas.restoreToCount(sc);
}
复制代码

image
image

左边是没有设置setLayerType获得的混合图像,符合CLEAR模式的预期;右边是设置LAYER_TYPE_SOFTWARE后获得的图像,咱们发现混合后所有变成透明的了,这是为何呢?我给出这样一个猜测,在没有设置LAYER_TYPE_SOFTWARE时,只有有效图像区域参与像素混合,透明区域不参与像素混合;而设置LAYER_TYPE_SOFTWARE后透明区域也参与像素混合了。为了验证这一点,咱们进行测试五。

测试五

在测试四的基础上,目标bitmap和源bitmap是同样大的,咱们的测试方法是将源bitmap的大小修改成只有目标bitmap的1/4,这样一来源bitmap将只占据整个图像左上角1/4区域,获得的测试结果以下:

image

根据咱们的猜测,源bitmap的整个区域(透明区域和图像区域)都参与到与目标图像的像素混合中,致使左上角1/4区域变为了透明的。

以上分析根据我的经验得出,并不透彻,若有不对,望指正。

相关文章
相关标签/搜索