各個擊破搞明白PorterDuff.Mode

原文:https://www.jianshu.com/p/d11892bbe055

做過圖形圖像處理coding的Android程序員一定用過或瞭解過PorterDuff.Mode這個枚舉變量中的某些值,對此瞭解不多理解不深刻的時候是不是會很糾結到底該用那個模式呢?至少不能快速準確地用到恰當的模式,那麼PorterDuff.Mode究竟是什麼,它的各個枚舉值有什麼作用呢?這裏我整理一下筆記,加深理解。

PorterDuff.Mode是什麼

public Xfermode setXfermode(Xfermode xfermode) {
    long xfermodeNative = 0;
    if (xfermode != null)
        xfermodeNative = xfermode.native_instance;
    native_setXfermode(mNativePaint, xfermodeNative);
    mXfermode = xfermode;
    return xfermode;
}

在android SDK Paint類中有一個很重要的方法setXfermode(源碼如上),這個方法用於設置圖像的過渡模式,所謂過渡是指圖像的飽和度、顏色值等參數的計算結果的圖像表現。在SDK中Xfermode有三個子類:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,前兩個類在API 16被遺棄了,而且不是本文的主題內容,所以這裏不作介紹。PorterDuffXfermode類主要用於圖形合成時的圖像過渡模式計算,其概念來自於1984年在ACM SIGGRAPH計算機圖形學出版物上發表了“Compositing digital images(合成數字圖像)”的Tomas Porter和Tom Duff,合成圖像的概念極大地推動了圖形圖像學的發展,PorterDuffXfermode類名就來源於這倆人的名字組合PorterDuff。下面是android SDK中PorterDuff的Mode枚舉類型定義。

public enum Mode {
    /** [0, 0] */
    CLEAR       (0),
    /** [Sa, Sc] */
    SRC         (1),
    /** [Da, Dc] */
    DST         (2),
    /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
    SRC_OVER    (3),
    /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
    DST_OVER    (4),
    /** [Sa * Da, Sc * Da] */
    SRC_IN      (5),
    /** [Sa * Da, Sa * Dc] */
    DST_IN      (6),
    /** [Sa * (1 - Da), Sc * (1 - Da)] */
    SRC_OUT     (7),
    /** [Da * (1 - Sa), Dc * (1 - Sa)] */
    DST_OUT     (8),
    /** [Da, Sc * Da + (1 - Sa) * Dc] */
    SRC_ATOP    (9),
    /** [Sa, Sa * Dc + Sc * (1 - Da)] */
    DST_ATOP    (10),
    /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
    XOR         (11),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
    DARKEN      (12),
    /** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
    LIGHTEN     (13),
    /** [Sa * Da, Sc * Dc] */
    MULTIPLY    (14),
    /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
    SCREEN      (15),
    /** Saturate(S + D) */
    ADD         (16),
    OVERLAY     (17);
    Mode(int nativeInt) {
        this.nativeInt = nativeInt;
    }
    /**
     * @hide
     */
    public final int nativeInt;
}

上面代碼中每種模式的註釋都說明了該模式的alpha通道和顏色值的計算方式,要理解各個模式的計算方式需要先弄明白公式中各個元素的具體含義:

Sa:全稱爲Source alpha,表示源圖的Alpha通道;
Sc:全稱爲Source color,表示源圖的顏色;
Da:全稱爲Destination alpha,表示目標圖的Alpha通道;
Dc:全稱爲Destination color,表示目標圖的顏色.

當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

如何應用PorterDuff.Mode

圖像合成效果示意圖

 

上面這張圖從一定程度上形象地說明了運用PorterDuff.Mode進行圖像合成的作用,兩個圖形一圓一方通過一定的計算產生了不同的合成效果,我們在實際工作中需要做圖片處理時可以參考這張圖的示意快速地選擇合適的Mode。
爲了更清楚地理解各個Mode的作用效果,我自己寫了一個Demo,逐一驗證上面的Mode。

/**
 * Created by Alex Pang on 2016/8/20.
 * 自定義View,使用PorterDuff.Mode驗證圖像合成效果
 */
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);
        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);
    }

    @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/2;
        int centerY = h/2;
        int quarterWidth = width /4;
        srcRect = new RectF(centerX-quarterWidth, centerY-quarterWidth, centerX+quarterWidth, centerY+quarterWidth);
        dstRect = new RectF(centerX-quarterWidth, centerY-quarterWidth, centerX+quarterWidth, centerY+quarterWidth);
    }
}

其中用到的圖片資源分別如下,destination沿圖片左上到右下的對角線將圖片分成兩部分,左下部分是完全不透明的,右上部分是半透明的;source沿圖片左下到右上的對角線將圖片分成兩部分,左上部分是半透明的,右下部分是完全不透明的。圖中有顏色的區域外都是完全透明的,這樣當兩張圖片合成時可以讓各種情況交叉呈現,便於分析理解。

 

