最近在网上看了一些关于图片压缩的博客,自己也动手实验了一遍,也算事对图片压缩有了一个了解,打算写个博客记录一下。文末附上参考链接。
Android中涉及到图片的话一般都会用到BitMap类和BitmapFactory类。而本文讲述的图片压缩也都是通过这两个类来实现的。Android中,图片占用内存大小计算公式:图片宽度 * 图片高度 * 图片每一个像素占用的字节数。所以图片占用内存的大小(或者说压缩),就是通过改变这三个值来改变的。
1、质量压缩
这种方法,它是通过改变图片的位深和透明度来实现图片压缩的。它不会改变图片的大小,也不会改变图片占用的内存空间。它改变的是图片对应的BitMap对象的length属性值。下面是实现代码:
private Bitmap compressQuality(Bitmap bitmap){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos);
byte bytes[] = bos.toByteArray();
Log.d(TAG, "compressQuality: length = "+bytes.length);
return BitmapFactory.decodeByteArray(bytes,0,bytes.length);
}
上述代码重点是compress方法的使用,下面是它的API的形式。
//按指定的图片格式以及画质,将图片转换为输出流。
public boolean compress(CompressFormat format, int quality, OutputStream stream)
这个方法接收一个图片格式参数(format),画质参数(quality)还有一个输出流(stream)。图片格式参数有JPEG、PNG和WEBP,画质参数是[0,100]区间。使用这个方法压缩图片的时候,我们主要是想把图片对应的BitMap对象的length属性变小,以应对一些对图片大小有限制的情况。而这里就需要注意,当图片格式参数为Bitmap.CompressFormat.PNG的时候,是无法改变这个length属性值的,因为PNG格式是无损的。画质参数为100的时候,length属性值也不变,表示不压缩。
对于上面代码,还可以学习到的知识点是:BitMap转换成字节数组(使用ByteArrayOutputStream类)及字节数组转换成BitMap(使用decodeByteArray方法)
2、采样率压缩
这种方法是通过改变BitmapFactory.Options的inSampleSize属性值来改变图片的大小的。通过它的代码注释可以知道,它是用来记录图片压缩倍数的,如果它的值是4,那么它的宽高最后都变成原来的1/4,根据前面图片占用内存大小公式,最终它占用内存大小就变成原来的1/16。这里我设置它的值为2,则最后占用内存大小为原来的1/4。
private Bitmap compressSampling(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
return BitmapFactory.decodeResource(getResources(),R.mipmap.fengjing,options);
}
/**
* If set to a value > 1, requests the decoder to subsample the original
* image, returning a smaller image to save memory. The sample size is
* the number of pixels in either dimension that correspond to a single
* pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the
* number of pixels. Any value <= 1 is treated the same as 1. Note: the
* decoder uses a final value based on powers of 2, any other value will
* be rounded down to the nearest power of 2.
*/
public int inSampleSize;
3、缩放法压缩
这种方法通过给给定一个矩阵来对图片进行压缩,通过查看createBitMap的源码发现,最终也是改变图片的宽高实现的。
private Bitmap compressMatrix(Bitmap bitmap){
Matrix matrix = new Matrix();
matrix.setScale(0.5f,0.5f);
return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
}
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter) {
//...
if (m == null || m.isIdentity()) {
bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
final boolean transformed = !m.rectStaysRect();
m.mapRect(deviceR, dstR);
neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height());
//...
bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha());
//...
}
return bitmap;
}
4、RGB_565格式压缩
前面说到,图片占用内存大小公式是:图片宽度 * 图片高度 * 图片每一个像素占用的字节数。而上面的两种方法都是通过改变图片的宽高来实现压缩的,接着讲解的这种方法就是通过改变图片每一个像素占用的字节数来改变图片占用内存空间。
先来了解一下,图片常用的压缩格式:
ALPHA_8 | 一个像素点占用1个字节,它没有颜色,只有透明度 |
ARGB_4444 | 一个像素点占用2个字节 |
ARGB_8888 | 一个像素点占用4个字节 |
RGB_565 | 一个像素点占用2个字节,它没有透明度 |
代码如下:
private Bitmap compressRGB565(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8;
Log.d(TAG, "compressRGB565: "+options.inSampleSize);
Log.d(TAG, "compressRGB565: "+options.inPreferredConfig);
Log.d(TAG, "compressRGB565: "+options.inDensity);
Log.d(TAG, "compressRGB565: "+options.outWidth);
Log.d(TAG, "compressRGB565: "+options.outHeight);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.fengjing,options);
Log.d(TAG, "compressRGB565: -----------------");
Log.d(TAG, "compressRGB565: "+options.inSampleSize);
Log.d(TAG, "compressRGB565: "+options.inPreferredConfig);
Log.d(TAG, "compressRGB565: "+options.inDensity);
Log.d(TAG, "compressRGB565: "+options.outWidth);
Log.d(TAG, "compressRGB565: "+options.outHeight);
return bitmap;
}
实现这种方式的方法是通过更改BitmapFactory.Options的inPreferredConfig值。默认情况下,Android使用ARGB_8888格式。实验中,除了ARGB_4444格式,看到的现象是一个黑色的图片区域,没有显示外,其他格式图片正常显示,内存大小以ARGB_8888格式为准的话,其他格式都缩小相应倍数。
注意:由于ARGB_4444的画质惨不忍睹,一般假如对图片没有透明度要求的话,可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。
上面的代码我加了一些日志打印,主要是为了验证,执行完BitmapFactory.decodeResource方法之后,会对options的一些属性进行赋值,所以在某些情况下,会使用这种方式来获取图片的options信息。
5、使用createScaleBitMap压缩
private Bitmap compressScaleBitMap(Bitmap bitmap){
int w = bitmap.getWidth();
int h = bitmap.getHeight();
return Bitmap.createScaledBitmap(bitmap, (int) (w*0.8), (int) (h*0.7),true);
}
这种方法通过给定期望的图片宽高来进行图片的压缩。注意图片大小改动太大会导致图片的画质变得很差。
总结
第一种方法,质量压缩。不改变图片的大小,不改变内存占用空间。但会改变图片对于的Bitmap对象的字节length值。
第二种方法,采样率压缩。通过改变图片的宽高来改变图片大小及占用的内存空间。使用BitmapFactory类来实现。
第三种方法,缩放法压缩。通过给定一个具体的矩阵来改变图片大小及占用的内存空间。使用BitMap类实现。
第四种方法,RGB_565格式压缩。通过改变图片每一个像素占用的字节数来改变图片大小及占用内存空间。
第五种方法,createScaleBitMap压缩。通过给定期望的图片宽高来改变图片的大小及占用内存空间。
以上五种方法只是针对Android运行加载的Bitmap占用内存来说,压缩后的Bitmap存储到SD卡中,占用的内存空间并不一样。App开发过程中,如果需要对内存做优化,可以从这方面入手。
参考链接: