Android View之Xfermode

(一) Xfermode 概述

Xfermode图层混合模式:主要用于处理图形图像的绘制。最常用也是最核心的就是其子类PorterDuffXfermode,而PorterDuffXfermode使用的是 PorterDuff 模式创建一个图层混合模式,PorterDuff用于(2D)数字图像的合成。

(二) PorterDuff.Mode 枚举

PorterDuff.Mode 定义了18种混合模式:

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

其中: Sa 代表source alpha,Da 代表 Destination alpha ,Sc 代表 source color,Dc 代表 Destination color。

1、 SRC       只保留源图像的 alpha 和 color
2、 DST       只保留目标图像的 alpha 和 color

3、 SRC_OVER  在目标图片顶部绘制源图像
4、 DST_OVER  将目标图像绘制在上方

5、 SRC_IN    在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响;
6、 DST_IN    在两者相交的地方绘制目标图像,并且绘制的效果会受到源图像对应地方透明度的影响;

7、 SRC_OUT   在不相交的地方绘制源图像,相交处根据目标alpha进行过滤,目标色完全不透明时则完全过滤,完全透明则不过滤;
8、 DST_OUT   在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤;

9、 SRC_ATOP  源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响;
10、DST_ATOP  源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像,并且相交处的效果会受到源图像和目标图像alpha的影响;

11、 XOR      在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和色值影响,按上面公式进行计算,如果都完全不透明则相交处完全不绘制;
12、 DARKEN   该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;
13、 LIGHTEN  如果在均完全不透明的情况下 ,色值取源色值和目标色值中的较大值,否则按上面算法进行计算;

14、 MULTIPLY 正片叠底,即查看每个通道中的颜色信息,并将基色与混合色复合。结果色总是较暗的颜色。
15、 SCREEN   滤色,滤色模式与我们所用的显示屏原理相同。

16、 ADD     饱和度叠加
17、 OVERLAY 像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留;

18、 CLEAR   所有点的像素的alpha 和color 都为 0。

图像合成效果图

图像合成效果图

(三) PorterDuffXfermode 实例

1、基础效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
- Xfermode 图层混合模式的使用
*
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class PorterDuffXfermodeView extends View {

private Paint mPaint;

// 图层混合模式
private Xfermode mXfermode;
// 控件大小
private int mWidth;
private int mHeight;

private Bitmap mSourceBitmap;
private Bitmap mDestBitmap;

private Rect mBottomSrcRect;
private Rect mBottomDestRect;

private Rect mTopSrcRect;
private Rect mTopDestRect;

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

public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);

mSourceBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_src);
mDestBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_dest);

mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
mBottomSrcRect = new Rect(0, 0, mDestBitmap.getWidth(), mDestBitmap.getHeight());
mBottomDestRect = new Rect(0, 0, mWidth, mHeight / 2);

mTopSrcRect = new Rect(0, 0, mSourceBitmap.getWidth(), mSourceBitmap.getHeight());
mTopDestRect = new Rect(0, 0, mWidth, mHeight);
}

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

// 这里会生成了一个全新的bitmap,之后所有的绘图操作都是在这个bitmap上进行的
// 目的是区分哪一步的图形,应该与合成模式的bitmap去合成 运算;
int saveCount = canvas.saveLayer(0, 0, mWidth, mHeight, mPaint, Canvas.ALL_SAVE_FLAG);

// 注意:先绘制目标图(底部)
canvas.drawBitmap(mDestBitmap, mBottomSrcRect, mBottomDestRect, mPaint);
// 设置转换模式
mPaint.setXfermode(mXfermode);
// 绘制源图 (顶部)
canvas.drawBitmap(mSourceBitmap, mTopSrcRect, mTopDestRect, mPaint);

// 清除转换模式
mPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}

}

2、进度上色效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* Xfermode 进度上色效果
*
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class XfermodeLoadingColorView extends View {
private Paint mPaint;
// 控件大小
private int mWidth;
private int mHeight;

private Xfermode mXfermode;

// 目标图
private Bitmap mDestBitmap;
private Rect mSrcRect;
private Rect mDestRect;

// 源图
private Rect mDynamicRect;
// 起点
private int mSrcStart;
// 终点
private int mSrcEnd;

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

public XfermodeLoadingColorView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);// 去锯齿
mPaint.setDither(true);// 防抖动
mPaint.setFilterBitmap(true);
mPaint.setColor(Color.RED);

