美文网首页
Android Paint XferMode踩坑记

Android Paint XferMode踩坑记

作者: 星空下奔跑 | 来源:发表于2018-04-30 23:06 被阅读0次

    前言

    某天,在网上看到一个水波纹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)

    相关文章

      网友评论

          本文标题:Android Paint XferMode踩坑记

          本文链接:https://www.haomeiwen.com/subject/nfytrftx.html