Paint PorterDuffXfermode详解

作者: hewking | 来源:发表于2018-12-23 21:00 被阅读3次

    多年以来自己都有一个毛病,知识或者说技术储备看到过或者知道在哪里就觉得自己掌握了,但实际上并没有,当自己开始做,两眼抓瞎。最好的例子在于ImageView scaleType 相关的。以前一直记不住,直到我写demo 去对比区别才很清楚明白,所以对于PorterDuffXfermode 也一样。

    以下理论解释来自于Xfermode in android

    什么是Xfermode

    有三个实现类: AvoidXfermode,PixelXorXfermode,PorterDuffXfermode

    AvoidXfermode

    AvoidXfermode xfermode will draw the src everywhere except on top of the
    opColor or, depending on the Mode, draw only on top of the opColor.

    官方解释,按照我的理解是如果想把原来图像进行处理,比如绿色换成红色,可以使用。这里有个容差值的概念,比如红色是0xff0000,但在一定范围内都是红色,如果设置一个容差,在范围内的 各种符合要求的红色 都会被处理。

    PixelXofXermode

    没设么用,不支持硬件加速

    接下来说说重点,也就是最常用的

    PorterDuffXfermode

    Porter-Duff 来由

    Porter-Duff 操作是 1 组 12 项用于描述数字图像合成的基本手法,包括
    Clear、Source Only、Destination Only、Source Over、Source In、Source
    Out、Source Atop、Destination Over、Destination In、Destination
    Out、Destination Atop、XOR。通过组合使用 Porter-Duff 操作,可完成任意 2D
    图像的合成。
    Thomas Porter 和 Tom Duff 发表于 1984年原始论文的扫描版本

    可以支持任何2D图像的合成。理论支撑

    PorterDuffXfermode 各种模式之间的区别

    有哪些种类?

    android 中共有18种不同模式,分别是:

    • CLEAR
    • SRC
    • DST
    • SRC_OVER
    • DST_OVER
    • SRC_IN
    • DST_IN
    • SRC_OUT
    • DST_OUT
    • SRC_ATOP
    • DST_ATOP
    • XOR
    • DARKEN
    • LIGHTEN
    • MULTIPLY
    • SCREEN
    • ADD
    • OVERLAY

    文档解释

    public enum Mode {
        /** [0, 0] */
        CLEAR       (0),
        /** [Sa, Sc] */
        SRC         (1),
        /** [Da, Dc] */
        DST         (2),
        /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
        SRC_OVER    (3),
        /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
        DST_OVER    (4),
        /** [Sa * Da, Sc * Da] */
        SRC_IN      (5),
        /** [Sa * Da, Sa * Dc] */
        DST_IN      (6),
        // ...以下省略
    

    结合Paint 如何使用

    1.声明Paint

        private val paint by lazy {
            Paint().apply {
                isAntiAlias = true
                color = ContextCompat.getColor(ctx, R.color.app_color_blue_2_pressed)
                style = Paint.Style.FILL
            }
        }
    

    2.对Paint 设置

                paint.xfermode = PorterDuffXfermode(modes[i])
    
    

    3.canvas 绘制

                // draw dst
                canvas.drawBitmap(makeDst()
                ,0f,0f,paint)
    
                paint.xfermode = PorterDuffXfermode(modes[i])
                // draw src
    
                canvas.drawBitmap(makeSrc(),0f,0f,paint)
                paint.xfermode = null
    

    XfermodeSampleView 分析

    先上图

    为了仔细对比以及理解各种模式之间的区别,以及使用中遇到的问题,还有疑惑,接下来仔细的分析各种出现的情况。

    1. 首先xfermode 绘图需要两部分,DST,SRC 两种。可以理解为DST 在下边,SEC在上面。也就是说DST先绘制,SRC 后绘制。看一下代码生成
        fun makeSrc() : Bitmap{
            val radius = rectSize.div(3f)
            val bitmap = Bitmap.createBitmap(rectSize
                    ,rectSize,Bitmap.Config.ARGB_8888)
            val c = Canvas(bitmap)
            val p = Paint().apply {
                style = Paint.Style.FILL
                color = ContextCompat.getColor(context, R.color.app_color_theme_3)
            }
            c.drawRect(radius,radius,rectSize.times(0.75f),rectSize.times(0.75f),p)
            return bitmap
        }
    
        fun makeDst() : Bitmap{
            val radius = rectSize.div(3f)
            val bitmap = Bitmap.createBitmap(radius.times(2).toInt()
                    ,radius.times(2).toInt(),Bitmap.Config.ARGB_8888)
            val c = Canvas(bitmap)
            val p = Paint().apply {
                style = Paint.Style.FILL
                color = ContextCompat.getColor(context, R.color.app_color_blue_2_pressed)
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                c.drawOval(0f,0f,radius.times(2),radius.times(2),p)
            }
            return bitmap
        }
    
    

    这一段代码需要理解的地方在于,canvas.drawRect,canvas.drawOval,为什么是传递这些参数。比如说 c.drawOval(0f,0f,radius.times(2),radius.times(2),p),为什么传的left = 0f,top = 0f,right = 2 * radius,bottom = 2 * radius。首先我们创建宽高为 2 * radius。 所以画布总的大小为固定2 * radius 大小。canvas 坐标还是以左上有起点。

    1. 在对DST,SRC进行绘制的时候,为什么是需要传入bitmap 呢?
      例如 canvas.drawBitmap(makeSrc(),0f,0f,paint)
      在之前的测试中也写过不是bitmap的情况。直接drawOval ,drawRect。但是情况跟现在的情况完全不一样。各种模式之间的混合不如预期,也只有现在通过bitmap 之间的混合才会生效。
      一下错误的用法(具体原因不是很明确)
                canvas.drawCircle(cx, cy, radius, paint)
    
                paint.xfermode = PorterDuffXfermode(modes[i])
                paint.color = ContextCompat.getColor(context, R.color.app_color_theme_3)
                // draw src
                canvas.drawRect(cx, cy
                        , cx + radius.times(2), cy + radius.times(2), paint)
    
                paint.xfermode = null
    

    可以看到这两种绘制的方式,一种通过生成biamp,为它创建canvas 并且绘图。另一种直接使用canvas 去绘制。本来我认为这两种没有区别,也不会有问题。可实际上出现了问题。没有达到预期的混合。具体的原因目前还没有明确,我猜测可能是因为通过生成bitmap,这是本来已经在新的画布上绘制的。而且xfermode 本来就是图像的绘图混合。drawRect,drawCircle本身不是图像。所以会有根本的差异。也就是说使用xfermode 要在于场景,在drawBitmap()中去使用。
    采用drawXXX 的方式,混合错误,如下


    错误的使用
    1. 还有一个问题也是混合中很常见的。混合之后会出现混合处会有黑色占位的情况。对于这样的情况,很多次没有搞明白的时候我都是拒绝的。这到底是什么情况? 现在我可以理解为是因为dst,src混合的窗口是透明的,其实对于这种解释,也很疑惑,因为调用过canvas.drawColor(Color.WHITE)使canvas 背景为白色,可惜这样也会有问题。在混合之后,进行裁剪了。就黑色了。这样子我还没有找到根本确切的解释。但是解决方法是有的。在进行混合之前需要保存画布
    val sc = canvas.saveLayer(posX.toFloat() * rectSize
                        , posY.toFloat() * rectSize
                        , (posX.toFloat() + 1) * rectSize, (posY.toFloat() + 1) * rectSize, null,
                        Canvas.ALL_SAVE_FLAG )
                        
                        ...
                        canvas.restoreToCount(sc)
                        
    

    可以看到调用结束之后,恢复了画布。关于canvas 的saveLayer,canvas.restoreToCount的分析理解,请看另一篇文章。还有为什么是saveLayouer 里面是一部分保存。而不是canvas.save() 它们有什么区别?

    可以看到通过restoreToCount 的处理,并没有黑色部分了。结果也很符合预期

    1. 对于背景的添加,这里用到了bitmapShader。也是Paint 另外一个很值得好好理解的方法paint.setShader(),shader 也就是着色器
    • 创建bitmapShadow
            // make a ckeckerboard pattern
            val bm = Bitmap.createBitmap(intArrayOf(-0x1, -0x333334, -0x333334, -0x1), 2, 2,
                    Bitmap.Config.RGB_565)
            mBG = BitmapShader(bm,
                    Shader.TileMode.REPEAT,
                    Shader.TileMode.REPEAT)
    
            val m = Matrix()
            m.setScale(6f, 6f)
            mBG.setLocalMatrix(m)`
    
    

    具体的参数意义,以及mBg.setLocalMatrix 以后在好好写一下。

    • 使用
                paint.setShader(mBG)
                // draw bg
                canvas.drawRect(x,y, x + rectSize.toFloat() - 25,y + rectSize.toFloat(),paint)
                paint.setShader(null)
    

    在这里,paint.setShadow() 设置好shadow,在drawRect中会把创建bitmapShader 传入的bitmap 绘制上去。至于绘制的顺序,比如是绘制的shader 的bitmap 和 canvas.drawCircle 的图形,谁在上谁在下。可以认为shader 是在最下的。

    • 清除shader
    paint.setShadow(null)
    设置为Null 即可去除
    

    相关文章

      网友评论

        本文标题:Paint PorterDuffXfermode详解

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