Matrix、Rect、PorterDuff.Mode

文章讲述了在使用Canvas进行图形绘制时,如何通过Rect类描述矩形并理解PorterDuff.Mode的不同模式,以及为何直接使用drawCircle和drawRect会导致与预期效果不符,通过实例分析解决了这一问题。
摘要由CSDN通过智能技术生成

Matrix

如下是四种变换对应的控制参数:

Rect

常用的一个“绘画相关的工具类”,常用来描述长方形/正方形,他只有4个属性:

public int left;

    public int top;

    public int right;

    public int bottom;

这4个属性描述着这一个“方块”,但是这有一个知识点需要理清楚,先看这张图

本Rect最左侧到屏幕的左侧的距离是 left 

本Rect最上面到屏幕上方的距离是 top 

本Rect最右侧到屏幕左侧的距离是 right 

本Rect最下面到屏幕上方的距离是 bottom

这四个属性不单单描述了这个长方形4个点的坐标,间接的描述出这个长方形的尺寸:

长 = bottom - top 

宽 = right - left
 

PorterDuff.Mode

PorterDuff.Mode在自定义View时经常用到,但具体使用的效果,往往是通过不断尝试,而后选择某个Mode来实现效果,这里详细解析下。

先说些通俗的概念:

PorterDuff.Mode源码:

在设置时,调用setXfermode,最终调用nSetXfermode方法,继续追踪:

会发现这是个native方法。

能从源码看到的内容并不多,这个方法用于设置图像的过渡模式。

所谓的过渡是指图像的饱和度、颜色值等参数的计算结果的图像表现。

看下Mode的枚举类型定义:

//其中Sa全称为Source alpha表示源图的Alpha通道;Sc全称为Source color表示源图的颜色;Da全称为Destination alpha表示目标图的Alpha通道;Dc全称为Destination color表示目标图的颜色,[...,..]前半部分计算的是结果图像的Alpha通道值,“,”后半部分计算的是结果图像的颜色值。
    //效果作用于src源图像区域
    private static final Xfermode[] sModes = {
            //所绘制不会提交到画布上
            new PorterDuffXfermode(PorterDuff.Mode.CLEAR),
            //显示上层绘制的图像
            new PorterDuffXfermode(PorterDuff.Mode.SRC),
            //显示下层绘制图像
            new PorterDuffXfermode(PorterDuff.Mode.DST),
            //正常绘制显示,上下层绘制叠盖
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER),

            //上下层都显示,下层居上显示
            new PorterDuffXfermode(PorterDuff.Mode.DST_OVER),
            //取两层绘制交集,显示上层
            new PorterDuffXfermode(PorterDuff.Mode.SRC_IN),
            //取两层绘制交集,显示下层
            new PorterDuffXfermode(PorterDuff.Mode.DST_IN),
            //取上层绘制非交集部分,交集部分变成透明
            new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT),

            //取下层绘制非交集部分,交集部分变成透明
            new PorterDuffXfermode(PorterDuff.Mode.DST_OUT),
            //取上层交集部分与下层非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP),
            //取下层交集部分与上层非交集部分
            new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP),
            //去除两图层交集部分
            new PorterDuffXfermode(PorterDuff.Mode.XOR),

            //取两图层全部区域,交集部分颜色加深
            new PorterDuffXfermode(PorterDuff.Mode.DARKEN),
            //取两图层全部区域,交集部分颜色点亮
            new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN),
            //取两图层交集部分,颜色叠加
            new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY),
            //取两图层全部区域,交集部分滤色
            new PorterDuffXfermode(PorterDuff.Mode.SCREEN),

            //取两图层全部区域,交集部分饱和度相加
            new PorterDuffXfermode(PorterDuff.Mode.ADD),
            //取两图层全部区域,交集部分叠加
            new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
    };

上述代码数中注释了该模式的alpha通道和颜色值的计算方式;

alpha_{out} : 最终输出的alpha通道的值

C_{out} : 最终输出的颜色值

alpha_{src} : 原图的alpha通道值

alpha_{dst} : 目标图的alpha通道值

C_{src} : 原图的颜色值

C_{dst} : 目标图的颜色值

知道了这些值的含义,代入计算公式,即可计算出最终该模式下的alpha通道和颜色值,也即是最终显示出来的效果。

接下来看下图像合成效果示意图:

一目了然,可以清晰的看到不同的模式显示的效果,这也是几乎每一篇相关文档中必不可少的图,之前也从未做过验证,偶尔使用的时候,发现效果不太一致,重新尝试其它值,最终找到想要的效果,就算结束,从未真正了解过,为什么使用的时候,与图中的效果并不一致,仔细研究过后,才发现,这里有个坑,需要注意,具体的细节,继续往下看。

