貝塞爾曲線
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;
}
}
}