情景
需求:实现一个简单的图片编辑功能,图片在编辑时可缩放、滑动和设置圆角弧度等操作。
我通过继承ImageView重写onDraw()给图片添加一个半透明灰色罩层,通过Matrix进行图像的缩放和滑动,使用ScaleGestureDetector监听双指的滑动,这都是一些比较常规的操作。但是在保存编辑后的图片时出现了问题:
很显然圆角裁剪弧度不对。通俗来说就是四个圆角[不够圆]。下面是裁剪图片的逻辑:
1 | //方形裁剪: |
圆角弧度系数corner是用户设定的,理论上来用户所见即所得。
问题分析
圆角问题
canvas.drawRoundRect(rectF, corner, corner, paint)这个方法的定义是:
drawRoundRect(rectF: RectF, rx: Float, ry: Float, paint: Paint)
首先我们要知道 rx 和 ry的意思:
从图中可以看出来当rx和ry的值保持不变的情况下:
矩形的尺寸越小,则看起来[越圆]
矩形尺寸越大,则看起来[没那么圆]
而我们的问题就是实际裁剪到的圆角图片[不够圆],也就是说可能是图片尺寸变大了。
打印一下裁剪前后bitmap的尺寸:
可以看到裁剪后bitmap的宽高更大了。再看看我们保存图片:
8.22MB的图片!!原图1.15MB。那么为什么裁剪后圆角与预定不符的原因找到了:裁剪后图片尺寸变大了。
到这里我们遇到了一个很重要的问题:图片内存问题。
图片内存问题
理论
几百KB的图片经过编辑变成了8.22MB?Why?
首先我们知道一张图片是由一个个像素点组成的。举例:一张尺寸1080x2028的图片,它就是由1080x2028个像素点组成的。
而一个像素点可以表示三种颜色,也就是三原色(红、绿、蓝),另外加上一个透明度。三原色+透明度,我们称为四条通道。
而一条通道的大小也是不一样的,可以是8bit,表示有0-255个值;可以是4bit,表示有0-16个值。一条通道越大那么该图片的分辨率就越大。举例红色通道,如果是8bit,那么它可以表示255种红色。使用4bit就只能表示16种红色。
Android开发中遇到的 ARGB_8888 就表示了这张图片的信息:四通道,每条通道8bit。RGB_565 表示三通道(不支持透明度),每条通道大小分别为5bit,6bit和5bit。
ARGB_8888 图片一个像素点占用内存是:4 * 8bit / 8 = 4 个字节。如果该图尺寸为1080x2028,那么该图占用内存1080 * 2028 * 4 = 8760960 Byte。
那么影响一张图片的大小的因素就显而易见了:尺寸和质量。质量包含通道数量和每条通道大小。
实际代码
从上面那个例子来看,原图1.15MB裁剪后变成8.22MB的原因是尺寸变大了。那么尺寸变大的位置是:
1 | //方形裁剪: |
查看源码Bitmap.createBitmap()源码(下面给出伪代码):
1 | public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter) { |
可以发现当传入Matrix的时候,会直接从Matrix获取width和height作为新bitmap的宽和高。也就是说我们实际使用createBitmap()创建的bitmap的尺寸是传入Matrix的尺寸,如果Matrix进行了缩放操作,那么创建的bitmap的尺寸也会发生变化。
问题解决
既然知道是因为使用Matrix进行放大导致的裁剪后图片尺寸变大,那么我们可以使用scale记录一下放大的倍数,然后在createBitmap()之前对Matrix进行一个等倍数缩小操作就行了。
1 | //方形裁剪: |
等倍数缩小尺寸,那么裁剪出来的图片和原来的尺寸一致,圆角的问题自然就解决了。对用户来说,所见即所得。