概述:
什么是Xfermode?
通过使用Xfermode将绘制的两个图形(图片)的像素按照一定的规则进行混合,形成新的像素,再更新到Canvas中形成最终的图形(图片)。设置混合规则,paint.setXfermode(Xfermode xfermode)函数来设置,为paint画笔设置一个混合模式。
一、16种混合模式
Xfermode混合规则(或者叫模式)分为16种,如下图所示:
16种模式的含义见官方代码中的注释:
public enum Mode {
// these value must match their native equivalents. See SkXfermode.h
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_CLEAR.png" />
* <figcaption>Destination pixels covered by the source are cleared to 0.</figcaption>
* </p>
* <p>\(\alpha_{out} = 0\)</p>
* <p>\(C_{out} = 0\)</p>
*/
CLEAR (0),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC.png" />
* <figcaption>The source pixels replace the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src}\)</p>
* <p>\(C_{out} = C_{src}\)</p>
*/
SRC (1),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST.png" />
* <figcaption>The source pixels are discarded, leaving the destination intact.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{dst}\)</p>
*/
DST (2),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OVER.png" />
* <figcaption>The source pixels are drawn over the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
SRC_OVER (3),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_OVER.png" />
* <figcaption>The source pixels are drawn behind the destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst} + (1 - \alpha_{dst}) * \alpha_{src}\)</p>
* <p>\(C_{out} = C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
*/
DST_OVER (4),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_IN.png" />
* <figcaption>Keeps the source pixels that cover the destination pixels,
* discards the remaining source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} * \alpha_{dst}\)</p>
*/
SRC_IN (5),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_IN.png" />
* <figcaption>Keeps the destination pixels that cover source pixels,
* discards the remaining source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{dst} * \alpha_{src}\)</p>
*/
DST_IN (6),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_OUT.png" />
* <figcaption>Keeps the source pixels that do not cover destination pixels.
* Discards source pixels that cover destination pixels. Discards all
* destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src}\)</p>
*/
SRC_OUT (7),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_OUT.png" />
* <figcaption>Keeps the destination pixels that are not covered by source pixels.
* Discards destination pixels that are covered by source pixels. Discards all
* source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{src}) * C_{dst}\)</p>
*/
DST_OUT (8),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SRC_ATOP.png" />
* <figcaption>Discards the source pixels that do not cover destination pixels.
* Draws remaining source pixels over destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{dst}\)</p>
* <p>\(C_{out} = \alpha_{dst} * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
SRC_ATOP (9),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DST_ATOP.png" />
* <figcaption>Discards the destination pixels that are not covered by source pixels.
* Draws remaining destination pixels over source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src}\)</p>
* <p>\(C_{out} = \alpha_{src} * C_{dst} + (1 - \alpha_{dst}) * C_{src}\)</p>
*/
DST_ATOP (10),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_XOR.png" />
* <figcaption>Discards the source and destination pixels where source pixels
* cover destination pixels. Draws remaining source pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = (1 - \alpha_{dst}) * \alpha_{src} + (1 - \alpha_{src}) * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst}\)</p>
*/
XOR (11),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_DARKEN.png" />
* <figcaption>Retains the smallest component of the source and
* destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + min(C_{src}, C_{dst})\)</p>
*/
DARKEN (16),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_LIGHTEN.png" />
* <figcaption>Retains the largest component of the source and
* destination pixel.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = (1 - \alpha_{dst}) * C_{src} + (1 - \alpha_{src}) * C_{dst} + max(C_{src}, C_{dst})\)</p>
*/
LIGHTEN (17),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_MULTIPLY.png" />
* <figcaption>Multiplies the source and destination pixels.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} * C_{dst}\)</p>
*/
MULTIPLY (13),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_SCREEN.png" />
* <figcaption>Adds the source and destination pixels, then subtracts the
* source pixels multiplied by the destination.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(C_{out} = C_{src} + C_{dst} - C_{src} * C_{dst}\)</p>
*/
SCREEN (14),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_ADD.png" />
* <figcaption>Adds the source pixels to the destination pixels and saturates
* the result.</figcaption>
* </p>
* <p>\(\alpha_{out} = max(0, min(\alpha_{src} + \alpha_{dst}, 1))\)</p>
* <p>\(C_{out} = max(0, min(C_{src} + C_{dst}, 1))\)</p>
*/
ADD (12),
/**
* <p>
* <img src="{@docRoot}reference/android/images/graphics/composite_OVERLAY.png" />
* <figcaption>Multiplies or screens the source and destination depending on the
* destination color.</figcaption>
* </p>
* <p>\(\alpha_{out} = \alpha_{src} + \alpha_{dst} - \alpha_{src} * \alpha_{dst}\)</p>
* <p>\(\begin{equation}
* C_{out} = \begin{cases} 2 * C_{src} * C_{dst} & 2 * C_{dst} \lt \alpha_{dst} \\
* \alpha_{src} * \alpha_{dst} - 2 (\alpha_{dst} - C_{src}) (\alpha_{src} - C_{dst}) & otherwise \end{cases}
* \end{equation}\)</p>
*/
OVERLAY (15);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
这里根据我自己的理解重新解释了下16种混合模式:
1.CLEAR
Destination pixels covered by the source are cleared to 0
所有图层都不会显示
2.SRC
The source pixels replace the destination pixels.
只显示源图
3. DST
The source pixels are discarded, leaving the destination intact.
只显示目标图,即底层图片
4. SRC_OVER
The source pixels are drawn over the destination pixels
相交的地方,只显示源图,目标图在背后
5. DST_OVER
The source pixels are drawn behind the destination pixels
相交的地方,只显示目标图,源图在背后
6. SRC_IN
Keeps the source pixels that cover the destination pixels,
discards the remaining source and destination pixels.
源图除了相交的地方保留,源图其余部分都不显示。目标图都不显示。
7. DST_IN
Keeps the destination pixels that cover source pixels,
discards the remaining source and destination pixels
和SRC_IN相反,相交的地方保留 目标图,其余不显示。
8.SRC_OUT
Keeps the source pixels that do not cover destination pixels.
Discards source pixels that cover destination pixels. Discards all
destination pixels
源图除了相交的地方不要,源图其余部分都显示。目标图都不显示。
9. DST_OUT
Keeps the destination pixels that are not covered by source pixels.
Discards destination pixels that are covered by source pixels. Discards all
source pixels
与SRC_OUT相反,目标图除了相交的地方不要,目标图其余部分都显示。源图都不显示。
10.SRC_ATOP
Discards the source pixels that do not cover destination pixels.
Draws remaining source pixels over destination pixels
丢弃相交区域外的源图,其余都显示。
11.DST_ATOP
Discards the destination pixels that are not covered by source pixels.
Draws remaining destination pixels over source pixels
只绘制源图未相交的部分 、 目标图相交的区域(绘制目标图)。
12. XOR
Discards the source and destination pixels where source pixels
cover destination pixels. Draws remaining source pixels.
丢弃相交部分
13.DARKEN
Retains the smallest component of the source and destination pixels
相交部分变暗
14.LIGHTEN
Retains the largest component of the source and destination pixel
相交部分变亮
15.MULTIPLY
Multiplies the source and destination pixels
示两个图层的交集,颜色变换类似于PS中的“正片叠底”效果
16.SCREEN
adds the source and destination pixels, then subtracts(减去) the
source pixels multiplied by the destination.
显示两个图层的并集,颜色变换类似于PS中的“滤色”效果
二、实例
本节根据实例代码来理解混合模式。
1. 实例:用SRC_IN实现圆角图片
效果图:
1.1 原理
SRC_IN混合模式概念: 源图除了相交的地方保留,其余部分都不显示,目标图都不显示。
这里要确定两张图片,哪个是源图,哪个是目标图。在SRC_IN模式里。在SRC_IN模式里,源图绘制在目标图之上。
在这里我们准备2张图片,源图为一个女孩的图片,目标图是一个同样大小的圆角图片。这时2张图相交的部分就是 正好是一张女孩的圆角图片(去掉了源图中的四个角)。根据SRC_IN模式的概念,最终画在canvas上的就是一张圆角“源图“,即女孩的圆角图片。以下两张图分别是上述中的源图和目标图:
源图: 目标图:
1.2 代码实现及分析
1.2.1用SRC_IN模式实现圆角头像的代码如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
public class RoundImageView_SRCIN extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC;
public RoundImageView_SRCIN(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.shade,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
}
//gxw: src_in只在源图像和目标图像相交的地方绘制【源图像】
//SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
最后把这个自定义视图类:RoundImageView_SRCIN,放在xml布局里:
<com.xiaowei.paint_xfermode_src.RoundImageView_SRCIN
android:id="@+id/roundsrcin_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
最后在MainActivity里加载这个布局文件,运行会显示一张圆角图片,当然这里图片是写死在RoundImageView_SRCIN里的,大家也可以做成自定义属性配置想要设置圆角图片。运行效果如下图:
1.2.2 代码分析
我们主要分析一下RoundImageView_SRCIN的源码。
setLayerType(View.LAYER_TYPE_SOFTWARE, null);是用来关闭硬件加速器,混合模式Xfermode要有效果,需关闭这个加速器。
onDraw函数里3行代码搞定:
1. 画BmpDST图(是一个圆角图片);
2. 设置paint的混合模式为SRC_IN ;
3 画源图BmpSRC:女孩的图片。
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); 这行是将上述2张图片绘制在一个指定的图层上。cavas的绘制可以分多层来绘制。例如百度地图,越下面的图层地址越详细。绘制完之后,让canvas恢复到原来的状态:
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
2. 实例,使用SRC_ATOP制作圆角图片
效果图:
2.1 原理
SRC_ATOP混合模式的概念:丢弃相交区域外的源图,其余(源图相交部分与目标图未被覆盖的部分)都显示。
这里和实例1一样,还是准备那2张图片,1张源图(女孩的图片),1张大小相同的目标图(圆角图片),源图覆盖在目标图之上。
使用SRC_ATOP混合模式,将丢弃相交区域外的源图 ,其余(源图相交部分与目标图未被覆盖的部分)都显示,刚好显示的是圆角女孩(源图相交部分)与 目标图标图未被覆盖的部分(不存在,因为都被源图覆盖了),最终只显示的就是 1张圆角女孩。
2.2. 代码实现及分析
2.1.1代码实现如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
import com.xiaowei.paint_xfermode_src.R;
/**
* 取下层非交集部分与上层交集部分,和SRC_IN效果差不多,只是比SRC_IN饱和度增加,更亮一些
* 上层就是SRC,下层是DST
*/
public class RoundImageView_SRCATOP extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC;
public RoundImageView_SRCATOP(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.shade,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
2.2.2 代码分析:
同实例1 :SRC_IN实现圆角的代码,只有这行代码不同:
BitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
即设置的Xfermode不一样,其余都相同。
3. 实例3用SRC_OUT实现橡皮擦
效果:
3.1 原理
SRC_OUT模式的概念:目标图与源图相交的地方不显示,目标图其余部分也不显示。但是,源图未相交的其余部分都显示。
这里我们要确定2张图片,谁是源图,谁是目标图。在这里擦除的手势轨迹就是目标图片,源图还是原来的女孩图片,源图覆盖在目标图之上,那么根据SRC_OUT模式的概念,凡是手指划过的地方就是目标图与源图相交的地方,都不显示,目标图其余部分也不显示。但是,源图未相交的其余部分都显示,这样就实现了橡皮擦效果。
3.2 代码及分析:
3.2.1代码实现如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* SRC_OUT [Sa * (1 - Da), Sc * (1 - Da)] --- 同SRC_IN类似 (1 - Da)
* 用我们目标图片的透明度的补值来改变源图片的透明度和饱和度,当目标图片的透明度为不透明时,源图片就不会显示
* 示例:橡皮擦效果
*
* 解释2: 取上层(SRC图)绘制非交集部分。
*/
public class EraserView_SRCOUT extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC;
private Path mPath;
private float mPreX,mPreY;
public EraserView_SRCOUT(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先把手指轨迹画到目标Bitmap上
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath,mBitPaint);
//然后把目标图像画到画布上
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
//计算源图像区域
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(),event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (mPreX+event.getX())/2;
float endY = (mPreY+event.getY())/2;
mPath.quadTo(mPreX,mPreY,endX,endY);
mPreX = event.getX();
mPreY =event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
postInvalidate();
return super.onTouchEvent(event);
}
}
3.2.2 代码分析:
1. 手势轨迹如何形成一张目标图片呢?
我们可以这么做:
(1)先创建一个空位图BmpDST
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
(2)然后绘制手势轨迹到BmpDST上,形成目标图。将来参与混合像素的就是手指划出的这些轨迹像素,因为空位图没有颜色没有透明度,到时候只有这些轨迹才是真实的像素。绘制目标图的核心代码如下:
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath,mBitPaint);
其中mPath就是手指划过的路径。mPath = new Path();然后改变mPath是在onTouchEvent事件里。 mPath.moveTo(event.getX(),event.getY());是手势轨迹的起点。
mPath.quadTo(mPreX,mPreY,endX,endY);贝塞尔曲线,让手势滑动形成平滑的轨迹。
2. 使用SRC_OUT模式混合源图与目标图
绘制出了“有轨迹”的目标图后,下来的操作就和我们实例1和2一样了,只是。
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
实例4,使用SRC_IN实现倒影
原理:准备一张目标图片:透明度由不透明到透明渐变。准备一张原图(要为它绘制倒影的图片),并生成一张倒装的图片作为混合模式中的源图片。
step1: 先画原图,再用SRC_IN模式将倒装的源图片与 目标图合并。SRC_IN源图的透明度将随目标图的透明度正比变化,也就是说目标图片透明度由不透明到渐变
到透明,那么这张倒装的源图也将从不透明到渐变到透明。
代码如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
public class InvertedImageView_SRCIN extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpRevert;
public InvertedImageView_SRCIN(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.invert_shade,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
Matrix matrix = new Matrix();
matrix.setScale(1F, -1F);
// 生成倒影图
BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true);
}
/**
*
SRC_IN [Sa * Da, Sc * Da] ---- 处理图片相交区域时,受到目标图片的Alpha值影响
当我们的目标图片为空白像素的时候,源图片也会变成空白
简单的来说就是用目标图片的透明度来改变源图片的透明度和饱和度,当目标图片的透明度为0时,源图片就不会显示
示例:圆角头像 、倒影图片
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先画出正图
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
//再画出倒影图
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.translate(0,BmpSRC.getHeight());
canvas.drawBitmap(BmpDST,0,0,mBitPaint);//目标图片是一个透明渐变图片
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(BmpRevert,0,0,mBitPaint); //倒装的图片作为源图
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
实例5.使用SRC_ATOP实现倒影
丢弃相交区域外的源图,其余都显示。
SRC_ATOP [Da, Sc * Da + (1 - Sa) * Dc]
---- 当透明度为100%和0%时,SRC_IN 和 SRC_ATOP是通用的
当透明度不为上述的两个值时,SRC_ATOP 比 SRC_IN 源图像的饱和度会增加,变得更亮一些
原来和实例4一样,只是显示出的 倒影图片,饱和度更高些,更亮些。
源代码如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
public class InvertedImageView_SRCATOP extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpRevert;
public InvertedImageView_SRCATOP(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.invert_shade,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null);
Matrix matrix = new Matrix();
matrix.setScale(1F, -1F);
// 生成倒影图
BmpRevert = Bitmap.createBitmap(BmpSRC, 0, 0, BmpSRC.getWidth(), BmpSRC.getHeight(), matrix, true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先画出小狗图片
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
//再画出倒影
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.translate(0,BmpSRC.getHeight());
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
canvas.drawBitmap(BmpRevert,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
实例6:实现刮刮卡效果SRC_OUT
这个和橡皮擦一样,只是比橡皮擦多画了一个文字。代码如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.xiaowei.paint_xfermode_src.R;
public class GuaGuaCardView_SRCOUT extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpText;
private Path mPath;
private float mPreX,mPreY;
public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpText = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka_text1,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BmpText,0,0,mBitPaint);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先把手指轨迹画到目标Bitmap上
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath,mBitPaint);
//然后把目标图像画到画布上
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
//计算源图像区域
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(),event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (mPreX+event.getX())/2;
float endY = (mPreY+event.getY())/2;
mPath.quadTo(mPreX,mPreY,endX,endY);
mPreX = event.getX();
mPreY =event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
postInvalidate();
return super.onTouchEvent(event);
}
}
实例6:实现刮刮卡效果SRC_OUT
这个和橡皮擦一样,只是比橡皮擦多画了一个文字。代码如下:
package com.xiaowei.paint_xfermode_src;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.xiaowei.paint_xfermode_src.R;
public class GuaGuaCardView_SRCOUT extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpText;
private Path mPath;
private float mPreX,mPreY;
public GuaGuaCardView_SRCOUT(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
mBitPaint.setColor(Color.RED);
mBitPaint.setStyle(Paint.Style.STROKE);
mBitPaint.setStrokeWidth(45);
BmpText = BitmapFactory.decodeResource(getResources(), R.drawable.guaguaka_text1,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.guaguaka,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
mPath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BmpText,0,0,mBitPaint);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先把手指轨迹画到目标Bitmap上
Canvas c = new Canvas(BmpDST);
c.drawPath(mPath,mBitPaint);
//然后把目标图像画到画布上
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
//计算源图像区域
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(),event.getY());
mPreX = event.getX();
mPreY = event.getY();
return true;
case MotionEvent.ACTION_MOVE:
float endX = (mPreX+event.getX())/2;
float endY = (mPreY+event.getY())/2;
mPath.quadTo(mPreX,mPreY,endX,endY);
mPreX = event.getX();
mPreY =event.getY();
break;
case MotionEvent.ACTION_UP:
break;
}
postInvalidate();
return super.onTouchEvent(event);
}
}
实例7. DST_IN模式,实现圆角
和SRC_IN相反,DST_IN是保留相交处的目标图像。而SRC_IN保留相交处的源图像,因此这里我们把 SRC_IN中的圆角透明图片作为 源图,把要制作圆角的图片作为目标图。
代码如下,你可以对比一下SRC_IN实现圆角的代码。
package com.xiaowei.paint_xfermode_dst;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
public class RoundImageView_DSTIN extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC;
public RoundImageView_DSTIN(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.xyjy6,null); //目标图换作我们要制作圆角的图片
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.shade,null);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
实例8: DST_IN实现倒影
和SRC_IN实现倒影相反,DST_IN是保留相交处的目标图像。而SRC_IN保留相交处的源图像,因此这里我们把 SRC_IN中的透明渐变作为 源图,把倒影图片作为目标图。
代码如下,你可以对比一下SRC_IN实现倒影的代码。
代码:
package com.xiaowei.lsn5_paint_xfermode_dst;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
public class InvertedImageView_DSTIN extends View {
private Paint mBitPaint;
private Bitmap BmpDST,BmpSRC,BmpRevert;
public InvertedImageView_DSTIN(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mBitPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.xyjy6,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.invert_shade,null);
Matrix matrix = new Matrix();
matrix.setScale(1F, -1F);
// 生成倒影图
BmpRevert = Bitmap.createBitmap(BmpDST, 0, 0, BmpDST.getWidth(), BmpDST.getHeight(), matrix, true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先画出小狗图片
canvas.drawBitmap(BmpDST,0,0,mBitPaint);
//再画出倒影
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.translate(0,BmpSRC.getHeight());
canvas.drawBitmap(BmpRevert,0,0,mBitPaint);
mBitPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mBitPaint);
mBitPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
}
实例9,使用 DST_IN 实现不规则WAVE
package com.xiaowei.lsn5_paint_xfermode_dst;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
public class IrregularWaveView_DSTIN extends View {
private Paint mPaint;
private int mItemWaveLength = 0;
private int dx=0;
private Bitmap BmpSRC,BmpDST;
public IrregularWaveView_DSTIN(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null);
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.circle_shape,null);
mItemWaveLength = BmpDST.getWidth();
startAnim();
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//先画上圆形
canvas.drawBitmap(BmpSRC,0,0,mPaint);
//再画上结果
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mPaint); //之所以要和BmpDST相交,是为了把BmpDST的范围截取在圆形里面。
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
public void startAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
animator.setDuration(4000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
}
1. 先绘制出那个圆形图片;
2. 另起一层,绘制圆形和波浪图片的 混合像素。使用DST_IN,所以最终只保留波浪。最终的效果是 波浪叠在1中的圆形图片上。
3. 然后ValueAnimator动画,获取4秒期间,某一时刻,在【0到mItemWaveLength】中的中间值dx。用dx来移动BmpDST,移动函数如下:
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
实例10,使用 DST_IN 实现心电图
原理:目标图是一个心电图图片,源图是一个Bitmap上的一个不透明矩形,这个矩形的宽度刚开始很小BmpDST.getWidth() - dx---BmpDST.getWidth(),也就是宽度为dx,
且这个小矩形的位置在 心电图的最右边,遮盖在心点图图片之上,这个dx通过valueAnimator不断增加,小矩形的宽度不断增加,小矩形就慢慢的往左扩充,由于使用的是DST_IN模式,因此凡是与小矩形相交的
部分,心电图这个目标图就会逐步显示出来,随着小矩形往左扩充的越多,就显示出更多的部分心电图。
package com.xiaowei.lsn5_paint_xfermode_dst;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
/**
*
*/
public class HeartMap_DSTIN extends View {
private Paint mPaint;
private int mItemWaveLength = 0;
private int dx=0;
private Bitmap BmpSRC,BmpDST;
public HeartMap_DSTIN(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.RED);
BmpDST = BitmapFactory.decodeResource(getResources(), R.drawable.heartmap,null);
BmpSRC = Bitmap.createBitmap(BmpDST.getWidth(), BmpDST.getHeight(), Bitmap.Config.ARGB_8888);
mItemWaveLength = BmpDST.getWidth();
startAnim();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Canvas c = new Canvas(BmpSRC);
//清空bitmap
c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
//画上矩形
c.drawRect(BmpDST.getWidth() - dx,0,BmpDST.getWidth(),BmpDST.getHeight(),mPaint);
//模式合成
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
public void startAnim(){
ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
animator.setDuration(6000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
dx = (int)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
}