在打算写Demo的时候,看到上图的效果,那么我们第一反应,必然是简单画一个圆形,在画一个矩形,而后使用PorterDuff.Mode来实现想要的效果,同时,我也是这么做的:

private fun canvasDrawRect(canvas: Canvas) {
    //创建图层
    val count = canvas.saveLayer(50f, 50f, width.toFloat() - 50f, height.toFloat() - 50f, mModePaint)
    //设置一个灰色背景
    canvas.drawColor(Color.GRAY)
    //将Paint的颜色设置为红色
    mModePaint.color = Color.RED
    //使用 canvas.drawCircle 绘制圆形,(以为这就是DST)
    canvas.drawCircle(width/2f, height/2f, mCircleRadius, mModePaint)  //DST
    //Paint设置蓝色
    mModePaint.color = Color.BLUE
    //设置模式
    mModePaint.xfermode = mDuff
    //绘制一个蓝色的矩形(SRC)
    canvas.drawRect(mRect, mModePaint)
    //清除模式
    mModePaint.xfermode = null
    canvas.restoreToCount(count)
}

为了能够对照鲜明,我是先在画布上绘制一个绿色的底图,而后才绘制上层的灰色背景和红圆蓝方。

完整的图:

下面效果的绿色部分,即为透明部分:

运行后的效果:

对照图像合成效果示意图,不能说一模一样,只能说没啥关系。

为什么会出现这样的问题?

在仔细查看代码后,并未从代码处看到什么问题。

再次查看参考的网上文档,我发现了不同之处:

文档中,圆形与矩形都是通过Bitmap的方式实现的,并未如我写的一般,直接使用canvas.drawCircle和canvas.drawRect,再次看到 图像合成效果示意图 几个字,灵光一闪,难道是,必须是两个bitmap,才能实现和合成效果示意图上一样的效果。

立刻着手切换:

创建了两个Bitmap,并在两个Bitmap上分别绘制了红圆与蓝色矩形。

private fun canvasDrawBitmap(canvas : Canvas) {
    val count =
        canvas.saveLayer(50f, 50f, width.toFloat() - 50f, height.toFloat() - 50f, mModePaint)
    //绘制红圆的bitmap
    mCircleBitmap?.let {
        canvas.drawBitmap(it, mMatrix, mModePaint)
    }
    //设置叠加模式
    mModePaint.xfermode = mDuff
    //绘制蓝色矩形的bitmap
    mRectBitmap?.let {
        canvas.drawBitmap(it, mMatrix, mModePaint)
    }
    //清除叠加模式
    mModePaint.xfermode = null
    canvas.restoreToCount(count)
}

迫不及待,运行!

(手动撒花)

出来的效果与效果示意图一致,长舒口气,看来,问题确实出现在这里,此时再回头看,发现概念里的过渡模式,已经说明,是图像表现,只是,当时未能理解。

这就是踩的最大的一个坑,这也是我在使用时效果总是区别于效果示意图的主要原因。

至此,已经完成想要的效果。

但此时,又想到,当使用canvas.drawCircle 与 canvas.drawRect,为什么会出现如图所示的效果,如果说完全不起作用,可明显还是有变化的。再次回到第一次实现的效果处,仔细思索。

若canvas.drawRect之前绘制的当作DST, 之后绘制的rect为SRC,是否正确?

显然不是:从上图看到,显示红色的圆未与矩形相交的部分,从始至终都是显示出来的,显然,PorterDuff.Mode计算的值,并不包含红色圆未与矩形相交的部分,反之,PorterDuff.Mode 影响的只有矩形部分。

按照这个逻辑,绿色矩形为SRC, 矩形下面灰色的部分与红色圆的部分为DST, 只不过,此时的两张图是完全相交的。 就比如:我们创建两个矩形,一个红色,一个绿色,它俩的大小,位置一模一样,那么出来的效果,是否就是我们一开始使用canvas.drawRect 和 canvas.drawCircle实现之后的效果。

经验证,确实如此,这里就不再贴代码与效果。

  所以,使用canvas.drawCircle 和 canvas.drawRect实现的效果完全就是两部分相交时显示的效果,即我们可以看效果图圆与矩形相交部分,

  若效果图相交部分是透明,则实现效果:显示的是底图绿色,

  若效果图相交部分是蓝色,则实现效果:显示的是蓝色矩形

  若效果图相交部分是黄色,则实现效果: 显示的是未绘制蓝色矩形的效果

至此,最开始使用canvs.darwCircle 和 canvas.drawRect 为什么会出现如图的效果,也有了结果。

至此,结束,又解决一个问题!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值