Bitmap
一、Bitmap的存储格式以及内存计算
当需要做性能优化或者防止OOM时,我们通常会使用RGB_565这种类型。
因为ALPHA_8类型的bitmap只有透明度,用处不多。ARGB_4444显示图片不清晰。ARGB_8888占用内存空间最多。
BItmap类型 | 一个像素所占内存 |
---|---|
ALPHA_8 | 1字节 【8位( A:8)】 |
RGB_565 | 2字节 【16位(R:5;G:6;B:5)】 |
ARGB_4444 | 2字节【16位( A:4;R:4;G:4;B:4)】 |
ARGB_8888 | 4字节【32位( A:8;R:8;G:8;B:8)】 |
Bitmap所占内存大小 = 宽度像素 × 高度像素 × 一个像素所占的内存大小。
在Bitmap里有两个获取内存占用大小的方法:
-
getByteCount()
:API12加入,代表存储Bitmap的像素需要的最少内存。 -
getAllocationByteCount()
:API19加入,代表在内存中为BItmap分配的内存大小,代替了getByteCount()方法。
二、Bitmap的加载
如何加载一张图片呢?BitmapFactory 类提供了四种方法:decodeFile()
、decodeResource()
、decodeStream()
和decodeByteArray()
,分别用于从文件系统、资源、输入流以及字节数组中加载一个Bitmap对象,其中decodeFile和decodeResource又间接调用了decodeStream方法,这四种方法对应着底层实现中相应的native方法。
decodeFile():
BitmapFactory.decodeFile(pathName: String!, opts: BitmapFactory.Options!)
decodeResource():
BitmapFactory.decodeResource(res: Resources!, resId: Int, opts: BitmapFactory.Options!)
decodeStream():
BitmapFactory.decodeStream(is: InputStream?, outPadding: Rect?, opts: BitmapFactory.Options?)
decodeByteArray():
BitmapFactory.decodeByteArray(data: ByteArray!, offset: Int, length: Int, opts: BitmapFactory.Options!)
BitmapFactory.Options类
上面的四个方法都有一个相同的参数BitmapFactory.Options
,这个参数作用很大。
通过这个参数可以对bitmap进行一些配置,例如,设置采样率来减少bitmap的像素,防止OOM。
BitmapFactory.Options类中一些重要的成员变量和方法:inSampleSize
:设置采样率,对图片进行缩放,例如:inSampleSize = 2时,加载出来的图片的宽高分别原来的二分之一,内存大小是原来的四分之一,inSampleSize的值需为2的指数。当设置的值不为2的指数时,系统会自动向下选取一个最接近的2的指数来代替。
outWidth
:获取图片的宽度。
outHeight
:获取图片的高度。
inJustDecodeBounds
:布尔类型,当设置为true时,不获取图片,不分配内存,但可以获得图片的高度(options.outHeight
)、宽度(options.outWidth
)、MIME类型(options.outMineType
)等。
inScreenDensity
:获取当前屏幕的像素密度。
三、Bitmap的压缩方法
1、压缩格式
当对图片进行压缩时,有以下三种压缩格式:
CompressFormat | 描述 |
---|---|
Bitmap.CompressFormat.JPEG | 表示以JPEG压缩算法进行图片压缩,压缩后的格式可以是 .jpg 或者 .jpeg ,是一种有损压缩 |
Bitmap.CompressFormat.PNG | 表示以PNG压缩算法进行压缩,是一种支持透明度的无损压缩格式 |
Bitmap.CompressFormat.WEBP | 是一种支持有损压缩和无损压缩的图片文件格式,无损的webp格式图片体积比无损的png图片小 |
2、采样率压缩
如何压缩一张图片,使其所占内存变小?首先,最简单的方式就是通过设置采样率来缩小图片的尺寸,减小图片内存占用。
例如,ImageView的大小为100×100像素,而图片的原始大小为200×200,那么只需要将采样率inSampleSize设置为2即可。
如何确定采样率的值,可以通过下面这两个方法来计算得到一个适合的采样率。
fun decodeSampledBitmapFromResource(res: Resources, resId: Int, reqWidth: Int, reqHeight: Int): Bitmap{
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(res, resId, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(res, resId, options)
}
private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
val width = options.outWidth
val height = options.outHeight
println("width: $width ,height: $height")
var inSampleSize = 1
if (width > reqWidth || height > reqHeight) {
val halfHeight = height / 2
val halfWidth = width / 2
while ((halfWidth / inSampleSize) >= reqWidth && (halfHeight / inSampleSize) >= reqHeight){
inSampleSize *= 2
}
}
println("inSampleSize: $inSampleSize")
return inSampleSize
}
这里用的是decodeResource()方法来加载图片,其它三个decode方法也支持采样率加载,并且处理方式也是类似的。
所以,想要通过采样率来高效的加载图片,主要的步骤流程是这样的:
- 将
BitampFactory.Options
的inJustDecodeBounds
参数设为true; - 从
BitampFactory.Options
中取出图片的原始宽高信息,它们对应于outWidth
和outHeight
参数; - 根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize;
- 将
BitampFactory.Options
的inJustDecodeBounds
参数设置为false,然后重新加载图片。
3、质量压缩
采样率压缩会改变图片的原始尺寸,如果你想压缩图片大小的同时保持原来的图片尺寸,那可以采用下面这种方式来处理。
通过Bitmap对象的compress(format:Bitmap.CompressFormat, quality: Int, stream: OutputStream)
方法,不仅可以进行图片质量的压缩,可以设置压缩后图片的格式。方法的第一个参数为压缩后的格式,第二个参数为压缩质量(0~100),100为不压缩图片质量,第三个参数就是要写入的输出流。
注意,如果设置格式为Bitmap.CompressFormat.PNG
,那设置压缩质量quality是没有效果的,因为png为无损压缩。
具体使用方法,请看代码:
/**
* 压缩Bitmap
* quality为压缩质量(0~100)
*/
fun compressBitmap(bitmap: Bitmap, quality: Int): Bitmap?{
if (bitmap == null) {
return null
}
var baos: ByteArrayOutputStream? = null
try {
baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
val bytes = baos.toByteArray()
val isBm = ByteArrayInputStream(bytes)
return BitmapFactory.decodeStream(isBm)
} catch (e: OutOfMemoryError){
e.printStackTrace()
} finally {
try {
baos?.close()
} catch (e: IOException){
e.printStackTrace()
}
}
return null
}
四、Bitmap与Drawable相互转换
/**
* Drawable转Bitmap
*/
fun drawableToBitmap(drawable: Drawable): Bitmap{
// 获取drawable的宽高
val width = drawable.intrinsicWidth
val height = drawable.intrinsicHeight
// 获取drawable的颜色类型
val config = if(drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565
// 建立bitmap对象
val bitmap = createBitmap(width, height, config)
// 创建对应的画布
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, width, height)
// 将drawable内容画到画布上
drawable.draw(canvas)
return bitmap
}
/**
* bitmap转drawable
*/
fun bitmapToDrawable(resources: Resources, bitmap: Bitmap) = BitmapDrawable(resources, bitmap)
五、使用Matrix处理图片
通过Matrix可以对图片进行缩放、旋转、移动、裁剪等操作。
Matrix提供的一些常用方法
方法名 | 作用 |
---|---|
setScale(sx:Float,sy:Float) | 图片缩放,sx 和sy 为缩放的比例 |
setRotate(degrees:Float,px:Float,py:FLoat) | 图片旋转,degrees为角度,px,py为旋转中心点坐标 |
setTranslate(dx:Float,dy:Float) | 图片平移,dx 和dy 为移动的距离 |
setSkew(kx:Float,ky:Float) | 图片倾斜,kx 和ky 为倾斜比例 |
使用方式:
/**
* 缩放图片
*/
fun scaleBitmap(bitmap: Bitmap): Bitmap{
val matrix = Matrix()
matrix.setScale(0.6f, 0.6f)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}