Android自定義View系列:PorterDuff.Mode

1 PorterDuff.Mode的作用和背景

PorterDuff.Mode 是用來指定兩個圖像共同繪製時的顏色策略的。它是一個枚舉,不同的Mode可以指定不同的策略。顏色策略的意思,就是說把源圖像繪製到目標圖像處時應該怎樣確定二者結合後的顏色。

PorterDuff.Mode 一共有17個,可以分爲兩類:

  • Alpha合成(Alpha Compositing)

  • 混合(Blending)

第一類Alpha合成,其實就是 PorterDuff 這個詞所指代的算法。PorterDuff 並不是一個具有實際意義的詞組,而是兩個人的名字。這兩個人當年共同發表了一篇論文,描述了12種將兩個圖像共同繪製的操作(即算法)。而這篇論文所論述的操作,都是關於Alpha通道(也就是我們通俗理解的透明度)的計算的,後來人們就把這類計算稱爲Alpha合成(Alpha Composiing)。

第二類混合,也就是Photoshop等製圖軟件裏都有的那些混合模式(multiply darken lighten之類的)。這一類操作的是顏色本身而不是Alpha通道,並不屬於Alpha合成,所以和Porter與Duff這兩個人也沒什麼關係,不過爲了使用方便,它們同樣也被Google加進了 PorterDuff.Mode 裏。

2 PorterDuff.Mode的顏色策略

2.1 Alpha合成

在這裏插入圖片描述
在這裏插入圖片描述

上面的Alpha合成效果是有條件的:

  • 你要選擇哪個是SRC,哪個是DST(藍色矩形是SRC,粉色圓形是DST)

  • 是SRC在上層還是DST在上層(或DST在上層還是DST在下層)【上層和下層的意思就是哪個先繪製,上層會疊加在下層的上面】

上面Google的官方文檔展示的Alpha合成是DST在下層先繪製,SRC在上層後繪製

根據上面的Alpha合成效果,我這邊再細分方便理解:

  • OUT:取指定層的非交集部分。比如 DST_IN 表示取下層非交集部分

  • IN:取指定層的交集部分。比如 DST_IN 表示取下層交集部分

  • OVER:指定層在上層顯示。比如 DST_OVER 表示取下層顯示在上面

  • ATOP:取與指定層的交集部分和相反層的非交集部分。比如 DST_ATOP 表示取下層的交集部分和上層的非交集部分

  • XOR:取上層和下層的非交集部分

那麼各個Alpha合成的描述如下:

  • PorterDuff.Mode.CLEAR:繪製不會提交到畫布上

  • PorterDuff.Mode.SRC:顯示上層繪製圖片

  • PorterDuff.Mode.SRC_OVER:正常顯示,指定上層覆蓋在下層上面

  • PorterDuff.Mode.SRC_IN:取上層交集部分

  • PorterDuff.Mode.SRC_OUT:取上層非交集部分

  • PorterDuff.Mode.SRC_ATOP:取上層交集部分和下層非交集部分

  • PorterDuff.Mode.DST:顯示下層繪製圖片

  • PorterDuff.Mode.DST_OVER:下層覆蓋在上層上面

  • PorterDuff.Mode.DST_IN:取下層交集部分

  • PorterDuff.Mode.DST_OUT:取下層非交集部分

  • PorterDuff.Mode.DST_ATOP:取下層交集部分和上層非交集部分

  • PorterDuff.Mode.CLEAR:所繪製不會提交到畫布上

  • PorterDuff.Mode.XOR:取上層和下層的非交集部分

2.2 Xfermode使用注意事項

2.2.1 控制好透明區域

你或許會認爲Google官方文檔中,SRC和DST就是隻繪製顏色區域一個矩形和一個圓形,你的理解是這樣的:

SRC:

// 假設矩形的繪製區域是在left=150, top=150, right=400, bottom=400
// 不包含外部透明區域
canvas.drawRect(150, 150, 400, 400, paint);

在這裏插入圖片描述

DST:

// 假設圓形的繪製區域是在圓心(400, 150),radius=150區域
// 不包含外部透明區域
canvas.drawCircle(400, 150, 150, paint);

在這裏插入圖片描述

// 只考慮顏色區域的疊加效果
public class XfermodeView extends View {
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Xfermode mXfermode;

    public XfermodeView(Context context) {
        super(context);
    }

