Android Canvas Matrix —— Bitmap是如何画出来的

上一篇《Android Canvas Matrix —— 从CircleImageView说起》是我对CircleImageView项目的学习和思考,这篇说一下从“绘制圆形头像”这个出发点深入下去的收获——如何将Bitmap绘制出来,如何控制Bitmap的绘制过程。

这篇博文只讨论绘制过程,所以不考虑Bitmap的获取和解析。
draw_bitmap

参见上图,绘制Bitmap的方法,基本有两种:1. Canvas.drawBitmap();方法。2. 和CircleImageView中的做法一样,使用BitmapShader,将Bitmap作为Paint的属性。

那么对Bitmap绘制过程的控制,也自然从这两方面入手,因为上一篇博文已经谈到了Shader的用法,这里重点讨论第一种方法——drawableBitmap()。

这个方法中,参与的对象有Canvas、Bitmap和Paint,分别控制这三个要素都可以达到控制Bitmap绘制的效果。Canvas的来源有两个,Bitmap和View。所以对于Bitmap的控制,也就是对于Canvas的控制(就是在绘制之前,先对Bitmap进行重绘,在重绘的过程中进行变换),所以主要就是对Canvas和Paint的调整。

对于Canvas,使用Matrix进行变换,在平面中的变换方式主要有scale、rotate、translate三种。这里先插入一段我对Canvas的理解:

不同于网上通常将Canvas理解为画布,我更倾向于将Canvas理解为坐标系(coordinate)。一个原因就是实际上我们在View或Bitmap上绘制的内容,都是绘制到了屏幕或Bitmap上,都是有限的大小,目标介质才是真正意义上的“画布”,而Canvas是无限大的。将Canvas理解为坐标系之后,Canvas实际上就成了我们绘制内容的 参考基准 ,这很重要。所以我们绘制内容的时候,要知道我们是基于什么样的坐标系进行绘制的。
有了上面的理解,下面的内容就容易的多了。首先,默认的Canvas的原点,在绘制区域的左上角,如果对Canvas进行位移,效果是这样的:

canvas

上图中,右侧红色位置为偏移后的坐标系,如果我们使用Canvas绘制一个矩形,那么位移前后绘制的效果就可想而知了。

因为Canvas内部也是通过Matrix进行控制变换的,所以这些变换的效果,同样适合于任意使用Matrix控制的元素,包含Shader,所以在上一篇博文中,CircleImageView库中对居中的操作,原理上和上图是一样的。

需要注意的的是,通过Matrix进行变换,那么Matrix必然会保留变换之后的状态,那么如果进行多次变换,就会造成变换的累加,我们要善用累加,避免因为累加造成的困惑。Canvas提供了save();和restore();两个方法对Canvas的状态进行暂存和恢复,当我我们需要在不同的坐标系状态下绘制不同的内容时,就需要使用save();和restore();,这两个方法配合的效果,类似“LS打法”,在将Canvas变换前,先使用save();方法,保存Cavnas的状态,绘制完之后,使用restore();方法恢复到上次save();的状态,进行下一次变换和绘制。

注意:这里再一次关联到上文提到的我对Canvas是坐标系的理解,因为虽然进行了restore();操作,但是之前绘制的内容是不会被清除的,就是说save();和restore();只是针对坐标系的变换而言。也之所以这样,才给了绘制多个内容的可能。

使用save();和restore();的效果如下图:

canvas_multiple

 

理解了Canvas的真正含义和Matrix的变换方法,以及“LS打法”的操作,对于Canvas绘制图像的基础,应该没有问题了,下面说一下如何对图片进行“裁切”。这里之所以用了引号,因为这个“裁切”实际上是遮罩——只显示需要显示的内容,对不需要显示的内容进行遮挡或拦截。

通过Paint的setXfermode()方法,设置Paint的遮罩模式,即可对稍后绘制的内容应用遮罩效果。还是以圆形头像为例,通过下面的方法,即可将Bitmap“裁剪”成圆形:

// desRect 目标绘制区域范围
// rect Bitmap范围
canvas.drawCircle(desRect.centerX(), desRect.centerY(), desRect.right - desRect.centerX(), paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmapimg, rect, desRect, paint);

通过设置Mode.SRC_IN的模式,将使得drawBitmap();的结果,只在之前绘制的circle内部可以显示出来,也就是SRC_IN的含义。这样就可以得到一个圆形的Bitmap(代码中得canvas是新建的Bitmap中的canvas)。