參考
Xfermode in android - 解釋文檔和模式部分寫(xiě)得很好
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解 - 代碼實(shí)踐分析部分值得細(xì)看
網(wǎng)上大部分文章都說(shuō)有3個(gè)類(lèi)可用, 但是實(shí)際上僅需要掌握PoterDuffXfermode
, 因?yàn)橹挥兴С钟布铀? 官方的文檔的描述中Xfermode
的直接繼承類(lèi)也只有它了, 所以一般只用它.
PoterDuffXfermode
Porter-Duff 操作是 1 組 12 項(xiàng)用于描述數(shù)字圖像合成的基本手法,包括Clear、Source Only、Destination Only、Source Over、Source In、SourceOut、Source Atop、Destination Over、Destination In、DestinationOut、Destination Atop、XOR。通過(guò)組合使用 Porter-Duff 操作,可完成任意 2D圖像的合成。Thomas Porter 和 Tom Duff 發(fā)表于 1984年原始論文的掃描版本
簡(jiǎn)單來(lái)說(shuō)就是一種圖像合成的理論依據(jù), 規(guī)定了合成圖像時(shí)的像素操作. Android中支持總共18種模式, 就不一一列舉了. 看懂文檔就行.
文檔解釋
public enum Mode {
// ...
/** [Sa + (1 - Sa)*Da, Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
// ...以下省略
}
文檔中每個(gè)模式對(duì)應(yīng)一條公式, 公式中的縮寫(xiě)表示:
SRC = source, 表示即將要畫(huà)的像素
DST = destination, 表示已經(jīng)存在的像素
Sa = Source alpha, 透明通道值
Da = Dest alpha
Sc = Source color, 顏色值
Dc = Dst color
[AlphaValue, ColorValue] -> 第一個(gè)值為進(jìn)行像素操作后的透明通道值, 第二個(gè)值為操作后的顏色值
舉個(gè)例子:
DST_IN - [Sa * Da, Sa * Dc]
為了簡(jiǎn)化分析, 假設(shè)透明通道值不是0就是1
主要看顏色值的計(jì)算 Sa * Dc, 當(dāng)Sa = 1的時(shí)候, 顏色值就是Dc, 也就是說(shuō)在準(zhǔn)備畫(huà)的像素的alpha值為1的地方, 直接顯示原來(lái)的像素, Sa = 0的時(shí)候不顯示任何顏色, 并且只有在Sa和Da都是1的地方才會(huì)顯示顏色.
加入透明通道值的分析參考Xfermode in android
看懂文檔后我們就可以利用Xfermode做各種圖形效果, Xfermode可以做的事情理論上
可完成任意 2D圖像的合成
遠(yuǎn)遠(yuǎn)不限于實(shí)現(xiàn)任意形狀的ImageView. 接下來(lái)就一步步分析如下實(shí)現(xiàn)任意形狀的ImageView和我遇到的問(wèn)題.
實(shí)現(xiàn)任意形狀I(lǐng)mageView
分析
為了最簡(jiǎn)化代碼, 最好能夠復(fù)用ImageView
, 而ImageView#onDraw
就是把圖片畫(huà)在屏幕上, 也就是說(shuō)經(jīng)過(guò)ImageView#onDraw
方法后, 圖片像素會(huì)變成DST
(已經(jīng)存在的像素).
所以要實(shí)現(xiàn)任意形狀最直接的辦法應(yīng)該是根據(jù)形狀裁剪圖片像素, 即顯示DST
和SRC
重合的部分的DST
像素, 形狀內(nèi)的像素自然是SRC
(即將要畫(huà)的像素), 轉(zhuǎn)化成公式應(yīng)該是Sa * Dc
, 查模式說(shuō)明文檔找到我們需要的模式DST_IN
- [Sa * Da, Sa * Dc]
所以我們的核心代碼應(yīng)該如下
super.onDraw(canvas);// 畫(huà)圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawXX();// 畫(huà)多邊形覆蓋在圖片上
使用saveLayer
在實(shí)際測(cè)試的時(shí)候會(huì)發(fā)現(xiàn), 多邊形外的像素會(huì)變成黑色(也有可能是白色), 這是因?yàn)槟J(rèn)情況畫(huà)布只有一個(gè)圖層(就是Photoshop里面的圖層概念), 此時(shí)的DST
不僅是圖片, 還包括圖片后面的背景像素, 如果清除了多邊形外的像素, 當(dāng)然背景也會(huì)被清除掉了, 而一般情況下我們僅需要處理圖片本身, 所以實(shí)際使用中通常會(huì)使用Canvas#saveLayer
來(lái)創(chuàng)建新的透明圖層來(lái)進(jìn)行圖像合成的操作, 此時(shí)背景的像素就不會(huì)被納入DST
中.
核心代碼變成
int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);// 新增透明圖層
super.onDraw(canvas);// 畫(huà)圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawXX();// 畫(huà)多邊形覆蓋在圖片上
canvas.restoreToCount(layerId);// 合并圖層
使用Bitmap
如果我想制作圓形圖片, 那么直接通過(guò)Canvas#drawCircle
畫(huà)圓, 實(shí)際運(yùn)行就會(huì)發(fā)現(xiàn)結(jié)果跟預(yù)測(cè)的不同, 圓形外的像素并沒(méi)有消失, 為什么?
這是因?yàn)?strong>直接通過(guò)Canvas#drawXX
方法畫(huà)圖時(shí), SRC
僅是圖形內(nèi)的像素, 例如你畫(huà)了一個(gè)圓, 那么SRC(即將要畫(huà)的像素)僅是圓內(nèi)的像素, 也就是說(shuō)圖片與圓不重疊的像素并不會(huì)有任何變化, 當(dāng)然就不會(huì)消失了.
所以要想圓形外的像素會(huì)消失, 我們要把圓形外的像素也納入SRC
并且使其透明通道值為0.
所以進(jìn)行"過(guò)濾"操作的時(shí)候, 例如DST_IN, 僅顯示即將要畫(huà)的像素一般會(huì)先創(chuàng)建一個(gè)Bitmap
實(shí)例, 并先在Bitmap
畫(huà)要保留的圖形, 然后再把Bitmap
畫(huà)在圖片上, 此時(shí)SRC
的像素包括了整個(gè)Bitmap
而不僅僅是圖形內(nèi)的像素
.假設(shè)我們要制作的是菱形的圖片, 那么代碼就變成
// 創(chuàng)建一個(gè)跟圖片一樣大小的Bitmap, 并畫(huà)一個(gè)旋轉(zhuǎn)了45度的正方形
private Bitmap createMask() {
int maskWidth = getMeasuredWidth();
int maskHeight = getMeasuredHeight();
Bitmap mask = Bitmap.createBitmap(maskWidth, maskHeight, Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(mask);
canvas.translate(maskWidth / 2, 0);
canvas.rotate(45);
int rectSize = (int) (maskWidth / 2 / Math.sin(Math.toRadians(45)));
canvas.drawRect(0, 0, rectSize, rectSize, mPaint);
}
// 核心操作
int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);// 新增透明圖層
super.onDraw(canvas);// 畫(huà)圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawBitmap(createMask(), 0, 0, mPaint);// 畫(huà)多邊形覆蓋在圖片上
canvas.restoreToCount(layerId);
這里有個(gè)小技巧, 在創(chuàng)建Bitmap
的時(shí)候使用了Bitmap.Config.ALPHA_8
, 這是因?yàn)?code>DST_IN的公式中僅使用了Sa
, 不需要有顏色, 所以只使用ALPHA_8
就足夠了, 可以節(jié)省內(nèi)存.
效果圖
完整的實(shí)現(xiàn)旋轉(zhuǎn)45度正方形ImageView
代碼
注: 在邊長(zhǎng)計(jì)算中假設(shè)了ImageView
本身是一個(gè)正方形