前要:此篇主要以Android舉例博敬,iOS可以參考CGBlendMode
Canvas
繪制四要素:
1個Bitmap用來承載像素信息缩多,
1個繪圖基元(例如呆奕,Rect,Path衬吆,text梁钾,Bitmap),
1個Canvas用來管理Draw相關(guān)方法(把繪圖基元寫入Bitmap中)逊抡,
1個畫筆(用于描繪圖像的顏色和風格)姆泻。
這和我們?nèi)粘@斫獾睦L畫異曲同工,Bitmap作為畫布秦忿,Canvas管理著繪畫的手法麦射,繪圖基元代表著要繪制的目標,Paint就是你手里的畫筆和顏料灯谣。
獲取Canvas
1.onDraw方法的入口參數(shù)就是Canvas潜秋,直接可以使用,而我們操作這個Canvas最終的效果會反應(yīng)在這個View上胎许。
2.新建Canvas峻呛。1個Canvas對象一定要結(jié)合1個Bitmap對象,所以一定要為新建的Canvas對象設(shè)置1個Bitmap對象辜窑。但是要注意屿脐,該bitmap一定要是mutable(可變的)
Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888) ;
或者
BimtapFactory.decodeResource().copy(configu_argb_8888, true);
(BimapFactory.decodeResource() 得到的mutable 為false)
Canvas繪制
Canvas內(nèi)部維持了一個mutable Bitmap肋联,所以我們有一系列方法:
drawRGB(int r, int g, int b)
drawARGB(int a, int r, int g, int b)
drawColor(int color)
drawColor(int color, PorterDuff.Mode mode)
drawPaint(Paint paint)
繪制圖形
canvas.drawArc (扇形)
canvas.drawCircle(圓)
canvas.drawOval(橢圓)
canvas.drawLine(線)
canvas.drawPoint(點)
canvas.drawRect(矩形)
canvas.drawRoundRect(圓角矩形)
canvas.drawVertices(頂點)
cnavas.drawPath(路徑)
繪制圖片
canvas.drawBitmap(位圖)
canvas.drawPicture(圖片)
文本
canvas.drawText
Canvas變換
Canvas不僅僅可以draw一些圖形肤无、圖片论泛,其本身也提供了可操作的方法:rorate(旋轉(zhuǎn))、scale(壓縮)、translate(平移)方面、skew(扭曲)等放钦。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(new Rect(0, 0, 200, 200), new Paint());
canvas.scale(0.5f, 0.5f);//縮放了
canvas.drawRect(new Rect(400, 400, 600, 600), new Paint());
canvas.translate(600, 600);//平移了
canvas.rotate(45);//旋轉(zhuǎn)了
canvas.drawRect(new Rect(0, 0, 200, 200), new Paint());
canvas.translate(200, 200);
canvas.skew(.5f, .5f);//扭曲了
canvas.drawRect(new Rect(0, 0, 200, 200), new Paint());
}
Canvas保存和回滾
如繪制表盤:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < 360; i = i + 6) {
canvas.save();
canvas.rotate(i, 100, 100);
canvas.drawLine(100, 0, 100, 10, new Paint());
canvas.restore();
}
}
PorterDuffXfermode
概述
PorterDuffXfermode extends Xfermode。它將所繪制的圖形的像素與Canvas中對應(yīng)位置的像素按照一定規(guī)則進行混合恭金,形成新的像素值操禀,從而更新Canvas中最終的像素顏色值。
PorterDuffXfermode這個類中的Porter和Duff是兩個人名横腿,這兩個人在1984年一起寫了一篇名為《Compositing Digital Images》的論文颓屑。
我們下面會分析幾個代碼片段研究PorterDuffXfermode使用及工作原理詳解。
示例一
我們在演示如何使用PorterDuffXfermode之前耿焊,先看一下下面這個例子揪惦,代碼如下所示:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//設(shè)置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int r = canvasWidth / 3;
//繪制黃色的圓形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//繪制藍色的矩形
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
我們重寫了View的onDraw方法,首先將View的背景色設(shè)置為綠色搀别,然后繪制了一個黃色的圓形丹擎,然后再繪制一個藍色的矩形,效果如下所示:
上面演示就是Canvas正常的繪圖流程歇父,后來繪制的圖形就會覆蓋之前繪制的圖形!
示例二
下面我們使用PorterDuffXfermode對上面的代碼進行一下修改再愈,修改后的代碼如下所示:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//設(shè)置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int r = canvasWidth / 3;
//正常繪制黃色的圓形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作為PorterDuffXfermode繪制藍色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后將畫筆去除Xfermode
paint.setXfermode(null);
}
效果如下所示:
下面我們對以上代碼進行一下分析:
在canvas.drawRect()之前setXfermode榜苫,那么所繪制的矩形中的像素稱作源像素(Src),所繪制的矩形在Canvas中對應(yīng)位置的矩形內(nèi)的像素稱作目標像素(Dst)翎冲。
即根據(jù)Xfermode的規(guī)則垂睬,Src是繪制內(nèi)容像素,Dst是Canvas像素抗悍。Src和Dst的ARGB值會重新計算驹饺。
本例中Xfermode是PorterDuff.Mode.CLEAR,直接將目標像素的ARGB四個分量全置為0缴渊,即(0赏壹,0,0衔沼,0)蝌借,即透明色,所以實際上繪制了一個透明的矩形指蚁。但是效果圖為什么是白色的呢菩佑,因為屏幕本身是白色的。
示例三
我們在對示例二中的代碼進行一下修改凝化,將繪制圓形和繪制矩形相關(guān)的代碼放到canvas.saveLayer()和canvas.restoreToCount()之間稍坯,代碼如下所示:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//設(shè)置背景色
canvas.drawARGB(255, 139, 197, 186);
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG);
int r = canvasWidth / 3;
//正常繪制黃色的圓形
paint.setColor(0xFFFFCC44);
canvas.drawCircle(r, r, r, paint);
//使用CLEAR作為PorterDuffXfermode繪制藍色的矩形
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setColor(0xFF66AAFF);
canvas.drawRect(r, r, r * 2.7f, r * 2.7f, paint);
//最后將畫筆去除Xfermode
paint.setXfermode(null);
canvas.restoreToCount(layerId);
}
效果如下所示:
下面對上述代碼進行一下分析:
關(guān)于canvas繪圖中的layer有以下幾點需要說明:
canvas是支持圖層layer渲染這種技術(shù)的,canvas默認就有一個layer搓劫,當我們平時調(diào)用canvas的各種drawXXX()方法時瞧哟,其實是把所有的東西都繪制到canvas這個默認的layer上面袜蚕。
我們還可以通過canvas.saveLayer()新建一個layer,新建的layer放置在canvas默認layer的上部绢涡,當我們執(zhí)行了canvas.saveLayer()之后牲剃,我們所有的繪制操作都繪制到了我們新建的layer上,而不是canvas默認的layer雄可。
用canvas.saveLayer()方法產(chǎn)生的layer所有像素的ARGB值都是(0凿傅,0,0数苫,0)聪舒,即canvas.saveLayer()方法產(chǎn)生的layer初始時時完全透明的。
canvas.saveLayer()方法會返回一個int值虐急,用于表示layer的ID箱残,在我們對這個新layer繪制完成后可以通過調(diào)用canvas.restoreToCount(layer)或者canvas.restore()把這個layer繪制到canvas默認的layer上去,這樣就完成了一個layer的繪制工作止吁。
那你可能感覺到很奇怪被辑,我們只是將繪制圓形與矩形的代碼放到了canvas.saveLayer()和canvas.restoreToCount()之間,為什么不再像示例二那樣顯示白色的矩形了敬惦?
在將一個新建的layer繪制到Canvas上去時盼理,Android會用整個layer上面的像素顏色去更新Canvas對應(yīng)位置上像素的顏色,并不是簡單的替換俄删,而是Canvas和新layer進行Alpha混合宏怔,可參見此處鏈接。
大部分情況下畴椰,我們想要本例中實現(xiàn)的效果臊诊,而不是想要示例二中形成的白色矩形,所以大部分情況下在使用PorterDuffXfermode時都是結(jié)合canvas.saveLayer()斜脂、canvas.restoreToCount()的抓艳,將關(guān)鍵代碼寫在這兩個方法之間。
一張被不經(jīng)大腦瘋傳的神圖
這張圖是Android的sdk下自帶的API的Demo示例中的一個秽褒,其源碼對應(yīng)的物理路徑是C:\Users\iSpring\AppData\Local\Android\sdk\samples\android-23\legacy\ApiDemos\src\com\example\android\apis\graphics\Xfermodes.java壶硅。
這張圖演示了先繪制黃色的圓形,然后將畫筆paint設(shè)置為16種不同的PorterDuffXfermode销斟,然后再繪制藍色矩形的效果庐椒。
但是上圖是錯誤的,它實現(xiàn)這個效果是在該代碼中對所繪制的黃色圖形和藍色圖形大小都做了手腳蚂踊。
不同混合模式的計算規(guī)則
為了方便觀察對比约谈,整個View的背景設(shè)置為綠色,最終運行效果應(yīng)該是如下所示:
上面的例子演示了了16種混合模式的效果,并且關(guān)鍵代碼都放在了canvas.saveLayer()與canvas.restoreToCount()之間棱诱。DST是黃色圓泼橘,SRC是藍色矩形,PorterDuffXfermode公式應(yīng)用于該Rectangle(canvas.drawRect())區(qū)域迈勋。
我們知道一個像素的顏色由四個分量組成炬灭,即ARGB,第一個分量A表示的是Alpha值靡菇,后面三個分量RGB表示了顏色重归。我們用S代表源像素,源像素的顏色值可表示為[Sa, Sc]厦凤,Sa中的a是alpha的縮寫鼻吮,Sa表示源像素的Alpha值,Sc中的c是顏色color的縮寫较鼓,Sc表示源像素的RGB椎木。我們用D代表目標像素,目標像素的顏色值可表示為[Da, Dc]博烂,Da表示目標像素的Alpha值香椎,Dc表示目標像素的RGB。
源像素與目標像素在不同混合模式下計算顏色的規(guī)則如下所示:
CLEAR:[0, 0]所繪制不會提交到畫布上
SRC:[Sa, Sc] 顯示上層繪制圖片
DST:[Da, Dc]顯示下層繪制圖片
SRC_OVER:[Sa + (1 – Sa)Da, Rc = Sc + (1 – Sa)Dc]正常繪制顯示脖母,上下層繪制疊蓋士鸥。
DST_OVER:[Sa + (1 – Sa)Da, Rc = Dc + (1 – Da)Sc]上下層都顯示。下層居上顯示谆级。
SRC_IN:[Sa * Da, Sc * Da] 取兩層繪制交集。顯示上層讼积。
DST_IN:[Sa * Da, Sa * Dc] 取兩層繪制交集肥照。顯示下層。
SRC_OUT:[Sa * (1 – Da), Sc * (1 – Da)] 取上層繪制非交集部分勤众。
DST_OUT:[Da * (1 – Sa), Dc * (1 – Sa)] 取下層繪制非交集部分舆绎。
SRC_ATOP:[Da, Sc * Da + (1 – Sa) * Dc] 取下層非交集部分與上層交集部分
DST_ATOP:[Sa, Sa * Dc + Sc * (1 – Da)] 取上層非交集部分與下層交集部分
XOR:[Sa + Da – 2 * Sa * Da, Sc * (1 – Da) + (1 – Sa) * Dc]
DARKEN:[Sa + Da – SaDa, Sc(1 – Da) + Dc*(1 – Sa) + min(Sc, Dc)]
LIGHTEN:[Sa + Da – SaDa, Sc(1 – Da) + Dc*(1 – Sa) + max(Sc, Dc)]
MULTIPLY:[Sa * Da, Sc * Dc]
SCREEN:[Sa + Da – Sa * Da, Sc + Dc – Sc * Dc]
ADD:Saturate(S + D)
OVERLAY:Saturate(S + D)
最后需要說明一下,DARKEN们颜、LIGHTEN吕朵、OVERLAY等幾種混合規(guī)則在GPU硬件加速下不起效,如果你覺得混合模式?jīng)]有正確使用窥突,可以讓調(diào)用View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)方法努溃,把我們的View禁用掉GPU硬件加速,切換到軟件渲染模式阻问,這樣所有的混合模式都能正常使用了梧税,具體可參見博文《Android中GPU硬件加速控制及其在2D圖形繪制上的局限》。
最后總結(jié)一下,PorterDuffXfermode用于實現(xiàn)新繪制的像素與Canvas上對應(yīng)位置已有的像素按照混合規(guī)則進行顏色混合第队。
參考:
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解
Android中Canvas繪圖基礎(chǔ)詳解(附源碼下載)