首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PorterDuff采坑记

PorterDuff采坑记

作者头像
烧麦程
发布2022-05-10 20:40:00
发布2022-05-10 20:40:00
6300
举报
文章被收录于专栏:半行代码半行代码

因为以前 UI 相关的东西写的不多,今天看了下 Android 中图像合成的部分。顺便写了几个 demo 踩坑。内容比较简单,简单总结一下,分享给大家。

什么是 PorterDuff

porterduff 并不是Android 特有的概念,我们看一下 porterduff 的 wikipedua, 它是 1984 年由 ThomasPorterTomDuff 创建的数字图像合成算法,并且成为了计算机图形学基础的一部分。

这个算法的意思是,当2个图像进行合成的时候,必须知道合成对单个像素有什么影响。除了颜色通道(RGB),还有透明度(A),从而定义每个像素在和基础像素重叠的时候应该如何显示,或者下面一个图像的像素的颜色强度如何照到覆盖像素上。简单点说就是,2张图,叠在一起,不同的mode下,根据ARGB,会显示出不同的样子。

根据 Android 文档的介绍,合成算法大概分成下面几个模式:

我们分别定义一个源图像 src 和 目标图像 dest

【src】

【dest】

按照绘制顺序的话, dest先绘制,在下面。 src后绘制,在上方。

合成的模式大致有透明度合成模式和混合模式,这些在官网上有具体的介绍。在官方文档中,每个模式都会附带 2 个公式来计算输出部分的透明度和颜色。

其中 α 表示 alpha,C 表示 color。

至于每种模式,可以参考下面这张广为流传的图:

实践

效果是下面的,dest是蓝色的圆形,宽高是100*100, src是红色的矩形,宽高是 100 * 30。

直接放代码:

代码语言:javascript
复制
class XferModeView : View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    var paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
    private var mode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)

    var srcRect: RectF = RectF(0f, 70f, 100f, 100f)
    val bitmapCanvas = Canvas()
    var bitmap: Bitmap? = null

    override fun onDraw(canvas: Canvas?) {
        canvas?.let {
            val saveCount = it.saveLayer(
                0f,
                0f,
                canvas.width.toFloat(),
                canvas.height.toFloat(),
                null,
                Canvas.ALL_SAVE_FLAG
            )

            paint.color = Color.BLUE
            paint.style = Paint.Style.FILL
            it.drawCircle(
                50f, 50f,
                50f, paint
            )  // dst

            paint.color = Color.RED

            if (bitmap == null) {
                bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
            }

            bitmapCanvas.setBitmap(bitmap)
            bitmapCanvas.drawRect(srcRect, paint)
            paint.xfermode = mode
            canvas.drawBitmap(bitmap!!, 0f, 0f, paint)

            paint.xfermode = null
            it.restoreToCount(saveCount)

        }

    }
}

这里给这段demo做一下总结:

  1. 开始绘制之前需要调用 canvas.saveLayer,结束的时候调用 restoreToCountsaveLayer的意思是在下一步绘制的之前把所在的图层状态进行保存,后面的内容绘制在一个新的图层,这样旧图层的内容也不会影响最后的绘制效果。
  2. 关于 saveLayer,在文档介绍中,这是一个非常昂贵的操作,我们可能需要花费2倍以上的时间去绘制。在 xfermode的场景下,其实可以不调用 savelayer,直接在view初始化的时候开启硬件加速就可以实现同样的效果,并且这也是比较推荐的做法。
代码语言:javascript
复制
init {
    setLayerType(LAYER_TYPE_HARDWARE, null)
}
  1. bitmap,刚开始使用 porterDuff的时候,很想当然的直接切换mode, draw 2个图像,发现效果和文档上的示图完全不一致。最后看了很多其他的demo,发现都是先把 dest和 src 写到 bitmap里面的。

尝试了一下,发现实际上至少需要把 src 先画到 bitmap 上,然后再切换 mode,绘制到 canvas 上面。至于最后的bitmap的坐标(大小)如何控制呢,2个bitmap一样就可以了。仔细想想, porterduff 的算法其实是操作了像素点,那肯定是需要 bitmap 来操作的。类似场景还是都使用 srcbitmap和destbitmap来处理比较稳妥。

  1. bitmap的大小 在 Bitmap 创建的时候:
代码语言:javascript
复制
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)

这里的width和right和 dest 的 width,height一样就可以,像上面的demo示例,直接设置成view的 width, height 就行。

在 draw 的时候,需要调用:

代码语言:javascript
复制
canvas.drawBitmap(bitmap!!, 0f, 0f, paint)

当宽高和 view 一致的时候,left、top的坐标值也很简单,直接设置成 (0, 0) 即可。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 半行代码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是 PorterDuff
  • 实践
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档