首先要清楚为什么要对图片进行压缩,对于移动设备来说,硬件(CPU、内存、磁盘等)资源有限,图片放在内存中要占据大量的内存,放在磁盘中也要占据较大的空间,所以在不影响用户体验的条件下,我们要尽可能的把图片进行压缩,以节省有限的硬件资源。下面我们分析一下常用的几种压缩方式,以及在什么情况下使用。
目录
一、质量压缩
1 format
2 quality
3 stream
二、采样率压缩
三、按比例压缩
一、质量压缩
质量压缩的特点是图片文件大小会减小,但是图片的像素数不会改变,加载压缩后的图片,占据的内存不会减少。质量压缩依靠Bitmap. compress(CompressFormat format, int quality, OutputStream stream)实现。
* @param format The format of the compressed image
* @param quality Hint to the compressor, 0-100. 0 meaning compress for
* small size, 100 meaning compress for max quality. Some
* formats, like PNG which is lossless, will ignore the
* quality setting
* @param stream The outputstream to write the compressed data.
* @return true if successfully compressed to the specified stream.
*/
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
1 format
format表示以某种格式压缩图片,支持三种格式:Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP。注意Bitmap.CompressFormat.XXX要和文件格式一致。
//生成png格式图片
String fileName = "test.png";
File file = new File(appDir, fileName);
bitmap.compress(Bitmap.CompressFormat.PNG, compressRatio, baos);
//生成jpeg格式图片
String fileName = "test.jpeg";
File file = new File(appDir, fileName);
bitmap.compress(Bitmap.CompressFormat.JPEG, compressRatio, baos);
2 quality
quality代表压缩程度,取值0-100。100表示不压缩,压缩后图片和原图片文件大小一样;0表示压缩到最小的图片文件大小,质量压缩是保持像素不变的前提下改变图片的位深及透明度,来压缩图片大小,因为要保持像素不变,所以它就无法无限压缩,图片文件大小到达一个值之后就不会继续变小了。如下,原图是png,压缩成jpeg,quality依次设置为100、50、10、5、2、1、0,当quality=2时已经压缩到极限,quality再减小,图片大小不在减小,并且随着图片质量的压缩,图片越来越模糊。
quality为100、10、2时:
此外,还有一点需要注意,如果format以Bitmap.CompressFormat.PNG格式压缩,如论quality为何值,压缩后图片文件大小都不会变化,也就是quality对Bitmap.CompressFormat.PNG压缩格式不起作用,因为png图片是无损的,不能进行压缩。总结如下:
3 stream
将压缩的图片写入输出流中,进而保存成文件。
根据上面实验内容,可以知道质量压缩适用于保存图片时,减小图片文件大小,节约磁盘空间,quality选取要适当,不能使图片模糊。
二、采样率压缩
当ImageView尺寸比图片尺寸过小时,加载原图的话,ImageView不仅不能全部展现出来,而且原图会占据较大的内存,容易出现oom,这时要使用采样率压缩将图片宽高压缩到ImageView的宽高。采样率压缩时图片的宽高按比例压缩,压缩后图片像素数会减少,原图1000*1000,inSampleSize=4,压缩后的图片宽高为250*250,内存只占据原图的1/16,实现过程如下:
/**
* 采样压缩,图片宽高按比例缩小,像素数减少、图片容量缩小
* 宽、高压缩比为1/2、1/4/、1/8、1/16
*
* @param requiredWidth
* @param requiredHeight
*/
private void sampleSizeCompress(int requiredWidth, int requiredHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
//第一次加载,返回null,没有生成bitmap
BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
// 源图片的高度和宽度
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if (height > requiredHeight || width > requiredWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height / (float) requiredHeight);
final int widthRatio = Math.round((float) width / (float) requiredWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
Log.i("图像inSampleSize", inSampleSize + "");
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
final Bitmap compressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl, options);
mCompressImg.setImageBitmap(compressBitmap);
Log.i("压缩后图像素数:", compressBitmap.getWidth() + "*" + compressBitmap.getHeight());
File appDir = new File(Environment.getExternalStorageDirectory(), "123");
if (!appDir.exists()) {
appDir.mkdir();
}
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = df.format(new Date()) + ".jpeg";
final File file = new File(appDir, fileName);
new Thread() {
@Override
public void run() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把压缩后的数据存放到baos中
compressBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
采样率压缩,需要两次加载bitmap。第一次加载前,将options.inJustDecodeBounds(只是找到bitmap的边界值)设置为true,第一次加载,返回null,没有生成bitmap,但是可以得到原图的宽高。之后根据ImageView的宽高计算采样率,也就是bitmap要压缩的倍率,inSampleSize可以稍小,不能偏大,inSampleSize稍小时,图片会比ImageView稍大些,这种情况可以接受,但是inSampleSize偏大时,图片会比imageView稍小,此时图片会拉伸,图片质量会下降。此外,inSampleSize只能是2^n,即使计算的值不是2^n,实际压缩时依然是按照2^n压缩。如计算得到inSampleSize=3,实际按照inSampleSize=2压缩,inSampleSize=7,实际按照inSampleSize=4压缩,inSampleSize=9,实际按照inSampleSize=8压缩。下面看实际的例子,ImageView宽高为200*200时:
imagView宽高是200*200,压缩后图片宽高是630*395,比原图缩小了4倍,内存占用为原来的1/16。
imagView宽高是300*300时:
ImageView宽高是300*300,压缩后的图像宽高1260*788,比原图缩小了2倍,占用内存为原图的1/4。
根据上面的实验内容,当ImageView比图片明显偏小时,为了减少内存占用,防止oom,用采样率压缩压缩图片,降低内存占用。
三、按比例压缩
按比例压缩过程是:给定压缩图的宽高,根据原图的宽高,计算宽、高压缩比例,按比例压缩。如,原图100*100,要求压缩图为50*25,那么原图的宽压缩2倍、高压缩4倍,能得到压缩图,图片像素数为原图的1/8。
/**
* 指定宽高进行尺寸压缩,图片按比例缩小,原图像素数: 1260*788,压缩后图像素数::126*78
,宽为原图宽1/10,高为原图高1/10,像素数减少为1/100
* ,
* 压缩后的图片文件大小也会减小
*
* @param bitmap
* @param requiredWidth
* @param requiredHeight
*/
private void dimenCompress(Bitmap bitmap, int requiredWidth, int requiredHeight) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) requiredWidth) / width;
float scaleHeight = ((float) requiredHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
final Bitmap compressBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
Log.i("压缩后图像素数:", compressBitmap.getWidth() + "*" + compressBitmap.getHeight());
mCompressImg.setImageBitmap(compressBitmap);
File appDir = new File(Environment.getExternalStorageDirectory(), "123");
if (!appDir.exists()) {
appDir.mkdir();
}
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String fileName = df.format(new Date()) + ".jpeg";
final File file = new File(appDir, fileName);
new Thread() {
@Override
public void run() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 把压缩后的数据存放到baos中
compressBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
下图分别为宽高压缩1/5、1/5 ; 宽高压缩1/5、1/10:
图片压缩到这里就总结完了,个人能力有限,希望大家批评指正!