android PorterDuffXferMode真正的效果測試集合(對比官方demo)

前言

當(dāng)時(shí)在做頭像imageView,就是切圓頭像
1、如果我們先畫一個(gè)circle(非bitmap),然后setXfermode 為Src_In,再畫一個(gè)bitmap(圖片的)。成功,完美 。

成功

2、如果我們先畫一個(gè)bitmap(圖片的),然后setXfermode 為Dsr_In,再畫circle。粗略學(xué)習(xí)了網(wǎng)上那張圖之后,理論上應(yīng)該也是成功的,但是卻出現(xiàn)了問題。

不成功

所以,我很疑惑,就開始了各種探索學(xué)習(xí)測試。(當(dāng)然,至于為什么出現(xiàn)這種情況,文末會有解釋)

到目前為止已經(jīng)被PorterDuffXferMode坑了有1天時(shí)間了,網(wǎng)上看了無數(shù)的文章,很亂很雜,沒有寫得能夠讓我很清楚很明白且有點(diǎn)權(quán)威的文獻(xiàn),因?yàn)楹芏鄾]有測試結(jié)果,并且我沒有親身實(shí)踐過,所以,現(xiàn)在我要重新自己動手實(shí)踐一下。

在網(wǎng)上搜羅了一大圈,在群里和很多人交流了,大概有2篇文章,個(gè)人認(rèn)為說得在理。(建議大家先去看一下,不過可能你會跟我一樣,看了之后就更云里霧里,但是還是需要親身實(shí)踐為好)

感謝兩位作者。

PorterDuffXferMode不正確的真正原因PorterDuffXferMode深入試驗(yàn)

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

第一篇主要總結(jié):
如果想讓PorterDuffXferMode按照預(yù)期Demo(或者效果圖)的效果圖像實(shí)現(xiàn),必須滿足以下條件:
1、關(guān)閉硬件加速。(經(jīng)過作者修改為 開啟硬件離屏緩存)
2、兩個(gè)bitmap大小盡量一樣。
3、背景色為透明色。
4、如果兩個(gè)bitmap位置不完全一樣,可能也是預(yù)期效果,只不過你看到的效果和你自己腦補(bǔ)的預(yù)期效果不一致。

第二篇主要總結(jié):
PorterDuffXfermode用于實(shí)現(xiàn)新繪制的像素與Canvas上對應(yīng)位置已有的像素按照混合規(guī)則進(jìn)行顏色混合。


我主要是在第一篇的結(jié)論基礎(chǔ)上去做測試,并給大家展示測試結(jié)果。
下面高能篇幅,如果你不是真正在學(xué)習(xí)研究PorterDuffXfermode踩坑的人,如果你沒有耐心閱讀的,就可以別往下看了。
亦或你也想親手嘗試,那還是先去嘗試一下吧。


首先代碼圖:

public class TestXfermodeView extends View {
    public TestXfermodeView(Context context) {
        super(context);
    }

    public TestXfermodeView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestXfermodeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


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

        Bitmap circle = getCircleBitmap();
        Bitmap rectangle = getRetangleBitmap();

//        int sc = canvas.saveLayer(0, 0, 400, 400, 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);

        /**
         * 開啟硬件離屏緩存
         */
        setLayerType(LAYER_TYPE_HARDWARE, null);
        Paint paint = new Paint();
        /**
         * 畫bitmap的也透明
         */
        canvas.drawARGB(0, 0, 0, 0);
//        canvas.drawCircle(100, 100, 100, paint);
        canvas.drawBitmap(rectangle, 100, 100, paint);
//        Bitmap b= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
//        Rect rect = new Rect(0, 0, 100, 100);
//        canvas.drawBitmap(b,rect, rect, paint);
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));

//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.OVERLAY));
//        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
        canvas.drawBitmap(circle, 0, 0, paint);
//        canvas.restoreToCount(sc);

    }

    @NonNull
    private Bitmap getRetangleBitmap() {
        /**
         * bm1 在bitmap上面畫正方形
         */
        Bitmap rectangle = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
        Canvas c1 = new Canvas(rectangle);
        Paint p1 = new Paint(Paint.ANTI_ALIAS_FLAG);
        p1.setColor(getResources().getColor(R.color.colorAccent));
        /**
         * 設(shè)置透明
         */
        c1.drawARGB(0, 0, 0, 0);
        c1.drawRect(0, 0, 200, 200, p1);
        return rectangle;
    }

    @NonNull
    private Bitmap getCircleBitmap() {
        /**
         * bm 在bitmap上面畫圓
         */
        Bitmap circle = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(circle);
        /**
         * 設(shè)置透明
         */
        c.drawARGB(0, 0, 0, 0);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        p.setColor(getResources().getColor(R.color.colorPrimary));
        c.drawCircle(100, 100, 100, p);
        return circle;
    }
}

這倆方法 就是在2個(gè)新的bitmap上畫圓和正方形
注意是跟官方demo一致 ** 先畫 正方形 后畫 圓**
滿足條件
2、兩個(gè)bitmap大小盡量一樣。
3、背景色為透明色。

onDraw中也設(shè)置了透明和開啟硬件離屏緩存

 setLayerType(LAYER_TYPE_HARDWARE, null);
        Paint paint = new Paint();
        /**
         * 畫bitmap的也透明
         */
        canvas.drawARGB(0, 0, 0, 0);

