位图运算

  参考 Android 8.0 源码

/framework/base/graphics/java/android/graphics/Xfermode.java
/framework/base/graphics/java/android/graphics/PorterDuffXfermode.java
/framework/base/graphics/java/android/graphics/PorterDuff.java

PorterDuffXfermode

  位图运算为位图的功能繁衍提供了强大的技术基础,大大增强了位图的可塑性和延伸性,使很多看起来非常复杂的效果和功能都能轻易实现,比如圆形头像、不规则图片、橡皮擦、稀奇古怪的自定义进度条等等。

  类 PorterDuffXfermode 提供对位图运算模式的定义与支持, “ProterDuff” 是两个人名组合:Tomas Proter 和 Tom Duff,他们是最早在 SIGGRAPH 上提出图形混合概念的大神级人物。创建 PorterDuffXfermode 对象时,可以提供多达 16 种运算模式。

  位图运算模式定义在PorterDuff类的内部枚举类型Mode中,对应了16个不同的枚举值:

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),
    ADD(12), 
    MULTIPLY(13),
    SCREEN(14),
    OVERLAY(15),
    DARKEN(16),      
    LIGHTEN(17),        
}

  为了实现位图运算,创建PorterDuffXfermode对象后,调用Paint类的setXfermode(Xfermode xfermode)方法,PorterDuffXfermode是Xfermode的子类,将PorterDuffXfermode对象作为实际参数传入即可,如下:

paint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));

图层

  Canvas在一般的情况下可以看作是一张画布,所有的绘图操作如位图、圆、直线等都在这张画布上绘制,Canvas同时还定义了相关属性如Matrix、颜色等等。但是,倘若需要实现一些相对复杂的绘图操作,比如多层动画、地图(地图可以有多个地图层叠加而成,比如:政区层、道路层、兴趣点层)等,需要Canvas供的图层(layer)支持,缺省情况下可以看作只有一个图层 layer。如果需要按层次来绘图, Canvas需要创建一些中间层。layer按照“栈结构”来管理。

  既然是栈结构,自然存在入栈和出栈两种行为。layer入栈时,后续的绘图操作都发生在这个layer上,而layer出栈时,将把本图层绘制的图像“绘制”到上层或是Canvas 上,复制layer到Canvas上时,还可以指定layer的透明度。

  layer可以翻译为图层,Canvas默认的图层称之为“主图层(main layer)”,阴影显示在“阴影图层(shader layer)”中,实际上,我们还能自己创建新的图层并入栈,创建图层通过saveLayer()和saveLayerAlpha()方法,该方法有下面几个重载的版本:

//left、top、right 和 bottom 用于确定图层的位置和大小;
//参数 paint可以指定为 null;
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint)
public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint)

//还可以使用下面的方法为图层指定透明度;
//saveFlags 用于指定保存标识位,虽然也有好几个值可供选择,但官方推荐使用Canvas.ALL_SAVE_FLAG;
//参数 alpha 自然就是指定图层的透明度(0~255);
public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha(@Nullable RectF bounds, int alpha)

  以上这两组方法的返回值为一个 int 值,代表当前入栈的 layer 的 id,通过该 id 能明确是哪一个 layer 出栈。layer从栈中弹出(出栈),需要调用publicvoidrestoreToCount(intsaveCount)方法,该方法的参数就是 saveLayer()或 saveLayerAlpha()的返回值。

位图运算技巧

  要实现位图的混合运算,一方面需要通过 PorterDuffXfermode 指定运算的模式,另一方面还需要借助 layer 进行“离屏缓存”,达到类似 Photoshop 中“遮罩层”的效果。归纳起来,大概有下面几个参考步骤(其实可以有更加简化的步骤):
1 . 准备好分别代表 DST 和 SRC 的位图,同时准备第三个位图,该位图用于绘制 DST 和 SRC 运算后的结果;
2 . 创建大小合适的图层(layer)并入栈;
3 . 先将 DST 位图绘制在第三个位图上;
4 . 调用 Paint 的 setXfermode()方法定义位图运算模式;
5 . 再将 SRC 位图绘制在第三个位图上;
6 . 清除位图运算模式;
7 . 图层(layer)出栈;
8 . 将第三个位图绘制在 View 的 Canvas 上以便显示;