Android之Canvas绘图中PorterDuffXfermode

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36347817/article/details/88988544

忙里偷闲,增进一下自己的做UI功底,最近进行一段时间学习自定义View。

在Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值。

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。

1.重写了View的onDraw方法,首先将View的背景色设置为绿色,然后绘制了一个黄色的圆形,然后再绘制一个蓝色的矩形:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //设置背景色
    canvas.drawARGB(255, 139, 197, 186);

    int canvasWidth = canvas.getWidth();
    int r = canvasWidth / 3;
    //绘制黄色的圆形
    paint.setColor(0xFFFFCC44);
    canvas.drawCircle(r, r, r, paint);
    //绘制蓝色的矩形
    paint.setColor(0xFF66AAFF);
    canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}

效果图:

2.使用PorterDuffXfermode对上面的代码进行一下修改:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //设置背景色
    canvas.drawARGB(255, 139, 197, 186);

    int canvasWidth = canvas.getWidth();
    int r = canvasWidth / 3;
    //正常绘制黄色的圆形
    paint.setColor(0xFFFFCC44);
    canvas.drawCircle(r, r, r, paint);
    //使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    paint.setColor(0xFF66AAFF);
    canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
    //最后将画笔去除Xfermode
    paint.setXfermode(null);
}

效果图:

以上代码分析:

  • 首先,我们调用了canvas.drawARGB(255, 139, 197, 186)方法将整个Canvas都绘制成一个颜色,此时所有像素都不透明。
  • 然后我们通过调用canvas.drawCircle(r, r, r, paint)绘制了一个黄色的圆形到Canvas上面。
  • 然后我们执行代码paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)),将画笔的PorterDuff模式设置为CLEAR。
  • 然后调用canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint)方法绘制蓝色的矩形,但是最终界面上出现了一个白色的矩形。
  • 在绘制完成后,我们调用paint.setXfermode(null)将画笔去除Xfermode。

当调用canvas.drawXXX()方法时传入一个画笔Paint对象,在绘图时会先检查该画笔Paint对象有没有设置Xfermode。若没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。本例中的Xfermode是PorterDuff.Mode.CLEAR,通过canvas.drawRect()在Canvas上绘制了一个透明的矩形,由于Activity本身屏幕的背景时白色的,所以此处就显示了一个白色的矩形。

3.将绘制圆形和绘制矩形相关的代码放到canvas.saveLayer()和canvas.restoreToCount()之间。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //设置背景色
    canvas.drawARGB(255, 139, 197, 186);

    int canvasWidth = canvas.getWidth();
    int canvasHeight = canvas.getHeight();
    //在默认layer的上部新建一个layer
    int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
        int r = canvasWidth / 3;
        //正常绘制黄色的圆形
        paint.setColor(0xFFFFCC44);
        canvas.drawCircle(r, r, r, paint);
        //使用CLEAR作为PorterDuffXfermode绘制蓝色的矩形
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        paint.setColor(0xFF66AAFF);
        canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
        //最后将画笔去除Xfermode
        paint.setXfermode(null);
    //新的layer绘制到canvas默认的layer上去
    canvas.restoreToCount(layerId);
}

  效果图:

以上代码分析:

  • 首先,调用canvas.drawARGB()方法将整个Canvas都绘制成一个颜色,此时所有像素都不透明。
  • 然后我们将主要的代码都放到了canvas.saveLayer()以及canvas.restoreToCount()之间。

canvas支持图层layer渲染,调用canvas的各种drawXXX()方法,都是绘制到canvas默认的layer上面。通过canvas.saveLayer()新建一个layer放置在默认layer的上部。执行canvas.saveLayer()后,所有绘制操作都在新建的layer上,且新的layer是完全透明的。新的layer绘制完成后调用canvas.restoreToCount(layer)或者canvas.restore()把这个layer绘制到canvas默认的layer上去。

不同混合模式的计算规则:

像素颜色四个分量:ARGB,A表示Alpha值,RGB表示颜色。

用S代表源像素,源像素的颜色值可表示为[Sa, Sc],Sa表示源像素的Alpha值,Sc表示源像素的RGB。

用D代表目标像素,目标像素的颜色值可表示为[Da, Dc],Da表示目标像素的Alpha值,Dc表示目标像素的RGB。

源像素与目标像素在不同混合模式下计算颜色的规则:

  • CLEAR:[0, 0]
  • SRC:[Sa, Sc]
  • DST:[Da, Dc]
  • SRC_OVER:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
  • DST_OVER:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]
  • SRC_IN:[Sa * Da, Sc * Da]
  • DST_IN:[Sa * Da, Sa * Dc]
  • SRC_OUT:[Sa * (1 - Da), Sc * (1 - Da)]
  • DST_OUT:[Da * (1 - Sa), Dc * (1 - Sa)]
  • SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
  • DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]
  • XOR:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]
  • DARKEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
  • LIGHTEN:[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]
  • MULTIPLY:[Sa * Da, Sc * Dc]
  • SCREEN:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]
  • ADD:Saturate(S + D)
  • OVERLAY:Saturate(S + D)

效果图:

注:DARKEN、LIGHTEN、OVERLAY等几种混合规则在GPU硬件加速下不起效。

若混合模式没有正确使用,可以调用:

//View禁用掉GPU硬件加速,切换到软件渲染模式
View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

这样混合模式就能正常使用。

猜你喜欢

转载自blog.csdn.net/qq_36347817/article/details/88988544