xfermode的基本用法

xfermode叠加模式,很多特效都可以用这个做。

Posted by 陈再峰 on January 14, 2018

1、Xfermode是什么

在Android自定义控件或者对图片等进行处理时需要做一些图像混合的操作时,会用到xfermode。利用xfermode可以做出许多有趣的UI效果时。比如做不同形状的头像,刮刮卡。 在做这些效果之前需要先了解xfermode的使用。

2、Xfermode的基本用法

2.1、Xfermode的API

xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode。其中AvoidXfermode, PixelXorXfermode已经过时不推荐使用。那么PorterDuffXfermode则是需要了解的东西。

2.2、Xfermode的基本概念

一下的一张图和一段伪代码可以理解PorterDuffXfermode的基本概念。

[图片上传失败…(image-912ba6-1515977399270)]

Xfermode理解起来并不是很难,根据上面的图可以理解为,两个不同的像素点。通过Xfermode的不同的混合模式混合之后展示出来的新的像素点效果。(注意这里是针对每一个像素的混合效果。而且这两个像素点需要是在画布上的同一位置,可以理解为重叠)

伪代码可以这样表示:

// 初始化PorterDuffXfermode
private PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);

// 在ondraw中使用PorterDuffXfermode
 protected void onDraw(Canvas canvas) {

        // DstBitmap SRCBitmap 为两个不同的bitmap
        canvas.drawBitmap(DstBitmap,0,0,mPaint);
        // PorterDuffXfermode和paint联用
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(SRCBitmap,0,0,mPaint);
        
        // 将xfermode制空
        mPaint.setXfermode(null);
    }

以上的代码也比较简单理解: 先draw一个bitmap,然后设置paint的xfermode,然后在画第二个bitmap。这样他们重叠的部分就会出现不通过的UI效果了。

2.3、Xfermode的多种混合模式

PorterDuffXfermode的构造函数:

public PorterDuffXfermode(PorterDuff.Mode mode) {}

参数传入了PorterDuff.Mode mode,以下列出PorterDuff的所以的Mode。

public enum Mode {
    /** [0, 0] */
    CLEAR       (0),
    /** [Sa, Sc] */
    SRC         (1),
    /** [Da, Dc] */
    DST         (2),
    /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
    SRC_OVER    (3),
    /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
    DST_OVER    (4),
    /** [Sa * Da, Sc * Da] */
    SRC_IN      (5),
    /** [Sa * Da, Sa * Dc] */
    DST_IN      (6),
    /** [Sa * (1 - Da), Sc * (1 - Da)] */
    SRC_OUT     (7),
    /** [Da * (1 - Sa), Dc * (1 - Sa)] */
    DST_OUT     (8),
    /** [Da, Sc * Da + (1 - Sa) * Dc] */
    SRC_ATOP    (9),
    /** [Sa, Sa * Dc + Sc * (1 - Da)] */
    DST_ATOP    (10),
    /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
    XOR         (11),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
    DARKEN      (12),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
    LIGHTEN     (13),
    /** [Sa * Da, Sc * Dc] */
    MULTIPLY    (14),
    /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
    SCREEN      (15),
    /** Saturate(S + D) */
    ADD         (16),
    OVERLAY     (17);
}

注释中表明了这些模式的算法。
在了解这些算法之前需要先了解像素颜色通道。

一个像素的颜色是由四个分量组成即:ARGB
A为透明度通道。RGB为颜色通道。

A : 像素点的透明度通道 值为0-1f
R : 像素点红色通道 值为0-250f
G :像素点绿色通道 值为0-250f
B :像素点蓝色通道 值为0-250f

如果某个像素点的通道值越大则改像素点所占颜色比例越多。比如A(透明通道)A值越小就越透明。A为0就完全透明,A为1f就是完全不透明。当然这个概念在xfermode中只要了解就行了。xfermode不会去改变这些值。

那么注释中的sa、sc、da、 dc可以如下理解

S为源图 D为目标图
Sa:全称为Source alpha,表示源图的Alpha通道;
Sc:全称为Source color,表示源图的颜色;
Da:全称为Destination alpha,表示目标图的Alpha通道;
Dc:全称为Destination color,表示目标图的颜色.

表示一个源图片的像素点: [Sa , Sc]
表示一个目标图片的像素点: [Da , Dc]
然后根据不同的模式的算法就可以生成混合后的像素点了。当然混合后的像素点也是用[A , B]这种格式表示。

