PorterDuffXfermode誤區總結

[TOC]android

概述

  類android.graphics.PorterDuffXfermode繼承自android.graphics.Xfermode。在用Android中的Canvas進行繪圖時,能夠經過使用PorterDuffXfermode將所繪製的圖形的像素與Canvas中對應位置的像素按照必定規則進行混合,造成新的像素值,從而更新Canvas中最終的像素顏色值,這樣會建立不少有趣的效果。當使用PorterDuffXfermode時,須要將將其做爲參數傳給Paint.setXfermode(Xfermode xfermode)方法,這樣在用該畫筆paint進行繪圖時,Android就會使用傳入的PorterDuffXfermode,若是不想再使用Xfermode,那麼能夠執行Paint.setXfermode(null)。
  上面的概述中咱們提煉出兩點:canvas

  • PorterDuffXfermode的做用是將所繪製的圖形的像素與Canvas中對應位置的像素進行混合,造成新的像素。這是基本原理,請謹記;
  • 若是PorterDuffXfermode再也不使用,請調用Paint.setXfermode(null),關閉效果;

PorterDuffXfermode正確使用

  借用google官方demo中的圖bash

image

不少同窗在測試過程當中發現和demo的實現有很大的出入,其實咱們仔細看官方demo,就會發現有很大不一樣,我將核心代碼抽取以下:ide

static Bitmap makeDst(int w, int h) {
        Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bm);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

        p.setColor(0xFFFFCC44);
        c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
        return bm;
    }

// create a bitmap with a rect, used for the "src" image
static Bitmap makeSrc(int w, int h) {
    Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

    p.setColor(0xFF66AAFF);
    c.drawRect(w / 3, h / 3, w * 19 / 20, h * 19 / 20, p);
    return bm;
}

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

    canvas.drawColor(Color.GREEN);

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    canvas.restoreToCount(sc);
}
複製代碼

代碼中有幾個核心的地方咱們要注意:測試

  • 調用saveLayer在一個新的Layer上進行圖像的混合;

  這是由於在調用saveLayer時,會生成了一個全新的bitmap,這個bitmap的大小就是咱們指定的保存區域的大小,新生成的bitmap是全透明的,在調用saveLayer後全部的繪圖操做都是在這個bitmap上進行的。ui

image

沒有saveLayer的繪圖流程google

  因爲咱們先把整個畫布給染成了綠色,而後再畫上了一個圓形,因此在應用xfermode來畫源圖像的時候,目標圖像當前Bitmap上的全部圖像了,也就是整個綠色的屏幕和一個圓形了。因此這時候源圖像的相交區域是沒有透明像素的,透明度全是100%,這也就不難解釋結果是這樣的緣由了。spa

image

調用saveLayer的目的就是讓你的目標圖像獨立的存在於一個layer上,而不受原始畫布圖像的干擾。這樣才能進行目標圖像和源圖像的對應位置的像素混合。3d

  • 建立的目標圖像bitmap與源圖像bitmap大小是一致(其實不一致也是能夠,只是計算圖像座標時更麻煩,沒有必要);

注:rest

  1. 代碼中w表明View的寬,h表明View的高;
  2. 咱們將canvas的背景繪製成GREEN;

PorterDuffXfermode使用誤區

測試一 使用saveLayer獲得的正確圖像以下

image
進行SRC_IN變換,獲得正確圖像:
image

測試二 不使用saveLayer

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

    canvas.drawColor(Color.GREEN);

    //int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    //canvas.restoreToCount(sc);
}
複製代碼

獲得的圖像以下:

image

PorterDuff.Mode.SRC_IN的含義是在二者相交的地方繪製源圖像,由於沒有使用saveLayer因此咱們目標圖像是綠色的背景+黃色的圓,源圖像是藍色正方形。

  1. 目標圖像bitmap與源圖像btmap大小是徹底同樣的;
  2. 源圖像有圖像區域小於源圖像btmap大小的,而目標圖像由於繪製了背景其有圖像的區域與bitmap的實際大小是同樣的;

那麼這裏有兩個錯誤點:

  1. 咱們的目的是對黃色圓與藍色方框進行圖像混合,顯然上面的結果是不對的,這就是圖層的重要性;
  2. 根據SRC_IN的含義獲得的結果應該只保留藍色的正方形,除了藍色的正方形外,其它區域應該是透明顏色,顯然這也不符合咱們的預期;

爲何獲得的是黑色圖像,而不是透明顏色,其實我沒弄明白明白,可是咱們知道了正確使用PorterDuffXfermode的代碼以下:

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

    .......

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);
    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);
    //繪製你的目標圖像
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    //繪製你的源圖像
    mPaint.setXfermode(null);
    canvas.restoreToCount(sc);
    
    .......
}
複製代碼

測試三 你真的瞭解SRC_IN嗎?

咱們在測試一的基礎上修改一段代碼

static Bitmap makeDst(int w, int h) {
    Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    Canvas c = new Canvas(bm);
    Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);

    p.setColor(0x7fFFCC44);
    c.drawOval(new RectF(0, 0, w * 3 / 4, h * 3 / 4), p);
    return bm;
}
複製代碼

將目標圖像中0xFFFFCC44改成0x7fFFCC44,也就透明度改成0.5

image
image

右邊是沒有修改透明度的圖像,左邊是修改後的;咱們發現SRC_IN的結果發生了變化。讓咱們來看坎SRC_IN完整的解釋:

  • 在二者相交的地方繪製源圖像,而且繪製的效果會受到目標圖像對應地方透明度的影響;

細節決定成敗,小小的細節頗有可能成爲你平常工做中的大坑,更多PorterDuff.Mode含義,請參考

測試四 LAYER_TYPE_SOFTWARE使用

網上有很多說使用LAYER_TYPE_SOFTWARE的,咱們也來實踐一下:
初始化時添加以下代碼,使用軟件加速

setLayerType(LAYER_TYPE_SOFTWARE, null);
複製代碼

測試代碼仍是使用調用了saveLayer的代碼,可是使用CLEAR模式

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

    canvas.drawColor(Color.GREEN);

    int sc = canvas.saveLayer(0, 0, W, H, null, Canvas.ALL_SAVE_FLAG);

    canvas.drawBitmap(makeDst(W, H), 0, 0, mPaint);

    //mPaint.setXfermode(sModes[mIndex]);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    canvas.drawBitmap(makeSrc(W, H), 0, 0, mPaint);

    mPaint.setXfermode(null);

    canvas.restoreToCount(sc);
}
複製代碼

image
image

左邊是沒有設置setLayerType獲得的混合圖像,符合CLEAR模式的預期;右邊是設置LAYER_TYPE_SOFTWARE後獲得的圖像,咱們發現混合後所有變成透明的了,這是爲何呢?我給出這樣一個猜測,在沒有設置LAYER_TYPE_SOFTWARE時,只有有效圖像區域參與像素混合,透明區域不參與像素混合;而設置LAYER_TYPE_SOFTWARE後透明區域也參與像素混合了。爲了驗證這一點,咱們進行測試五。

測試五

在測試四的基礎上,目標bitmap和源bitmap是同樣大的,咱們的測試方法是將源bitmap的大小修改成只有目標bitmap的1/4,這樣一來源bitmap將只佔據整個圖像左上角1/4區域,獲得的測試結果以下:

image

根據咱們的猜測,源bitmap的整個區域(透明區域和圖像區域)都參與到與目標圖像的像素混合中,致使左上角1/4區域變爲了透明的。

以上分析根據我的經驗得出,並不透徹,若有不對,望指正。

相關文章
相關標籤/搜索