Android 混合模式之 PorterDuffXfermode

Android 混合模式之 PorterDuffXfermode_第1张图片
注意:在使用PorterDuffXfermode的时候,目标图像(DST)和源图像(SRC)混合的操作要在一个新的图层上进行,否则当前的Canvas上的像素会影响混合操作。

@Override
protected void onDraw(Canvas canvas) {
    //注释1处,创建一个新的透明图层
    int layerId = canvas.saveLayer(new RectF(), null, Canvas.ALL_SAVE_FLAG);
    //...
    //目标图像(DST)和图像(SRC)混合的操作
    
    //先绘制目标bitmap
    canvas.drawBitmap(mDstB, 0, 0, paint);
    //设置混合模式
    paint.setXfermode(sModes[i]);
    //绘制源bitmap
    canvas.drawBitmap(mSrcB, 0, 0, paint);
    //绘制以后清空混合模式,避免影响其他绘制
    paint.setXfermode(null);
    //...
    //将新的图层绘制到上一个图层或者屏幕上(如果没有上一个图层)。
    canvas.restoreToCount(layerId);
}

注释1处,创建一个新的透明图层。

创建目标图像

public 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);
    //设置的透明度是FF,完全不透明
    p.setColor(0xFFFFCC44);
    c.drawOval(new RectF(0, 0, w * 3 / 4f, h * 3 / 4f), p);
    return bm;
}

注意:我们画笔设置的透明度是完全不透明,在Android中完全不透明用float类型的数表示为1f。Android 混合模式之 PorterDuffXfermode_第2张图片
注意:我们创建的目标图像的大小是红色框的大小,我专门给标了出来。只不过我们只在圆形区域绘制了黄色内容,黄色圆形区域(0xFFFFCC44)的alpha是FF,color是FFCC44。剩余其他部分没有绘制(0x00000000),也就是说其他部分的alpha是0,color也是0,是透明的,可以理解为没有像素。这一点很重要,因为在这个例子中,目标图像和源图像大小是一样的,图像的每个区域的alpha和color都会参与混合运算。

目标图像黄色圆形区域(0xFFFFCC44),把ARGB转化成0到1的浮点数就是 [1, 1, 0.8 , 0.267]。
目标图像其他区域(0x00000000),把ARGB转化成0到1的浮点数就是 [0, 0, 0 , 0]。

创建源图像

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);
    //设置的透明度是FF
    p.setColor(0xFF66AAFF);
    c.drawRect(w / 3f, h / 3f, w * 19 / 20f, h * 19 / 20f, p);
    return bm;
}

注意:我们画笔设置的透明度是完全不透明,在Android中完全不透明用float类型的数表示为1f。
Android 混合模式之 PorterDuffXfermode_第3张图片
注意:我们创建的目标图像的大小是红色框的大小,我专门给标了出来。只不过我们只在矩形区域绘制了蓝色内容,蓝色矩形区域(0xFF66AAFF)的alpha是FF,color是66AAFF。剩余其他部分没有绘制(0x00000000),也就是说其他部分的alpha是0,color也是0,是透明的,可以理解为没有像素。这一点很重要,因为在这个例子中,目标图像和源图像大小是一样的,图像的每个区域的alpha和color都会参与混合运算。

源图像蓝色矩形区域(0xFF66AAFF),把ARGB转化成0到1的浮点数就是 [1, 0.4, 0.667 , 1]。
源图像其他区域(0x00000000),把ARGB转化成0到1的浮点数就是 [0, 0, 0 , 0]。

PorterDuffXfermode的构造函数

public PorterDuffXfermode(PorterDuff.Mode mode) {
    porterDuffMode = mode.nativeInt;
}

