贝塞尔曲线
PorterDuff及Xfermode
PorterDuff、Xfermode类主要用于图形合成时的图像过渡模式计算,其概念来自于1984年在ACM SIGGRAPH计算机图形学出版物上发表了“Compositing digital images(合成数字图像)”的Tomas Porter和Tom Duff,合成图像的概念极大地推动了图形图像学的发展,PorterDuffXfermode类名就来源于这俩人的名字组合PorterDuff。
PorterDuff

PorterDuff的16种模式图:![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线
Enum Values | |||
---|---|---|---|
PorterDuff.Mode | ADD | Saturate(S + D) | |
PorterDuff.Mode | CLEAR | [0, 0] | 所绘制不会提交到画布上 |
PorterDuff.Mode | DARKEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] | 取两图层全部区域,交集部分颜色加深 |
PorterDuff.Mode | DST | [Da, Dc] | 显示下层绘制图片 |
PorterDuff.Mode | DST_ATOP | [Sa, Sa * Dc + Sc * (1 - Da)] | 取上层非交集部分与下层交集部分 |
PorterDuff.Mode | DST_IN | [Sa * Da, Sa * Dc] | 取两层绘制交集。显示下层 |
PorterDuff.Mode | DST_OUT | [Da * (1 - Sa), Dc * (1 - Sa)] | 取下层绘制非交集部分 |
PorterDuff.Mode | DST_OVER | [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] | 上下层都显示。下层居上显示 |
PorterDuff.Mode | LIGHTEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] | 取两图层全部,点亮交集部分颜色 |
PorterDuff.Mode | MULTIPLY | [Sa * Da, Sc * Dc] | 取两图层交集部分叠加后颜色 |
PorterDuff.Mode | OVERLAY | ||
PorterDuff.Mode | SCREEN | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] | 取两图层全部区域,交集部分变为透明色 |
PorterDuff.Mode | SRC | [Sa, Sc] | 显示上层绘制图片 |
PorterDuff.Mode | SRC_ATOP | [Da, Sc * Da + (1 - Sa) * Dc] | 取下层非交集部分与上层交集部分 |
PorterDuff.Mode | SRC_IN | [Sa * Da, Sc * Da] | 取两层绘制交集。显示上层 |
PorterDuff.Mode | SRC_OUT | [Sa * (1 - Da), Sc * (1 - Da)] | 取上层绘制非交集部分 |
PorterDuff.Mode | SRC_OVER | [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] | 正常绘制显示,上下层绘制叠盖 |
PorterDuff.Mode | XOR | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] | 异或:去除两图层交集部分 |
Xfermode
Xfermode有三个子类 :前两个类在API 16被遗弃了,而且不是本文的主题内容
class 说明 AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。 PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素异或操作。 PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
要应用转换模式,可以使用setXferMode方法,如下所示:
AvoidXfermode avoid = new AvoidXfermode(Color.BLUE, , AvoidXfermode.Mode. AVOID);
paint.setXfermode(avoid);
举个栗子
Thanks To http://www.jianshu.com/p/d11892bbe055
当Alpha通道的值为1时,图像完全可见;当Alpha通道值为0时,图像完全不可见;当Alpha通道的值介于0和1之间时,图像只有一部分可见。Alpha通道描述的是图像的形状,而不是透明度。
以SCREEN的计算方式为例:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],“[……]”里分为两部分,其中“,”前的部分“Sa + Da - Sa * Da”计算的值代表SCREEN模式的Alpha通道,而“,”后的部分“Sc + Dc - Sc * Dc”计算SCREEN模式的颜色值,图形混合后的图片依靠这个矢量来计算ARGB的值。关于图像Alpha合成的知识详见维基百科Alpha compositing。
Demo
public class PorterDuffXfermodeView extends View {
private Paint mPaint;
private Bitmap dstBmp, srcBmp;
private RectF dstRect, srcRect;
private Xfermode mXfermode;
private PorterDuff.Mode mPorterDuffMode = PorterDuff.Mode.MULTIPLY;
public PorterDuffXfermodeView(Context context) {
super(context);
init();
}
public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
dstBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.destination);
srcBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.source);
mXfermode = new PorterDuffXfermode(mPorterDuffMode);
}
public void setPorterDuffMode(int mode) {
mPorterDuffMode = intToMode(mode);
mXfermode = new PorterDuffXfermode(mPorterDuffMode);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//背景色设为白色,方便比较效果
canvas.drawColor(Color.WHITE);
//将绘制操作保存到新的图层,因为图像合成是很昂贵的操作,将用到硬件加速,这里将图像合成的处理放到离屏缓存中进行
int saveCount = canvas.saveLayer(srcRect, mPaint, Canvas.ALL_SAVE_FLAG);
//绘制目标图
canvas.drawBitmap(dstBmp, null, dstRect, mPaint);
//设置混合模式
mPaint.setXfermode(mXfermode);
//绘制源图
canvas.drawBitmap(srcBmp, null, srcRect, mPaint);
//清除混合模式
mPaint.setXfermode(null);
//还原画布
canvas.restoreToCount(saveCount);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int width = w <= h ? w : h;
int centerX = w / ;
int centerY = h / ;
int quarterWidth = width / ;
srcRect = new RectF(centerX - quarterWidth, centerY - quarterWidth, centerX + quarterWidth, centerY + quarterWidth);
dstRect = new RectF(centerX - quarterWidth, centerY - quarterWidth, centerX + quarterWidth, centerY + quarterWidth);
}
public static final PorterDuff.Mode intToMode(int val) {
switch (val) {
default:
case : return PorterDuff.Mode.CLEAR;
case : return PorterDuff.Mode.SRC;
case : return PorterDuff.Mode.DST;
case : return PorterDuff.Mode.SRC_OVER;
case : return PorterDuff.Mode.DST_OVER;
case : return PorterDuff.Mode.SRC_IN;
case : return PorterDuff.Mode.DST_IN;
case : return PorterDuff.Mode.SRC_OUT;
case : return PorterDuff.Mode.DST_OUT;
case : return PorterDuff.Mode.SRC_ATOP;
case : return PorterDuff.Mode.DST_ATOP;
case : return PorterDuff.Mode.XOR;
case : return PorterDuff.Mode.DARKEN;
case : return PorterDuff.Mode.LIGHTEN;
case : return PorterDuff.Mode.MULTIPLY;
case : return PorterDuff.Mode.SCREEN;
case : return PorterDuff.Mode.ADD;
case : return PorterDuff.Mode.OVERLAY;
}
}
}
其中用到的图片资源分别如下:
destination.png
source.png
CLEAR
清除模式,[0, 0],即图像中所有像素点的alpha和颜色值均为0,Demo中的实际效果就是白色背景,图略。
SRC
[Sa, Sc],只保留源图像的 alpha 和 color ,所以绘制出来只有源图,如source。有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图。
DST
[Da, Dc],只保留了目标图像的alpha和color值,所以绘制出来的只有目标图,如destination。
SRC_OVER
[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc],在目标图像上层绘制源图像
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 DST_OVER
[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],与SRC_OVER相反,此模式是目标图像被绘制在源图像的上方
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 SRC_IN
[Sa * Da, Sc * Da],在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 DST_IN
[Sa * Da, Sa * Dc],可以和SRC_IN 进行类比,在两者相交的地方绘制目标图像,并且绘制的效果会受到源图像对应地方透明度的影响
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 SRC_OUT
[Sa * (1 - Da), Sc * (1 - Da)],从字面上可以理解为在不相交的地方绘制源图像,那么我们来看看效果是不是这样,如下图。实际上color 是 Sc * ( 1 - Da ) ,表示如果相交处的目标色的alpha是完全不透明的,这时候源图像会完全被过滤掉,否则会受到相交处目标色 alpha 影响,呈现出对应色值。
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 DST_OUT
[Da * (1 - Sa), Dc * (1 - Sa)],可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 SRC_ATOP
[Da, Sc * Da + (1 - Sa) * Dc],源图像和目标图像相交处绘制源图像,不相交的地方绘制目标图像,并且相交处的效果会受到源图像和目标图像alpha的影响
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 DST_ATOP
[Sa, Sa * Dc + Sc * (1 - Da)],源图像和目标图像相交处绘制目标图像,不相交的地方绘制源图像,并且相交处的效果会受到源图像和目标图像alpha的影响
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 XOR
[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc],在不相交的地方按原样绘制源图像和目标图像,相交的地方受到对应alpha和颜色值影响,按公式进行计算,如果都完全不透明则相交处完全不绘制
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 DARKEN
[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)],该模式处理过后,会感觉效果变暗,即进行对应像素的比较,取较暗值,如果色值相同则进行混合;
从算法上看,alpha值变大,色值上如果都不透明则取较暗值,非完全不透明情况下使用上面算法进行计算,受到源图和目标图对应色值和alpha值影响。
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 LIGHTEN
[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],可以和 DARKEN 对比起来看,DARKEN 的目的是变暗,LIGHTEN 的目的则是变亮,如果在均完全不透明的情况下,色值取源色值和目标色值中的较大值,否则按上面算法进行计算。
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 MULTIPLY
[Sa * Da, Sc * Dc],正片叠底,即查看每个通道中的颜色信息,并将基色与混合色复合。结果色总是较暗的颜色,任何颜色与黑色复合产生黑色,任何颜色与白色复合保持不变,当用黑色或白色以外的颜色绘画时,绘画工具绘制的连续描边产生逐渐变暗的颜色。
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 SCREEN
[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],滤色,滤色模式与我们所用的显示屏原理相同,所以也有版本把它翻译成屏幕;简单的说就是保留两个图层中较白的部分,较暗的部分被遮盖;当一层使用了滤色(屏幕)模式时,图层中纯黑的部分变成完全透明,纯白部分完全不透明,其他的颜色根据颜色级别产生半透明的效果。
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 ADD
Saturate(S + D),饱和度叠加
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线 OVERLAY
像素是进行 Multiply (正片叠底)混合还是 Screen (屏幕)混合,取决于底层颜色,但底层颜色的高光与阴影部分的亮度细节会被保留
![]()
PorterDuff及Xfermode 贝塞尔曲线贝塞尔曲线
水波纹进度条
Thanks to http://mp.weixin.qq.com/s/GvgcGXhQwnPIimqqkVBOUw?ref=myread
这些只能够显示一些合成的效果,要想实现水波纹的效果还是不够的,我们还需要借助于贝塞尔曲线来实现水波效果。我们使用到的是Path类的quadTo(x1, y1, x2, y2)方法,属于二阶贝塞尔曲线,使用一张图来展示二阶贝塞尔曲线,这里的(x1,y1)是控制点,(x2,y2)是终止点,起始点默认是Path的起始点(0,0)。关于使用贝塞尔曲线来实现水波效果的原理就是:通过for循环画出两个波纹,我们以WL代表水波纹的长度。需要波纹的-WL点、-3/4*WL点、-1/2*WL、-1/4*WL四个点,通过path的quadTo画出,并无限重复。
水波纹
public class WaveView extends View {
private int width;
private int height;
private Path mPath;
private Paint mPathPaint;
private float mWaveHight = f;//水波纹的高度
private float mWaveWidth = f;//水波纹的宽度
private String mWaveColor = "#FFFFFF";//水波颜色
private int mWaveSpeed = ;
private Paint mTextPaint;
private String currentText = "";
private String mTextColor = "#000000";
private int mTextSize = ;
private int maxProgress = ;
private int currentProgress = ;
private float currentY;
private float distance = ;
private int RefreshGap = ;
private boolean mIsStop = false;
private static final int INVALIDATE = ;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mIsStop) {
return;
}
switch (msg.what) {
case INVALIDATE:
invalidate();
sendEmptyMessageDelayed(INVALIDATE, RefreshGap);
break;
}
}
};
public WaveView(Context context) {
this(context, null, );
}
public WaveView(Context context, AttributeSet attrs) {
this(context, attrs, );
}
public WaveView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setCurrent(int currentProgress, String currentText) {
this.currentProgress = currentProgress;
this.currentText = currentText;
}
public void setWaveColor(String mWaveColor) {
this.mWaveColor = mWaveColor;
}
private void init() {
mPath = new Path();
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setStyle(Paint.Style.FILL);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
handler.sendEmptyMessageDelayed(INVALIDATE, );
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
currentY = height;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(createImage(), , , null);
}
private Bitmap createImage() {
mPathPaint.setColor(Color.parseColor(mWaveColor));
mTextPaint.setColor(Color.parseColor(mTextColor));
mTextPaint.setTextSize(mTextSize);
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
float currentMidY = height * (maxProgress - currentProgress) / maxProgress;
if (currentY > currentMidY) {
currentY = currentY - (currentY - currentMidY) / ;
}
mPath.reset();
if (maxProgress > currentProgress) {
//之所以0-distance是因为有原点向上增加的
mPath.moveTo( - distance, currentY);
//显示的区域内的水波纹的数量
int waveNum = width / ((int) mWaveWidth);
int num = ;
for (int i = ; i < waveNum; i++) {
mPath.quadTo(mWaveWidth * (num + ) - distance, currentY - mWaveHight, mWaveWidth * (num + ) - distance, currentY);
mPath.quadTo(mWaveWidth * (num + ) - distance, currentY + mWaveHight, mWaveWidth * (num + ) - distance, currentY);
num += ;
}
distance += mWaveWidth / mWaveSpeed;
distance = distance % (mWaveWidth * );
mPath.lineTo(width, height);
mPath.lineTo(, height);
} else {
mIsStop = true;
mPath.moveTo(, );
mPath.lineTo(, height);
mPath.lineTo(width, height);
mPath.lineTo(width, );
}
mPath.close();
canvas.drawPath(mPath, mPathPaint);
canvas.drawText(currentText, width / , height / , mTextPaint);
return bmp;
}
}
图片水波纹
public class WaveProgressView extends View {
private int width;
private int height;
private Bitmap backgroundBitmap;
private Path mPath;
private Paint mPathPaint;
private float mWaveHight = f;
private float mWaveWidth = f;
private String mWaveColor = "#FFFFFF";
private int mWaveSpeed = ;
private Paint mTextPaint;
private String currentText = "";
private String mTextColor = "#FFFFFF";
private int mTextSize = ;
private int maxProgress = ;
private int currentProgress = ;
private float currentY;
private float distance = ;
private int RefreshGap = ;
private static final int INVALIDATE = ;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case INVALIDATE:
invalidate();
sendEmptyMessageDelayed(INVALIDATE, RefreshGap);
break;
}
}
};
public WaveProgressView(Context context) {
this(context, null, );
}
public WaveProgressView(Context context, AttributeSet attrs) {
this(context, attrs, );
}
public WaveProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setCurrent(int currentProgress, String currentText) {
this.currentProgress = currentProgress;
this.currentText = currentText;
}
public void setWaveColor(String mWaveColor) {
this.mWaveColor = mWaveColor;
}
private void init() {
backgroundBitmap = getBitmapFromDrawable(getBackground());
mPath = new Path();
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setStyle(Paint.Style.FILL);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Paint.Align.CENTER);
handler.sendEmptyMessageDelayed(INVALIDATE, );
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
currentY = height = MeasureSpec.getSize(heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
if (backgroundBitmap != null) {
canvas.drawBitmap(createImage(), , , null);
}
}
private Bitmap createImage() {
mPathPaint.setColor(Color.parseColor(mWaveColor));
mTextPaint.setColor(Color.parseColor(mTextColor));
mTextPaint.setTextSize(mTextSize);
mPathPaint.setColor(Color.parseColor(mWaveColor));
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
float currentMidY = height * (maxProgress - currentProgress) / maxProgress;
if (currentY > currentMidY) {
currentY = currentY - (currentY - currentMidY) / ;
}
mPath.reset();
//之所以0-distance是因为有原点向上增加的
mPath.moveTo( - distance, currentY);
//显示的区域内的水波纹的数量
int waveNum = width / ((int) mWaveWidth);
int num = ;
for (int i = ; i < waveNum; i++) {
mPath.quadTo(mWaveWidth * (num + ) - distance, currentY - mWaveHight, mWaveWidth * (num + ) - distance, currentY);
mPath.quadTo(mWaveWidth * (num + ) - distance, currentY + mWaveHight, mWaveWidth * (num + ) - distance, currentY);
num += ;
}
distance += mWaveWidth / mWaveSpeed;
distance = distance % (mWaveWidth * );
mPath.lineTo(width, height);
mPath.lineTo(, height);
mPath.close();
canvas.drawPath(mPath, mPathPaint);
backgroundBitmap = Bitmap.createScaledBitmap(backgroundBitmap, width, height, true);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
canvas.drawBitmap(backgroundBitmap, , , paint);
canvas.drawText(currentText, width / , height / , mTextPaint);
return bmp;
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
drawable = getResources().getDrawable(R.mipmap.ic_launcher);
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(, , canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
}