滿足條件
1、開啟硬件離屏緩存(順便說一下它的好處)
- 1.解決xfermode黑色問題。
- 2.效率比關(guān)閉硬件加速高3倍以上

好正式開始 Xfermode的條件測試:

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
PorterDuff.Mode.SRC

可以看出圓的canvas背景(透明,activity本身就是白色,所以這里為白色)也顯示出來并且覆蓋在了正方形上面

 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST));
PorterDuff.Mode.DST

可以看出后畫的圓已經(jīng)不顯示了

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));

PorterDuff.Mode.SRC_IN

可以看出圓的canvas背景顯示出來, 只取了與正方形相交的部分(是canvas背景區(qū)域相交的部分),并且相交部分顯示的是后畫的顏色

 paint.setXfermode(newPorterDuffXfermode(PorterDuff.Mode.DST_IN));

PorterDuff.Mode.DST_IN

可以看出圓的canvas背景顯示出來, 只取了與正方形相交的部分(是canvas背景區(qū)域相交的部分),并且相交部分顯示的是先畫正方形的顏色

 paint.setXfermode(newPorterDuffXfermode(PorterDuff.Mode.XOR));

PorterDuff.Mode.XOR

可以看出取的是相交部分之外,并且與官方demo效果一致

 paint.setXfermode(newPorterDuffXfermode(PorterDuff.Mode.CLEAR));

PorterDuff.Mode.CLEAR

可以看出后畫不見了 ,并且相交的部分也不見了

這前面幾個(gè)是相對比較常用,也是比較重要的。

后面的我直接列出來

PorterDuff.Mode.SRC_OUT
PorterDuff.Mode.SRC_ATOP
PorterDuff.Mode.SRC_OVER
PorterDuff.Mode.DST_OUT
PorterDuff.Mode.DST_ATOP
PorterDuff.Mode.DST_OVER
PorterDuff.Mode.ADD
PorterDuff.Mode.MUTIPLY
PorterDuff.Mode.DARKEN
PorterDuff.Mode.OVERPLAY
PorterDuff.Mode.SCREEN

總覽全局

官方demo
我的
第二篇文獻(xiàn)里面的

注:我的是跟官方demo一樣先畫正方形后畫圓,第二篇文獻(xiàn)里面的圖是先畫圓后畫正方形,可以對比一下,體會一下。

我說說我們的demo和官方api demo的區(qū)別:

  • 我的demo里面,兩個(gè)bitmap大小一樣,是在bitmap里面填充畫滿 circle和rectangle,并且在畫兩個(gè)Bitmap的時(shí)候,是調(diào)整的它的位置來畫的;
  • 官方的demo里面,是兩個(gè)bitmap大小一樣,circle的bitmap里面只填充了 左上角2/3,而rectangle的bitmap里面只填充了 右下角的2/3,并且在畫的時(shí)候,兩個(gè)bitmap的位置大小都是一樣的

我的

circle的bitmap
rectangle的bitmap
canvas.drawBitmap(rectangle,**100, 100**, paint);
canvas.drawBitmap(circle, 0, 0, paint);

官方的


circle的bitmap
rectangle的bitmap
canvas.drawBitmap(mSrcB, **0, 0**, paint);
canvas.drawBitmap(mDstB, 0, 0, paint);

大家可以再去對比對比兩張總結(jié)圖,思考思考。
如果想要探尋為什么不同的方法會導(dǎo)致不同的效果,也可以去閱讀頁首的第二篇文章

總結(jié)

如果你想要做出實(shí)際效果,那么你要按照官方的那種方式,你就能夠做出跟網(wǎng)上普遍流傳的那張圖一樣的效果。

如果你要根據(jù)自己的實(shí)際情況來,那么你可能就要考慮我的這種方式和官方的那種方式來決定怎么做了。

至于文首的問題,原因如下:
因?yàn)槲覀兊腦fermode 疊合裁剪,都是建立在不同的層級上,重新畫一個(gè)bitmap會新開一層。
第一種:先畫circle 在canvas那層,再畫Bitmap,新開了一層,中間鑲嵌Xfermode,成功。
第二種: 先畫bitmap,新開了一層,再畫circle,還是在bitmap那層,中間鑲嵌 Xfermode,不成功。

解決方案:

 //第一種
        
        canvas.drawCircle(scaleBitmap.getWidth() / 2, scaleBitmap.getHeight() / 2, scaleBitmap.getWidth() / 2, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

        canvas.drawBitmap(scaleBitmap, rect, rect1, paint);
//第二種
        canvas.drawBitmap(scaleBitmap, rect, rect1, paint);

        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
      
        Bitmap bitmap = Bitmap.createBitmap(scaleBitmap.getWidth(), scaleBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas1 = new Canvas(bitmap);
        Paint p = new Paint();
        canvas1.drawARGB(0, 0, 0, 0);
        canvas1.drawCircle(scaleBitmap.getWidth() / 2, scaleBitmap.getHeight() / 2, scaleBitmap.getWidth() / 2, p);
        canvas.drawBitmap(bitmap, rect, rect1, paint);

ps:寫這篇文章呢,主要是我個(gè)人想要實(shí)踐一下,順便記錄一下實(shí)驗(yàn)結(jié)果。最后也順便分享出來,個(gè)人感覺這里確實(shí)有很多坑,如果你是真正在研究PorterDuffXformode的人,那你肯定會跟我一樣很疑惑,希望這篇文章能夠帶給你一個(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)容