只是作为自己的笔记记录。
android开发中,经常会遇到图片的上传后者下载等操作,但是系统分配给当前应用的最大可用内存有限,一般是16M,32M,所以加载大图很容易出现OOM。因此需要对图片进行压缩处理。
先介绍一下图片占用内存的计算:
一个图片所占内存 = 长 x 宽 x 一个像素点所占字节数。(图片像素 = 长 x 宽)
Android中的Bitmap默认使用的是ARGB_8888。
所以一个1280*800的图片所占用的内存大小:1280*800*4/(1024*1024) = 3.9M。
图片压缩目的就是要减少图片所占内存大小,明显地,我们能改变的是长 和 宽。
一般来说,图片压缩无外乎:质量压缩,大小压缩。
1、质量压缩
关键方法:bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
这种压缩,图片的长 ,宽 和 像素的字节数 都没有变化,那么是怎么实现压缩效果的呢?
实际上,是通过该改变位深,透明度等牺牲图片质量的方式来达到压缩的目的。但是图片所占内存并没有
改变。
解释:
比如说图片是500k,1280 * 800像素的,通过质量压缩,File形式的图片大小确实变小了,比如说变成100k,以便于上传到服务器。
但是通过Bitmap.decodeFile到内存中,变成Bitmap时,它的像素仍然是:1280 * 800,图片的像素 = bitmap.getWidth() x bitmap.getHeight();
图片是由像素组成的,那么像素是由什么组成的呢?
像素是由位深(色深),透明度等组成。
该方法的官方文档也解释说, 它会让图片重新构造, 但是有可能图像的位深(即色深)和每个像素的透明度会变化,JPEG onlysupports opaque(不透明), 也就是说以jpeg格式压缩后, 原来图片中透明的元素将消失.所以这种格式很可能造成失真。
总结:通过改变图片质量,达到了对File形式的图片进行压缩,图片的像素并没有变化,也就是所占内存没变。(上传文件到服务器时,File要尽量小)
备注:bitmap.getByteCount() 是计算它的像素所占用的内存。
2、大小压缩
通过设置采样率,如:newOpts.inSampleSize = 2;来达到压缩的目的。
采样率改变的是图片的长和宽,这样就改变了图片的像素,最终改变了图片所占的内存。这就达到了压缩的目的。
项目中有上传图片的操作,处理图片用的是Luban图片压缩框架。
Luban:原作者经过多次比对测试,推理出一个可以计算出不同大小范围的图片最终应该压缩成的大小的逻辑:(接近微信压缩算法)
事实上,这个框架内部使用了大小压缩 和 质量压缩。
部分源码如下:
第三档压缩———
private File thirdCompress(@NonNull File file) {
String thumb = mCacheDir.getAbsolutePath() + File.separator +
(TextUtils.isEmpty(filename) ? System.currentTimeMillis() : filename) + ".jpg";
double size;
String filePath = file.getAbsolutePath();
int angle = getImageSpinAngle(filePath);
int width = getImageSize(filePath)[0];
int height = getImageSize(filePath)[1];
Log.i("TAG","realWidth == >" + width);
Log.i("TAG","realHeight == >" + height);
int thumbW = width % 2 == 1 ? width + 1 : width;
int thumbH = height % 2 == 1 ? height + 1 : height;
Log.i("TAG","thumbW == >" + thumbW);
Log.i("TAG","thumbH == >" + thumbH);
width = thumbW > thumbH ? thumbH : thumbW;
height = thumbW > thumbH ? thumbW : thumbH;
Log.i("TAG","width == >" + width);
Log.i("TAG","height == >" + height);
double scale = ((double) width / height);
if (scale <= 1 && scale > 0.5625) { // 图片伸缩比(短边:长边)范围在 9:16到1:1
Log.i("TAG","scale <= 1 && scale > 0.5625 ---->" + scale);
if (height < 1664) {
if (file.length() / 1024 < 150) return file;
size = (width * height) / Math.pow(1664, 2) * 150;
size = size < 60 ? 60 : size;
Log.i("TAG","height < 1664 ---->" + size);
} else if (height >= 1664 && height < 4990) {
thumbW = width / 2;
thumbH = height / 2;
size = (thumbW * thumbH) / Math.pow(2495, 2) * 300;
size = size < 60 ? 60 : size;
Log.i("TAG","height >= 1664 && height < 4990 ---->" + size);
} else if (height >= 4990 && height < 10240) {
thumbW = width / 4;
thumbH = height / 4;
size = (thumbW * thumbH) / Math.pow(2560, 2) * 300;
size = size < 100 ? 100 : size;
Log.i("TAG","height >= 4990 && height < 10240 ---->" + size);
} else {
int multiple = height / 1280 == 0 ? 1 : height / 1280;
thumbW = width / multiple;
thumbH = height / multiple;
size = (thumbW * thumbH) / Math.pow(2560, 2) * 300;
size = size < 100 ? 100 : size;
Log.i("TAG","else ---->" + size);
}
} else if (scale <= 0.5625 && scale > 0.5) {//400*800 ~ 1280*720
Log.i("TAG","scale <= 0.5625 && scale > 0.5 scale---->" + scale);
if (height < 1280 && file.length() / 1024 < 200) return file;
int multiple = height / 1280 == 0 ? 1 : height / 1280;
thumbW = width / multiple;
thumbH = height / multiple;
size = (thumbW * thumbH) / (1440.0 * 2560.0) * 400;
size = size < 100 ? 100 : size;
Log.i("TAG","scale <= 0.5625 && scale > 0.5 size---->" + size);
} else {
Log.i("TAG","else scale---->" + scale);
int multiple = (int) Math.ceil(height / (1280.0 / scale));
thumbW = width / multiple;
thumbH = height / multiple;
size = ((thumbW * thumbH) / (1280.0 * (1280 / scale))) * 500;
size = size < 100 ? 100 : size;
Log.i("TAG","else scale size---->" + size);
}
return compress(filePath, thumb, thumbW, thumbH, angle, (long) size);
}
/**
* 指定参数压缩图片
* create the thumbnail with the true rotate angle
*
* @param largeImagePath the big image path
* @param thumbFilePath the thumbnail path
* @param width width of thumbnail(想要压缩到的尺寸)
* @param height height of thumbnail(想要压缩到的尺寸)
* @param angle rotation angle of thumbnail
* @param size the file size of image
*/
private File compress(String largeImagePath, String thumbFilePath, int width, int height, int angle, long size) {
Bitmap thbBitmap = compress(largeImagePath, width, height);//大小压缩
thbBitmap = rotatingImage(angle, thbBitmap);
return saveImage(thumbFilePath, thbBitmap, size);//质量压缩
}
/**
* 压缩原图尺寸
* obtain the thumbnail that specify the size
*
* @param imagePath the target image path
* @param width the width of thumbnail
* @param height the height of thumbnail
* @return {@link Bitmap}
*/
private Bitmap compress(String imagePath, int width, int height) {
Log.i("TAG","compress_width---"+width+"compress_height---" + height);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, options);
int outH = options.outHeight; //获取实际宽
int outW = options.outWidth; //获取实际宽
int inSampleSize = 1;
//当满足width 跟 height 结束递归 得到压缩比值
if (outH > height || outW > width) {
int halfH = outH / 2;
int halfW = outW / 2;
while ((halfH / inSampleSize) > height && (halfW / inSampleSize) > width) {
inSampleSize *= 2;
}
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
Log.i("TAG","options.outHeight--->" + options.outHeight);
Log.i("TAG","options.outWidth--->" + options.outWidth);
Log.i("TAG","options.inSampleSize--->" + options.inSampleSize);
int heightRatio = (int) Math.ceil(options.outHeight / (float) height); //向上取整 获取原图比压缩图比值
int widthRatio = (int) Math.ceil(options.outWidth / (float) width);
if (heightRatio > 1 || widthRatio > 1) {
if (heightRatio > widthRatio) {
options.inSampleSize = heightRatio;
} else {
options.inSampleSize = widthRatio;
}
}
options.inJustDecodeBounds = false;
Log.i("TAG","1options.outHeight--->" + options.outHeight);
Log.i("TAG","1options.outWidth--->" + options.outWidth);
Log.i("TAG","1options.inSampleSize--->" + options.inSampleSize);
return BitmapFactory.decodeFile(imagePath, options);
}
/**
* 保存图片到指定路径
* Save image with specified size
*
* @param filePath the image file save path 储存路径
* @param bitmap the image what be save 目标图片
* @param size the file size of image 期望大小
*/
private File saveImage(String filePath, Bitmap bitmap, long size) {
checkNotNull(bitmap, TAG + "bitmap cannot be null");
File result = new File(filePath.substring(0, filePath.lastIndexOf("/")));
if (!result.exists() && !result.mkdirs()) return null;
ByteArrayOutputStream stream = new ByteArrayOutputStream();
int options = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, options, stream);
while (stream.toByteArray().length / 1024 > size && options > 6) {
stream.reset();
options -= 6;
bitmap.compress(Bitmap.CompressFormat.JPEG, options, stream);
}
bitmap.recycle();
try {
FileOutputStream fos = new FileOutputStream(filePath);
fos.write(stream.toByteArray());
fos.flush();
fos.close();
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
return new File(filePath);
}