最近开发中有用到图片压缩技术,对此,抽空来写一篇对此的总结,方便后期的查看,如果对文中有什么疑惑,欢迎留言探讨。
Ps:最近忙项目真的是身心俱疲,好容易前天状态好一点,眼睛不知道怎么搞的,肿了,导致文章也是一拖再拖,好吧,放假就多写几篇吧,加油!
在看接下来的内容之前先了解下基础的图片知识
0.图片的基本知识
2.流的形式(即以二进制形式存在于内存中)
获取大小(Byte):new FileInputStream(File).available()
3.Bitmap形式
获取大小(Byte):Bitmap.getByteCount()
看下图就知道3者的大小区别了
1、拍摄完的照片文件大小和读取到内存中的文件流大小是一样的,说明文件形式和流的形式对图片体积大小并没有影响。
2、当图片以Bitmap形式存在时,占用的内存就大的多了,为什么呢,首先我们需要知道Bitmap大小计算的方式
bitmap大小=图片长度(px)*图片宽度(px)*单位像素占用的字节数
单位像素所占字节数又是什么鬼,说白了就是图片的色彩模式。
在BitmapFactory.Options.inPreferredConfig这里可以找到,一共有4种, ARGB代表:A 透明度 , R 红色, G 绿色, B 蓝色
上面的bitmap在内存中的大小就可以计算了(默认色彩模式为ARGB_8888),
2368*4224*4/1024/1024=38.15625 M
由此可知看到bitmap占用内存消耗,所以用完调用Bitmap.recycle()是个好习惯,不过,要回收时一定要注意回收的时机,确保不再需要了才能去回收,否则会报错。
1.为什么要进行图片压缩
目的无非就2个,一,避免占用内存过多。二,可能要上传图片,如果图片太大,浪费流量。(有时候特殊需要需要上传原图除外)
(1)、避免内存过多的压缩方法:
归根结底,图片是要显示在界面组件上的,所以还是要用到bitmap,从上面可得出Bitmap的在内存中的大小只和图片尺寸和色彩模式有关,那么要想改变Bitmap在内存中的大小,要么改变尺寸,要么改变色彩模式。
(2)、避免上传浪费流量的压缩方法:
改变图片尺寸,改变色彩模式,改变图片质量都行。正常情况下,先改变图片尺寸和色彩模式,再改变图片质量。
2.图片压缩的方法(1)改变图片质量的压缩方法
/**
*
* 根据bitmap压缩图片质量
* @param bitmap 未压缩的bitmap
* @return 压缩后的bitmap
*/
public static Bitmap compressQuality(Bitmap bitmap){
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
int beginRate = 100;
//第一个参数 :图片格式 ,第二个参数: 图片质量,100为最高,0为最差 ,第三个参数:保存压缩后的数据的流
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bOut);
while(bOut.size()/1024/1024>100){ //如果压缩后大于100Kb,则提高压缩率,重新压缩
beginRate -=10;
bOut.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, beginRate, bOut);
}
ByteArrayInputStream bInt = new ByteArrayInputStream(bOut.toByteArray());
Bitmap newBitmap = BitmapFactory.decodeStream(bInt);
if(newBitmap!=null){
return newBitmap;
}else{
return bitmap;
}
}
(2) 改变图片大小的压缩方法
/**
* 压缩图片的尺寸
*
* @param filePath
* 图片文件路径名
* @param savePath
* 要保存压缩后图片的路径名
* @return
*/
public static boolean compressSize(String filePath, String savePath) {
OutputStream out = null;
BitmapFactory.Options option = new BitmapFactory.Options();
option.inJustDecodeBounds = true; // 设置为true,只读尺寸信息,不加载像素信息到内存
Bitmap bitmap = BitmapFactory.decodeFile(filePath, option); // 此时bitmap为空
option.inJustDecodeBounds = false;
int bWidth = option.outWidth;
int bHeight = option.outHeight;
int toWidth = 400;
int toHeight = 800;
int be = 1; // be = 1代表不缩放
if (bWidth / toWidth > bHeight / toHeight && bWidth > toWidth) {
be = (int) bWidth / toWidth;
} else if (bWidth / toWidth < bHeight && bHeight > toHeight) {
be = (int) bHeight / toHeight;
}
option.inSampleSize = be; // 设置缩放比例
Log.e("压缩大小---->", "" + be);
bitmap = BitmapFactory.decodeFile(filePath, option);
try {
out = new FileOutputStream(new File(savePath));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return bitmap.compress(CompressFormat.JPEG, 100, out);
}
一般情况下,要将两者结合使用,于是有了下面的算法,经过我的测试,此方法相比其他方法在保证了原图不失真的情况下,图片保存的尺寸是较小的。
/**
* 根据给定图片路径压缩图片并保存到指定路径
* @param imgpath
* @param outPath
*/
public static void compressImgUtil(String imgpath, String outPath) {
File imgPath = new File(imgpath);
long fileSize = imgPath.length();
final long fileMaxSize = 400 * 1024;
if (fileSize >= fileMaxSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
int height = options.outHeight;
int width = options.outWidth;
double scale = Math.sqrt((float) fileSize / fileMaxSize);
options.outHeight = (int) (height / scale);
options.outWidth = (int) (width / scale);
options.inSampleSize = (int) (scale + 0.5);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(imgpath, options);
File fout = new File(outPath);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fout);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fos);
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.d("ssss", "" + fout.length());
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
}
}
有时,有的手机(平板)拍的照片是旋转了角度的,你得在上面合适的位置加上以下代码,将其旋转回来。
/**
* 获取指定路径图片的旋转角度
*
* @param path
* @return 旋转的角度值
*/
private static int readPictureDegree(String path) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(path);
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
然后根据旋转角度将给定的bitmap给调整过来
/**
* 根据角度值旋转所选的图片的bitmap
*
* @param bitmap
* @param rotate
* @return 所选图片的bitmap
*/
private static Bitmap rotateBitmap(Bitmap bitmap, int rotate) {
if (bitmap == null)
return null;
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Setting post rotate to 90
Matrix mtx = new Matrix();
mtx.postRotate(rotate);
return Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true);
}
下面给出网上压缩bitmap的方法
public static void compressImage(String filePath, String fileName, int q)
throws FileNotFoundException {
Bitmap bm = getSmallBitmap(filePath);
int degree = readPictureDegree(filePath);
if (degree != 0) {// 旋转照片角度
bm = rotateBitmap(bm, degree);
}
File outputFile = new File(fileName);
FileOutputStream out = new FileOutputStream(outputFile);
bm.compress(Bitmap.CompressFormat.PNG, q, out);
}
其中readPictureDegree和rotateBitmap两个方法同上
getSmallBitmap方法如下
public static Bitmap getSmallBitmap(String filePath) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);//我不知道此处代码是什么意思,有知道的,麻烦留个言
// Calculate inSampleSize inSampleSize为1表示宽度和高度不缩放,为2表示压缩后的宽度与高度为原来的1/2
options.inSampleSize = calculateInSampleSize(options, 480, 800);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(filePath, options);
}
但是我用上面网上的方法,效果没有我最上面写的那个压缩效果好。
好的,文章到此就结束了,有问题的可以留言,欢迎探讨!!!