圖像合成Xfermode和任意形狀I(lǐng)mageView

參考
Xfermode in android - 解釋文檔和模式部分寫得很好
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解 - 代碼實(shí)踐分析部分值得細(xì)看

網(wǎng)上大部分文章都說有3個(gè)類可用, 但是實(shí)際上僅需要掌握PoterDuffXfermode, 因?yàn)橹挥兴С钟布铀? 官方的文檔的描述中Xfermode的直接繼承類也只有它了, 所以一般只用它.

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。通過組合使用 Porter-Duff 操作,可完成任意 2D圖像的合成。Thomas Porter 和 Tom Duff 發(fā)表于 1984年原始論文的掃描版本

簡單來說就是一種圖像合成的理論依據(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è)模式對應(yīng)一條公式, 公式中的縮寫表示:
SRC = source, 表示即將要畫的像素
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]
為了簡化分析, 假設(shè)透明通道值不是0就是1
主要看顏色值的計(jì)算 Sa * Dc, 當(dāng)Sa = 1的時(shí)候, 顏色值就是Dc, 也就是說在準(zhǔn)備畫的像素的alpha值為1的地方, 直接顯示原來的像素, Sa = 0的時(shí)候不顯示任何顏色, 并且只有在Sa和Da都是1的地方才會顯示顏色.

加入透明通道值的分析參考Xfermode in android

看懂文檔后我們就可以利用Xfermode做各種圖形效果, Xfermode可以做的事情理論上

可完成任意 2D圖像的合成

遠(yuǎn)遠(yuǎn)不限于實(shí)現(xiàn)任意形狀的ImageView. 接下來就一步步分析如下實(shí)現(xiàn)任意形狀的ImageView和我遇到的問題.

實(shí)現(xiàn)任意形狀I(lǐng)mageView

分析

為了最簡化代碼, 最好能夠復(fù)用ImageView, 而ImageView#onDraw就是把圖片畫在屏幕上, 也就是說經(jīng)過ImageView#onDraw方法后, 圖片像素會變成DST(已經(jīng)存在的像素).

所以要實(shí)現(xiàn)任意形狀最直接的辦法應(yīng)該是根據(jù)形狀裁剪圖片像素, 即顯示DSTSRC重合的部分的DST像素, 形狀內(nèi)的像素自然是SRC(即將要畫的像素), 轉(zhuǎn)化成公式應(yīng)該是Sa * Dc, 查模式說明文檔找到我們需要的模式DST_IN - [Sa * Da, Sa * Dc]

所以我們的核心代碼應(yīng)該如下

super.onDraw(canvas);// 畫圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawXX();// 畫多邊形覆蓋在圖片上

使用saveLayer

在實(shí)際測試的時(shí)候會發(fā)現(xiàn), 多邊形外的像素會變成黑色(也有可能是白色), 這是因?yàn)槟J(rèn)情況畫布只有一個(gè)圖層(就是Photoshop里面的圖層概念), 此時(shí)的DST不僅是圖片, 還包括圖片后面的背景像素, 如果清除了多邊形外的像素, 當(dāng)然背景也會被清除掉了, 而一般情況下我們僅需要處理圖片本身, 所以實(shí)際使用中通常會使用Canvas#saveLayer來創(chuàng)建新的透明圖層來進(jìn)行圖像合成的操作, 此時(shí)背景的像素就不會被納入DST中.

核心代碼變成

int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);// 新增透明圖層
super.onDraw(canvas);// 畫圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawXX();// 畫多邊形覆蓋在圖片上
canvas.restoreToCount(layerId);// 合并圖層

使用Bitmap

如果我想制作圓形圖片, 那么直接通過Canvas#drawCircle畫圓, 實(shí)際運(yùn)行就會發(fā)現(xiàn)結(jié)果跟預(yù)測的不同, 圓形外的像素并沒有消失, 為什么?

這是因?yàn)?strong>直接通過Canvas#drawXX方法畫圖時(shí), SRC僅是圖形內(nèi)的像素, 例如你畫了一個(gè)圓, 那么SRC(即將要畫的像素)僅是圓內(nèi)的像素, 也就是說圖片與圓不重疊的像素并不會有任何變化, 當(dāng)然就不會消失了.
所以要想圓形外的像素會消失, 我們要把圓形外的像素也納入SRC并且使其透明通道值為0.

所以進(jìn)行"過濾"操作的時(shí)候, 例如DST_IN, 僅顯示即將要畫的像素一般會先創(chuàng)建一個(gè)Bitmap實(shí)例, 并先在Bitmap畫要保留的圖形, 然后再把Bitmap畫在圖片上, 此時(shí)SRC的像素包括了整個(gè)Bitmap而不僅僅是圖形內(nèi)的像素

.假設(shè)我們要制作的是菱形的圖片, 那么代碼就變成

// 創(chuàng)建一個(gè)跟圖片一樣大小的Bitmap, 并畫一個(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);// 畫圖片
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));// 設(shè)置Xfermode模式
canvas.drawBitmap(createMask(), 0, 0, mPaint);// 畫多邊形覆蓋在圖片上
canvas.restoreToCount(layerId);

這里有個(gè)小技巧, 在創(chuàng)建Bitmap的時(shí)候使用了Bitmap.Config.ALPHA_8, 這是因?yàn)?code>DST_IN的公式中僅使用了Sa, 不需要有顏色, 所以只使用ALPHA_8就足夠了, 可以節(jié)省內(nèi)存.

效果圖


菱形圖片效果圖.png

完整的實(shí)現(xiàn)旋轉(zhuǎn)45度正方形ImageView代碼
注: 在邊長計(jì)算中假設(shè)了ImageView本身是一個(gè)正方形

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容