// 初始化Bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;// 缩小2倍
mDestBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_xfermode_dest, options);
mSrcRect = new Rect(0, 0, mDestBitmap.getWidth(), mDestBitmap.getHeight());
mDestRect = new Rect();
mDynamicRect = new Rect();

mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;

mDestRect.set((w - mDestBitmap.getWidth()) / 2, (h - mDestBitmap.getHeight()) / 2, (w + mDestBitmap.getWidth()) / 2, (h + mDestBitmap.getHeight()) / 2);
mDynamicRect.set((w - mDestBitmap.getWidth()) / 2, (h + mDestBitmap.getHeight()) / 2, (w + mDestBitmap.getWidth()) / 2, (h + mDestBitmap.getHeight()) / 2);
mSrcStart = (h + mDestBitmap.getHeight()) / 2;
mSrcEnd = (h - mDestBitmap.getHeight()) / 2;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saveCount = canvas.saveLayer(0, 0, mWidth, mHeight, mPaint, Canvas.ALL_SAVE_FLAG);

// 先绘制目标图
canvas.drawBitmap(mDestBitmap, mSrcRect, mDestRect, mPaint);
// 设置混合模式
mPaint.setXfermode(mXfermode);
// 绘制源图
canvas.drawRect(mDynamicRect, mPaint);

// 清除混合模式
mPaint.setXfermode(null);
canvas.restoreToCount(saveCount);
}

public void startAnimation() {
ValueAnimator animator = ValueAnimator.ofInt(mSrcStart, mSrcEnd);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(-1);
animator.setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mDynamicRect.top = (int) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}

}

3、橡皮檫效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* Xfermode 橡皮擦效果
*
* @author [*昨日重现*] lhy_ycu@163.com
* @since version 1.0
*/
public class EraserView extends View {
private Paint mPaint;

// 背景图
private Bitmap mBgBitmap;
// 前景图(目标图)
private Bitmap mDestBitmap;
// 手指轨迹(源图)
private Path mSrcPath;
// 轨迹画布
private Canvas mTrackCanvas;

// 记录最后一个点的坐标
private float mLastX;
private float mLastY;
// 最小移动距离
private float mMinMoveDst;

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

public EraserView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);// 防抖动
mPaint.setAlpha(0);// 让绘制的路径是透明的
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(DensityUtils.dip2px(context, 10));
mPaint.setStrokeJoin(Paint.Join.ROUND);// 拐角
mPaint.setStrokeCap(Paint.Cap.ROUND);// 落笔
mSrcPath = new Path();

int[] screenSize = ScreenUtil.getScreenSize(context);
mMinMoveDst = DensityUtils.dip2px(context, 5);
mBgBitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.img_bg);
mBgBitmap = Bitmap.createScaledBitmap(mBgBitmap, screenSize[0], screenSize[1], true);

// 要擦除的前景图(目标图)
mDestBitmap = Bitmap.createBitmap(mBgBitmap.getWidth(), mBgBitmap.getHeight(), Bitmap.Config.ARGB_8888);
// 将手指轨迹关联到目标画布上
mTrackCanvas = new Canvas(mDestBitmap);
// 绘制画布背景为中性灰
mTrackCanvas.drawColor(Color.LTGRAY);

// 在两者相交的地方绘制目标图像
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
}

@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mSrcPath.reset();
mSrcPath.moveTo(x, y);
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(x - mLastX);
float dy = Math.abs(y - mLastY);
if (dx > mMinMoveDst || dy > mMinMoveDst) {
mSrcPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
mLastX = x;
mLastY = y;
}
break;
}
invalidate();
return true;
}

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

canvas.drawBitmap(mBgBitmap, 0, 0, null);
canvas.drawBitmap(mDestBitmap, 0, 0, null);

// 绘制手指轨迹(源图)
mTrackCanvas.drawPath(mSrcPath, mPaint);
}

/**
* 重置
*/
public void reset() {
mTrackCanvas.drawColor(Color.LTGRAY);
mSrcPath.reset();
invalidate();
}

}

Hawky wechat
欢迎订阅我的微信公众号
坚持原创技术分享,您的支持将鼓励我继续创作!

分享