开发中经常需要使用Bitmap进行位图显示,由于现在手机像素的提升,直接频繁显示原图会消耗大量的内存,很容易造成OOM,因此我们需要对Bitmap进行压缩处理。
首先在通过BitmapFactory创建Bitmap时可以发现,官方为我们提供了一个option参数,大多数开发者都知道这个参数可以帮助我们调节Bitmap的质量,从而实现图片的压缩。
public enum Config {
ALPHA_8 (1),
RGB_565 (3),
ARGB_4444 (4),
ARGB_8888 (5);
}
官方为我们提供了如上几种配置信息。
其实这都是色彩的存储方法:我们知道ARGB指的是一种色彩模式,里面A代表Alpha,R表示red,G表示green,B表示blue,其实所有的可见色都是右红绿蓝组成的,所以红绿蓝又称为三原色,每个原色都存储着所表示颜色的信息值。
说白了其实就是:
ALPHA_8就是Alpha由8位组成,代表8位Alpha位图
ARGB_4444就是由4个4位组成即16位,代表16位ARGB位图
ARGB_8888就是由4个8位组成即32位,代表32位ARGB位图
RGB_565就是R为5位,G为6位,B为5位共16位,代表16位RGB位图根据上面的参数可以看出,当我们需要对一个图片进行压缩时,如果需要其拥有透明度则选择ARGB_4444,如果不需要透明度则选择RGB_565。虽然这会损失一些细节但是普通显示上是可以接受的。
除了颜色配置,还有一个参数影响着图片大小,那就是分辨率。这里就不多说原因,相信都知道,那就直接说怎么解决。
可以直接通过修改Bitmap大小实现:
/**
* @param bitmap 源文件
* @param convertWidth 输出宽度
* @param convertHeight 输出高度
* @param option 如果option定义为 OPTIONS_RECYCLE_INPUT,则会回收bitmap源文件,不用手动释放
*/
public static Bitmap convertBitmap(Bitmap bitmap, int convertWidth, int convertHeight,int option) {
Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, convertWidth, convertHeight, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
return thumbnail;
}
通过Matrix对图片按倍数放大和缩小:
/**
* 讲bitmap放大或缩小制定的倍数
* @param bytes bitmap的字节数组
* @param scale 倍数
*/
public static byte[] scaleBitmap(byte[] bytes, float scale) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
newBitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] byteArray = bos.toByteArray();
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
bitmap.recycle();
newBitmap.recycle();
return byteArray;
}
上面两种都有一定的局限性,第一个需要指定精确的长宽值,如果指定比例和原始比例不符,会导致图片变形。第二个虽然不会变形,但是只能按倍数增大和缩小。并且这两种都需要先生成一个Bitmap,再通过这个Bitmap进行转换。
最后一种,在Bitmap创建时便直接创建成想要的尺寸:
public interface CompressBitmapCallback {
void onComplete(Bitmap bitmap);
}
private static Executor executor = Executors.newFixedThreadPool(3);
/**
* 生成压缩过的bitmap
*
* @param height 定制最大高度
* @param width 定制最大宽度
* @param path 图片本地路径
* @param callback 操作完成回调
*/
public static void getCompressBitmap(final int height, final int width, final String path, final Handler handler, final CompressBitmapCallback callback) {
executor.execute(new Runnable() {
@Override
public void run() {
BitmapFactory.Options options = new BitmapFactory.Options();
//inJustDecodeBounds为true时创建的Bitmap只有尺寸信息,而没有真正的创建,可以节省资源
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int bitHeight = options.outHeight;
int bitWidth = options.outWidth;
int sample = 1;
//通过原始尺寸和需要输出尺寸判断sample值
if (bitHeight > height || bitWidth > width) {
while (bitHeight / (2 * sample) >= height || bitWidth / (2 * sample) >= width) {
sample *= 2;
}
}
Bitmap compressBitmap = null;
options.inJustDecodeBounds = false;
options.inSampleSize = sample;
options.inPreferredConfig = Bitmap.Config.RGB_565;
compressBitmap = BitmapFactory.decodeFile(path, options);
final Bitmap finalCompressBitmap = compressBitmap;
handler.post(new Runnable() {
@Override
public void run() {
callback.onComplete(finalCompressBitmap);
}
});
}
});
}
这种转换是比较推荐的一种方式,先将options的inJustDecodeBounds属性设为true,得到将要生成Bitmap的尺寸(注意这里其实系统并没有真正生成bitmap,只是拿到了对应的尺寸信息,因此不会消耗太多资源)。然后再通过需要输出的大小进行计算inSampleSize的值,这里解释一下,这个值的大小代表了生成图片会是原图片大小的N分之一,如果是2则为原图的一半,这也是官方比较推荐的一种方式,并且这样生成的图片因为是按比例缩放,所以不会出现图片变形的情况。还有需要注意的是,这个值只有当为2的指数时才会生效,即1、2、4、8。具体原因不详。
采用这种方法可以使用大多数情况,但是同样也有缺点,那就是不能保证输出图片尺寸的精确性,因为缩放比例只能是2的指数,所以和想要的尺寸肯定会存在出入。但是这种方法相对来说最节省资源,并且可以尽量保证图片的显示效果。
通过BitmapFactory.Options来缩放图片,主要用到inSampleSize属性,获得的Bitmap分辨率大小将会为原图的1/inSampleSize,其中该值必须是2的指数,即:1,2,4,8,16等,不满足时绘选择一个最最接近的指数来代替。
为了保证不同分辨率图片处理之后得到同样的效果,现将Options的inJustDecodeBounds设为true,这样生成的bitmap只有尺寸属性而没有真正的图片,通过得到的尺寸来计算inSampleSize的大小,再次生成bitmap即为我们想要得到的最终Bitmap。