Android Paint XferMode踩坑記

前言

某天,在網上看到一個水波紋ProgressView的例子,N天之后突然想自己實現一個,由于之前做過一個圓形ImageView的頭像框,所以就想到用同樣的原理(XferMode)實現之。不實踐不要緊,一實踐發現這是一個巨大的坑,并且網上99%的文章并沒有正確的解釋,同樣的代碼下載下來也跑不出預想的效果。
直到看到這篇文章https://blog.csdn.net/iispring/article/details/50472485才恍然大悟。原來網上基本上都是這個圖加上一段錯誤的代碼(-_-!):

網上的圖.png

官方代碼

// create a bitmap with a circle, used for the "dst" image  
    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) {  
            canvas.drawColor(Color.WHITE);  
  
            Paint labelP = new Paint(Paint.ANTI_ALIAS_FLAG);  
            labelP.setTextAlign(Paint.Align.CENTER);  
  
            Paint paint = new Paint();  
            paint.setFilterBitmap(false);  
  
            canvas.translate(15, 35);  
  
            int x = 0;  
            int y = 0;  
            for (int i = 0; i < sModes.length; i++) {  
                // draw the border  
                paint.setStyle(Paint.Style.STROKE);  
                paint.setShader(null);  
                canvas.drawRect(x - 0.5f, y - 0.5f,  
                                x + W + 0.5f, y + H + 0.5f, paint);  
  
                // draw the checker-board pattern  
                paint.setStyle(Paint.Style.FILL);  
                paint.setShader(mBG);  
                canvas.drawRect(x, y, x + W, y + H, paint);  
  
                // draw the src/dst example into our offscreen bitmap  
                int sc = canvas.saveLayer(x, y, x + W, y + H, null,  
                                          Canvas.MATRIX_SAVE_FLAG |  
                                          Canvas.CLIP_SAVE_FLAG |  
                                          Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |  
                                          Canvas.FULL_COLOR_LAYER_SAVE_FLAG |  
                                          Canvas.CLIP_TO_LAYER_SAVE_FLAG);  
                canvas.translate(x, y);  
                canvas.drawBitmap(mDstB, 0, 0, paint);  
                paint.setXfermode(sModes[i]);  
                canvas.drawBitmap(mSrcB, 0, 0, paint);  
                paint.setXfermode(null);  
                canvas.restoreToCount(sc);  
  
                // draw the label  
                canvas.drawText(sLabels[i],  
                                x + W/2, y - labelP.getTextSize()/2, labelP);  
  
                x += W + 10;  
  
                // wrap around when we've drawn enough for one row  
                if ((i % ROW_MAX) == ROW_MAX - 1) {  
                    x = 0;  
                    y += H + 30;  
                }  
            }  
        }  
    }  

官方的代碼中有幾點要注意:
1、在畫SRC和DST時都create了一個bitmap并在這個bitmap上畫出圖形。
2、兩個bitmap的大小相等并且重合
3、默認的bitmap是透明的
4、只有在draw SRC時才設置Paint的XferMode
只要根據官方Demo的代碼來寫就能實現網上那張圖里的所有效果(圖沒有問題),但是網上的很多講解都是關于設置什么layerType與硬件加速有關的標志位

官方代碼以外的探究

官方代碼中在繪制DST和SRC時都新建了一個Bitmap然后將bitmap返回再畫到原本的canvas上。
經過探究,只要SRC能完全覆蓋DST的區域,即使不新建bitmap也照樣可以達到預期的效果。如果沒有完全覆蓋,在進行運算時就無法將SRC與DST進行運算,也就達不到網圖中的效果,甚至有可能出現各種各樣的問題。

工作原理

摘抄自文章片段:
我們知道一個像素的顏色由四個分量組成,即ARGB,第一個分量A表示的是Alpha值,后面三個分量RGB表示了顏色。我們用S代表源像素,源像素的顏色值可表示為[Sa, Sc],Sa中的a是alpha的縮寫,Sa表示源像素的Alpha值,Sc中的c是顏色color的縮寫,Sc表示源像素的RGB。我們用D代表目標像素,目標像素的顏色值可表示為[Da, Dc],Da表示目標像素的Alpha值,Dc表示目標像素的RGB。

源像素與目標像素在不同混合模式下計算顏色的規則如下所示:

CLEAR:[0, 0]

SRC:[Sa, Sc]

DST:[Da, Dc]

SRC_OVER:[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc]

DST_OVER:[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc]

SRC_IN:[Sa * Da, Sc * Da]

DST_IN:[Sa * Da, Sa * Dc]

SRC_OUT:[Sa * (1 - Da), Sc * (1 - Da)]

DST_OUT:[Da * (1 - Sa), Dc * (1 - Sa)]

SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]

DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]

XOR:[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]

DARKEN:[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]

LIGHTEN:[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]

MULTIPLY:[Sa * Da, Sc * Dc]

SCREEN:[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]

ADD:Saturate(S + D)

OVERLAY:Saturate(S + D)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。