    public XfermodeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public XfermodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    {
        // 如果有效果沒有生效(比如混合的幾種模式),需要關閉硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);

        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

		// 繪製DST位於下層,DST爲圓形
        canvas.drawCircle(400, 150, 150, paint);

        mPaint.setXfermode(mXfermode);

		// 繪製SRC位於上層,SRC爲矩形
        canvas.drawRect(150, 150, 400, 400, paint);

        mPaint.setXfermode(null);

        canvas.restoreToCount(saved);
    }
}

如果按上面只繪製顏色區域的疊加,你會發現實際繪製效果和Google效果圖展示的不同,好像有些顏色策略顯示正常,有些顯示不正確。

Google官方文檔展示所繪製SRC和DST是Bitmap,Bitmap繪製的藍色矩形和粉色圓形顏色區域是另外的寬高。SRC和DST的Bitmap是大小控制了透明區域重疊的,不僅僅包含顏色區域,還有外部的網格透明區域也要計算進去。描述如下圖所示:

在這裏插入圖片描述

透明區域要足夠覆蓋到要和它結合繪製的內容,否則得到的結果很可能不是你想要的:

在這裏插入圖片描述

我們按照Google官方文檔的效果將上面的分析寫成項目示例代碼:

public class XfermodeView extends View {
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap mSrcBitmap;
    private Bitmap mDstBitmap;
    private Xfermode mXfermode;

    public XfermodeView(Context context) {
        super(context);
    }

    public XfermodeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public XfermodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    {
        // 如果有效果沒有生效(比如混合的幾種模式),需要關閉硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);

        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mSrcBitmap == null) {
            mSrcBitmap = createSrc(getWidth(), getHeight());
        }
        if (mDstBitmap == null) {
            mDstBitmap = createDst(getWidth(), getHeight());
        }

        int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);

		// 繪製DST位於下層,DST爲圓形
        canvas.drawBitmap(mDstBitmap, 0, 0, mPaint);

        mPaint.setXfermode(mXfermode);

		// 繪製SRC位於上層,SRC爲矩形
        canvas.drawBitmap(mSrcBitmap, 0, 0, mPaint);

        mPaint.setXfermode(null);

        canvas.restoreToCount(saved);
    }

    private Bitmap createSrc(int width, int height) {
    	// Bitmap大小是View的寬高,和DST是重疊的
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#3C92F5"));
        canvas.drawRect(150, 150, 400, 400, paint);
        return bitmap;
    }

    private Bitmap createDst(int width, int height) {
    	// Bitmap大小是View的寬高,和SRC是重疊的
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.parseColor("#ED1462"));
        canvas.drawCircle(400, 150, 150, paint);
        return bitmap;
    }
}

2.2.2 使用離屏緩衝(Off-screen Buffer)

如果在使用Xfermode時沒有開啓離屏緩衝,會出現繪製效果與期望不符。同樣按照上面SRC和DST的Bitmap都是同樣大小來繪製,沒有開啓離屏緩衝效果的 PorterDuff.Mode.DST_IN 的效果如下:

在這裏插入圖片描述

按照邏輯我們會認爲,在第二步繪製SRC畫圓的時候,跟它共同計算的是第一步繪製的DST矩形。但實際上,卻是整個View的顯示區域都在畫圓的時候參與計算,並且View自身的底色並不是默認的透明色,導致不僅繪製的是整個圓的範圍,而且在範圍之外都變成了黑色。

要想使用Xfermode正常繪製,必須使用離屏緩衝(Off-screen Buffer)把內容繪製在額外的層上,再把繪製好的內容貼回View中。通過使用離屏緩衝,把要繪製的內容單獨繪製在緩衝層,Xfermode的使用就不會出現奇怪的結果了。使用離屏緩衝有兩種方式:

  • Canvas.saveLayer():可以做短時的離屏緩衝
int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
...
canvas.restoreToCount(saved);
  • View.setLayerType():直接把整個View都繪製在離屏緩衝中

    • setLayerType(LAYER_TYPE_HARDWARE):使用GPU來緩衝

    • setLayerType(LAYER_TYPE_SOFTWARE):直接用一個Bitmap來緩衝

2.3 混合

混合的使用也可以使用上面的Demo來完成,但要注意關閉硬件加速否則存在無法生效的問題。一般實際開發用得不多瞭解即可,具體效果還是直接拿的Google官方文檔的效果圖:

在這裏插入圖片描述

參考:

Hencoder自定義View

Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解

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