2.4、xfermode模式算法的示例

不同的模式有不同的算法。算不同最后的结果也不同。

image

官方的贴图非常形象的展示出各种混合模式使用后展示的效果。

接下来挑出一个常用的例子SRC_IN来解释下这些算法的基本应用。
圆形头像实现的方式可能有很多。比如用bitmapshader等等。使用xfermode同样能实现。

看下SRC_IN注释的算法方式。

SRC_IN      (5),
/** [Sa * Da, Sa * Dc] */

SRC_IN的算法是这样的:

(a)、Sa * Da:源图(S)像素透明度和目标图片(D)像素的透明的决定混合后像素的透明度
(b)、Sa * Dc:源图(S)像素透明度和目标图片(D)像素的颜色决定混合后像素的颜色

那么混合的图解:

SRC_IN图解

从(a)(b)可以看出,源图片只采用了透明度的变化。混合后图像像素的透明度和颜色都和源图的像素的透明度的有关。如果源图的像素是透明的,那么混合后的像素为透明。反之不透明。所以源图为:

源图片(S)

从(b)可以看出,决定混合后图像素颜色是由目标图片(D)决定的。所以目标图片是:

目标图片(D)

这里主要是理解算法:[Sa * Da, Sa * Dc]
最后效果: TIM截图20180122152840.png

示例代码:

public class CustomHeadView extends View {

    private Bitmap DBitmap;

    private Bitmap SBitmap;

    private Paint mPaint;

    private PorterDuffXfermode xfermode;

    public CustomHeadView(Context context) {
        this(context, null);
    }

    public CustomHeadView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init(){
        mPaint = new Paint();
        // 这个图片是正常的头像
        DBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.head_d,null);
        // 这个图片是中间一个圆,四个角透明的图片
        SBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.head_s,null);
        xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);

        canvas.drawBitmap(SBitmap,0,0,mPaint);
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(DBitmap,0,0,mPaint);

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

上述例子已经非常清晰的说明了xfermode的算法:

源图(S)和目标图(D)像素的透明度和颜色,通过特定的算法来算出混合后新图的透明度和颜色。(注意这里是对每个像素进行操作)

这里只是举了一个例子,如果需要了解更多的xfermode的效果可以看下这个博客: https://www.jianshu.com/p/d11892bbe055

2.5、关闭硬件加速

有时候会发现,显示出来的东西就是一个纯色,没有图片。可能是没有关闭硬件加速。 这个API因为不支持硬件加速在API 16已经过时了(大家可以在HardwareAccel查看那些方法不支持硬件加速)如果想在高于API 16的机子上进行测试,必须现在应用或手机设置中关闭硬件加速。
这里我就不多做介绍了,看下这篇博客吧: https://www.cnblogs.com/libertycode/p/6290497.html

3、一些常用PorterDuffXfermode的例子

3.1、各种形状的图形

使用xfermode来完成圆形头像只是其中之一。如果有特殊要求,想弄成其他的形状都是可以的。 如果我上面写的圆形图片的例子能够理解,那么其他的各种形状的例子使用的方法是一样的。

各种形状的图形

3.2、刮刮卡效果

实际上实现一个效果并不是说只能采用一种叠加模式。用不同的模式也能做到相同的效果。 这里展示的刮刮卡效果,采用DST_OUT模式。 还有一个撕掉美女的衣服和刮刮卡类似,但是用的是SRC_OUT模式。

这里的源图和目标图有点不同,源图或者目标图是手指滑动的路径。毕竟划过的路径变成透明了。

刮刮卡效果

这里撕掉美女衣服太过底图太“火爆”,动图就不截取了。

3.3、xfermode不仅仅以上的效果。其他的就不一一介绍。

4、这个不知道起个什么标题好,就记录下我学习xfermode遇到的问题把。

1、 使用xfermode可能会纠结源图和目标图的问题。

 这个确实是这样的。具体哪个做源图和目标图要根据具体
 实现的效果和不同混合模式的算法去确定。 

2、使用xfermode是针对图片的像素的。

 前面提到多次,操作的是像素。

3、要调试多次才能达到效果。

这个问题我遇到多次,觉得xfermode有毒。   

4、这篇文章只是基础,而且写的还有点烂。

 好的文章应该是下一篇!

源码地址:https://github.com/AxeChen/XfermodeSimple

原文来自掘金:https://juejin.im/post/5a5aca3a6fb9a01cbf384ea7 作者:尘封的落叶(就是我自己。)