destination

 

source

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.Mode.SRC_OVER

DST_OVER

[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc],與SRC_OVER相反,此模式是目標圖像被繪製在源圖像的上方

 

PorterDuff.Mode.DST_OVER

SRC_IN

[Sa * Da, Sc * Da],在兩者相交的地方繪製源圖像,並且繪製的效果會受到目標圖像對應地方透明度的影響

 

PorterDuff.Mode.SRC_IN

DST_IN

[Sa * Da, Sa * Dc],可以和SRC_IN 進行類比,在兩者相交的地方繪製目標圖像,並且繪製的效果會受到源圖像對應地方透明度的影響

 

PorterDuff.Mode.DST_IN

SRC_OUT

[Sa * (1 - Da), Sc * (1 - Da)],從字面上可以理解爲在不相交的地方繪製源圖像,那麼我們來看看效果是不是這樣,如下圖。實際上color 是 Sc * ( 1 - Da ) ,表示如果相交處的目標色的alpha是完全不透明的,這時候源圖像會完全被過濾掉,否則會受到相交處目標色 alpha 影響,呈現出對應色值。

 

PorterDuff.Mode.SRC_OUT

DST_OUT

[Da * (1 - Sa), Dc * (1 - Sa)],可以類比SRC_OUT , 在不相交的地方繪製目標圖像,相交處根據源圖像alpha進行過濾,完全不透明處則完全過濾,完全透明則不過濾

 

PorterDuff.Mode.DST_OUT

SRC_ATOP

[Da, Sc * Da + (1 - Sa) * Dc],源圖像和目標圖像相交處繪製源圖像,不相交的地方繪製目標圖像,並且相交處的效果會受到源圖像和目標圖像alpha的影響

 

PorterDuff.Mode.SRC_ATOP

DST_ATOP

[Sa, Sa * Dc + Sc * (1 - Da)],源圖像和目標圖像相交處繪製目標圖像,不相交的地方繪製源圖像,並且相交處的效果會受到源圖像和目標圖像alpha的影響

 

PorterDuff.Mode.DST_ATOP

XOR

[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc],在不相交的地方按原樣繪製源圖像和目標圖像,相交的地方受到對應alpha和顏色值影響,按公式進行計算,如果都完全不透明則相交處完全不繪製

 

PorterDuff.Mode.XOR

DARKEN

[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)],該模式處理過後,會感覺效果變暗,即進行對應像素的比較,取較暗值,如果色值相同則進行混合;
從算法上看,alpha值變大,色值上如果都不透明則取較暗值,非完全不透明情況下使用上面算法進行計算,受到源圖和目標圖對應色值和alpha值影響。

 

PorterDuff.Mode.DARKEN

LIGHTEN

[Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)],可以和 DARKEN 對比起來看,DARKEN 的目的是變暗,LIGHTEN 的目的則是變亮,如果在均完全不透明的情況下,色值取源色值和目標色值中的較大值,否則按上面算法進行計算。

 

PorterDuff.Mode.LIGHTEN

MULTIPLY

[Sa * Da, Sc * Dc],正片疊底,即查看每個通道中的顏色信息,並將基色與混合色複合。結果色總是較暗的顏色,任何顏色與黑色複合產生黑色,任何顏色與白色複合保持不變,當用黑色或白色以外的顏色繪畫時,繪畫工具繪製的連續描邊產生逐漸變暗的顏色。

 

PorterDuff.Mode.MULTIPLY

SCREEN

[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc],濾色,濾色模式與我們所用的顯示屏原理相同,所以也有版本把它翻譯成屏幕;簡單的說就是保留兩個圖層中較白的部分,較暗的部分被遮蓋;當一層使用了濾色(屏幕)模式時,圖層中純黑的部分變成完全透明,純白部分完全不透明,其他的顏色根據顏色級別產生半透明的效果。

 

PorterDuff.Mode.SCREEN

ADD

Saturate(S + D),飽和度疊加

 

PorterDuff.Mode.ADD

OVERLAY

像素是進行 Multiply (正片疊底)混合還是 Screen (屏幕)混合,取決於底層顏色,但底層顏色的高光與陰影部分的亮度細節會被保留

 

PorterDuff.Mode.OVERLAY

網上關於Xfermode詳細介紹的文章已經很多,我在次留下拙筆以便後續工作需要查閱,通過自己查閱資料、編寫Demo、請朋友幫忙設計圖片資源然後分析程序效果的過程,讓我獲益匪淺。



作者:Alexyz123
鏈接:https://www.jianshu.com/p/d11892bbe055
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章