PorterDuff.Mode类,表示混合模式,枚举值有18个。

    public enum Mode {
      
        CLEAR       (0),
        SRC         (1),
        DST         (2),
        SRC_OVER    (3),
        DST_OVER    (4),
        SRC_IN      (5),
        DST_IN      (6),
        SRC_OUT     (7),
        DST_OUT     (8),
        SRC_ATOP    (9),
        DST_ATOP    (10),
        XOR         (11),
        DARKEN      (16),
        LIGHTEN     (17),
        MULTIPLY    (13),
        SCREEN      (14),
        ADD         (12),
        OVERLAY     (15);

        Mode(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        
        public final int nativeInt;
    }

定义:

α \alpha αdst :目标图像的透明度
C C Cdst:目标图像的色值

α \alpha αsrc :源图像的透明度
C C Csrc:源图像的色值

α \alpha αout:混合结果的透明度
C C Cout:混合结果的色值。

混合结果分为两部分, α \alpha αout 表示结果的透明度部分, C C Cout表示计算结果的色值部分。

注意:在这个例子中,目标图像和源图像的有颜色的区域alpha=FF,如果表示成浮点数就是1f。这一点要注意,透明度会影响我们的计算结果。

各种混合模式的计算

CLEAR

计算公式:

α \alpha αout = 0
C C Cout = 0

目标图像被源图像覆盖的区域像素清空到0。在这个例子中,源图像大小和目标图像是一样大的,就是红色框所示大小。最终显示结果是透明的。
Android 混合模式之 PorterDuffXfermode_第4张图片

SRC

源图像的像素替代目标图像的像素。注意是替代

计算公式:

α \alpha αout = α \alpha αsrc
C C Cout = C C Csrc

Android 混合模式之 PorterDuffXfermode_第5张图片

DST

源图像像素被丢弃,保留目标图像像素不变。

计算公式:

α \alpha αout = α \alpha αdst
C C Cout = C C Cdst

Android 混合模式之 PorterDuffXfermode_第6张图片

SRC_OVER

源像素会覆盖在目标像素之上。

计算公式:

α \alpha αout = α \alpha αsrc + (1 - α \alpha αsrc) * α \alpha αdst
C C Cout = C C Csrc + (1 - C C Csrc) * C C Cdst

在这个例子中:

  1. 源图像的蓝色矩形区域alpha=1,计算结果是:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  2. 源图像的其他区域的alpha=0,其他区域和目标图像的黄色圆形部分相交的区域计算结果是:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

  3. 源图像的其他区域的alpha=0,color=0x000000。目标图像的其他区域的alpha=0,color=0x000000,计算结果是:
    α \alpha αout = 0
    C C Cout = 0

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第7张图片

DST_OVER

源像素在目标像素下面绘制。

计算公式:

α \alpha αout = α \alpha αdst + (1 - α \alpha αdst) * α \alpha αsrc
C C Cout = C C Cdst + (1 - C C Cdst) * C C Csrc

在这个例子中:

  1. 目标图像的黄色圆形区域alpha=1,计算结果是:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

  2. 目标图像的其他区域的alpha=0,color=0x000000,其他区域和源图像的蓝色矩形部分相交的区域计算结果是:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  3. 目标图像的其他区域的alpha=0,color=0x000000,,源图像的其他区域的alpha=0,color=0x000000,计算结果是:
    α \alpha αout = 0
    C C Cout = 0

所以最终的显示效果如下所示:

Android 混合模式之 PorterDuffXfermode_第8张图片

SRC_IN

保留源图像覆盖目标图像的区域的源图像的像素,其他区域的像素全部丢弃。

计算公式:

α \alpha αout = α \alpha αsrc * α \alpha αdst
C C Cout = C C Csrc * α \alpha αdst

在这个例子中:

  1. 源图像完全覆盖目标图像,源图像的蓝色矩形部分和目标图像的黄色圆形区域相交的区域是一个扇形,计算结果是:
    α \alpha αout = 1
    C C Cout = C C Csrc
  2. 源图像的蓝色矩形部分和目标图像的黄色圆形区域不相交的区域,计算结果是:
    α \alpha αout = 0
    C C Cout = 0
  3. 其他区域的像素全部丢弃,计算结果是:
    α \alpha αout = 0
    C C Cout = 0

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第9张图片

DST_IN

在目标图像和源图像相交的区域,保留目标图像的像素,其他区域的像素全部丢弃。

计算公式:

α \alpha αout = α \alpha αsrc * α \alpha αdst
C C Cout = C C Cdst * α \alpha αsrc

在这个例子中:

  1. 目标图像和源图像完全相交,目标图像的黄色区域和源图像蓝色区域相交的区域,计算结果是:
    α \alpha αout = 1
    C C Cout = C C Cdst
  2. 目标图像的黄色区域和源图像蓝色区域不相交的区域,计算结果是:
    α \alpha αout = 0
    C C Cout = 0
  3. 其他区域的像素全部丢弃,计算结果是:
    α \alpha αout = 0
    C C Cout = 0

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第10张图片

SRC_OUT

保留源图像有像素的区域没有覆盖目标图像有像素的区域的像素。丢弃源图像有像素的区域覆盖目标图像有像素的区域的像素。丢弃所有的目标图像像素。

计算公式:

α \alpha αout = (1 - α \alpha αdst) * α \alpha αsrc
C C Cout = (1 - α \alpha αdst) * C C Csrc

在这个例子中:

  1. 保留源图像有像素的区域没有覆盖目标图像有像素的区域的像素。计算结果是:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc
  2. 丢弃源图像有像素的区域覆盖目标图像有像素的区域的像素。计算结果是:
    α \alpha αout = 0
    C C Cout = 0
  3. 丢弃所有的目标图像像素。计算结果是:
    α \alpha αout = 0
    C C Cout = 0

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第11张图片

DST_OUT

保留目标图像没被源图像覆盖部分的像素。丢弃被源图像像素覆盖部分的目标图像像素。丢弃所有的源图像像素。

计算公式:

α \alpha αout = (1 - α \alpha αsrc) * α \alpha αdst
C C Cout = (1 - α \alpha αsrc) * C C Cdst

在这个例子中:

  1. 保留目标图像没被源图像覆盖部分的像素。计算结果是:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst
  2. 丢弃被源图像像素覆盖部分的目标图像像素。计算结果是:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst
  3. 丢弃所有的源图像像素。计算结果是:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

所以最终的显示效果如下所示:

Android 混合模式之 PorterDuffXfermode_第12张图片

SRC_ATOP

丢弃源图像像素没有覆盖目标图像像素区域的源图像像素。在目标图像上绘制剩余的源图像的像素。目标图像其他区域不变。

计算公式:

α \alpha αout = α \alpha αdst
C C Cout = α \alpha αdst * C C Csrc + (1 - α \alpha αsrc) * C C Cdst

在这个例子中:
1.丢弃源图像像素没有覆盖目标图像像素区域的源图像像素。计算结果是:
α \alpha αout = 0
C C Cout = 0
2. 在目标图像上绘制剩余的源图像的像素。计算结果是:
α \alpha αout = α \alpha αdst
C C Cout = C C Csrc
3. 目标图像其他区域不变。计算结果是:
α \alpha αout = α \alpha αdst
C C Cout = C C Cdst

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第13张图片

DST_ATOP

丢弃没有被源图像像素覆盖的目标图像的像素。在源图像的绘制剩余的目标图像的像素。源图像其他区域不变。

计算公式:

α \alpha αout = α \alpha αsrc
C C Cout = α \alpha αsrc * C C Cdst + (1 - α \alpha αdst) * C C Csrc

在这个例子中:

  1. 丢弃没有被源图像像素覆盖的目标图像的像素,计算结果:
    α \alpha αout = 0
    C C Cout = 0
  2. 在源图像上绘制剩余的目标图像的像素。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Cdst
  3. 源图像其他区域不变。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

所以最终的显示效果如下所示:

Android 混合模式之 PorterDuffXfermode_第14张图片

XOR

计算结果:[ (1 - Da)*Sa + (1 - Sa)*Da, (1 - Da) *Sc + ( 1 - Sa ) * Dc],丢弃源图像像素覆盖的目标图像的像素的所有像素。绘制剩余的源图像的像素。目标图像其他区域不变。

计算公式:

α \alpha αout = (1 - α \alpha αdst) * α \alpha αsrc + (1 - α \alpha αsrc) * α \alpha αdst

C C Cout = (1 - α \alpha αdst) * C C Csrc + (1 - α \alpha αsrc) * C C Cdst

在这个例子中:

  1. 丢弃源图像像素覆盖的目标图像的像素的所有像素。计算结果:
    α \alpha αout = 0
    C C Cout = 0

  2. 绘制剩余的源图像的像素。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  3. 目标图像其他区域不变。计算结果:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

所以最终的显示效果如下所示:
Android 混合模式之 PorterDuffXfermode_第15张图片

DARKEN

保留源像素和目标像素的对应的RGB的最小分量。

计算公式:

α \alpha αout = α \alpha αsrc + α \alpha αdst - α \alpha αsrc * α \alpha αdst

C C Cout = (1 - α \alpha αdst) * C C Csrc + (1- α \alpha αsrc) * C C Cdst + min( C C Csrc, C C Cdst)

在这个例子中:

  1. 目标图像黄色区域和源图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

  2. 源图像蓝色区域和目标图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  3. 目标图像黄色区域和源图像蓝色区域叠加的部分。计算结果:
    α \alpha αout = 1,转化成16进制就是FF
    C C Cout = min( C C Csrc, C C Cdst)

这个min( C C Csrc, C C Cdst)怎么理解呢?我们看一看目标图像和源图像的色值。

目标图像黄色区域:0xFFCC44
源图像蓝色区域:0x66AAFF

RGB取各取两者之间小的那个值。是66AA44。对应的颜色如下所示:
Android 混合模式之 PorterDuffXfermode_第16张图片
所以最终显示结果如下所示:

Android 混合模式之 PorterDuffXfermode_第17张图片

LIGHTEN

保留源像素和目标像素的对应的RGB的最大分量。

计算公式:

α \alpha αout = α \alpha αsrc + α \alpha αdst - α \alpha αsrc * α \alpha αdst

C C Cout = (1 - α \alpha αdst) * C C Csrc + (1- α \alpha αsrc) * C C Cdst + max( C C Csrc, C C Cdst)

在这个例子中:

  1. 目标图像黄色区域和源图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

  2. 源图像蓝色区域和目标图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  3. 目标图像黄色区域和源图像蓝色区域叠加的部分。计算结果:
    α \alpha αout = 1,转化成16进制就是FF
    C C Cout = max( C C Csrc, C C Cdst)

这个max( C C Csrc, C C Cdst)怎么理解呢?我们看一看目标图像和源图像的色值。

目标图像:0xFFCC44
源图像:0x66AAFF

RGB取各取两者之间大的那个值。是FFCCFF。对应的颜色如下所示:

在这里插入图片描述
所以最终显示结果如下所示:
Android 混合模式之 PorterDuffXfermode_第18张图片

MULTIPLY

目标图像像素和源图像像素相乘。

计算公式:

α \alpha αout = α \alpha αsrc * α \alpha αdst
C C Cout = C C Csrc * C C Cdst

在这个例子中:

  1. 目标图像黄色区域和源图像透明区域叠加的部分。计算结果:
    α \alpha αout = 0
    C C Cout = 0

  2. 源图像黄色区域和目标图像透明区域叠加的部分。计算结果:
    α \alpha αout = 0
    C C Cout = 0

  3. 源图像黄色区域和目标图像蓝色区叠加的部分。计算结果:
    α \alpha αout = 1
    C C Cout = C C Csrc* C C Cdst

目标图像:0xFFCC44,把RGB看成0到1的浮点数就是 [1, 0.8 , 0.267]
源图像:0x66AAFF,把RGB看成0到1的浮点数就是 [0.4, 0.667 , 1]

RGB分别相乘,结果是 [0.4, 0.533 , 0.267],然后分别乘以255是[102, 136 , 68]。然后转化成RGB如下所示:

在这里插入图片描述

所以最终的显示结果如下所示:
Android 混合模式之 PorterDuffXfermode_第19张图片

SCREEN

源像素和目标像素相加,然后减去源像素乘以目标像素。

计算公式:

α \alpha αout = α \alpha αsrc + α \alpha αdst - α \alpha αsrc * α \alpha αdst
C C Cout = C C Csrc + C C Cdst - C C Csrc * C C Cdst

在这个例子中:

  1. 目标图像黄色区域和源图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αdst
    C C Cout = C C Cdst

  2. 源图像蓝色区域和目标图像透明区域叠加的部分。计算结果:
    α \alpha αout = α \alpha αsrc
    C C Cout = C C Csrc

  3. 源图像蓝色区域和目标图像黄色区域叠加的部分。计算结果:
    α \alpha αout = 1
    C C Cout = C C Csrc + C C Cdst - C C Csrc * C C Cdst

这个 C C Csrc + C C Cdst - C C Csrc * C C Cdst 怎么理解呢?我们看一看目标图像和源图像的色值。

目标图像:0xFFCC44转化成十进制是[255, 204, 68]
源图像:0x66AAFF转化成十进制是[102, 170, 255]

我们上面已经计算过 C C Csrc * C C Cdst的结果是[102, 136 , 68]。

[255, 204, 68] + [102, 170, 255] - [102, 136 , 68] = [255, 238, 255],对应的RGB如下所示:

在这里插入图片描述

所以最终的显示结果如下所示:
Android 混合模式之 PorterDuffXfermode_第20张图片

ADD

源像素添和目标像素相加,并使结果饱和。

计算公式:

α \alpha αout = max(0, min( α \alpha αsrc + α \alpha αdst ,1))

C C Cout = max(0, min( C C Csrc + C C Cdst ,1))

在这个例子中:

1.目标图像黄色区域和源图像透明区域叠加的部分。计算结果:

α \alpha αout = 1
C C Cout = C C Cdst

2.目标图像黄色区域和源图像蓝色区域的叠的部分。计算结果:

α \alpha αout = 1,十六进制表示就是FF
C C Cout = 1,十六进制表示就是FFFFFF

3.源图像蓝色区域和目标图像透明区域叠加的部分。计算结果:

α \alpha αout = 1
C C Cout = C C Csrc

所以最终的显示结果如下所示:
Android 混合模式之 PorterDuffXfermode_第21张图片

OVERLAY

根据目标颜色对源和目标进行倍增或筛选。

计算公式:

α \alpha αout = α \alpha αsrc + α \alpha αdst - α \alpha αsrc * α \alpha αdst

C o u t = { 2 ∗ C s r c ∗ C d s t 2 ∗ C d s t < α d s t α s r c ∗ α d s t − 2 ( α d s t − C s r c ) ( α s r c − C d s t ) o t h e r w i s e C_{out}=\left\{ \begin{array}{lcl} 2*C_{src}*C_{dst} & & {2*C_{dst} < {\alpha}_{dst}}\\ {\alpha}_{src}*{\alpha}_{dst}- 2({\alpha}_{dst}-C_{src})({\alpha}_{src}-C_{dst}) & & otherwise\\ \end{array} \right. Cout={2CsrcCdstαsrcαdst2(αdstCsrc)(αsrcCdst)2Cdst<αdstotherwise

目前还没搞明白这种模式。。。

所以最终的显示结果如下所示:

Android 混合模式之 PorterDuffXfermode_第22张图片
参考链接:

  • PorterDuff.Mode
  • Markdown数学符号&公式
  • 在markdown中如何加入上标、下标?
  • 在CSDN-Markdown中书写多行大括号公式

你可能感兴趣的:(Android)