在我们的业务场景中,一般需要使用客户端采集图片上传至服务器,为了提升性能,我们一般会对图片进行压缩。
在Android平台上,默认提供的压缩有三种方式:质量压缩和采样压缩。
一、质量压缩
质量压缩不改变图片的尺寸,只改变图片的存储体积,即原来是1080*1920的图片压缩后还是分辨率不变,并且压缩前后由File格式转换成Bitmap格式进入内存中,占用的内存并没有改变,因为内存是根据图片的像素来给图片分配内存大小的。
质量压缩主要借助Bitmap中的compress方法实现:
public boolean compress (Bitmap.CompressFormat format, int quality, OutputStream stream)
该方法接收三个参数,其含义分别如下:
- format:枚举类型,有三个选项 JPEG, PNG 和 WEBP,表示图片的格式;
-
quality:图片的质量,取值在 [0,100] 之间,表示图片质量,越大,图片的质量越高;(PNG格式会忽略该值设定 )
-
stream:一个输出流,通常是我们压缩结果输出的文件的流。
注意:当调用bitmap.compress(CompressFormat.JPEG, 100, fos);保存为图片时发现图片背景为黑色时,将格式改为png格式就好了。
二、采样压缩
通过设置采样率,减少图片的像素,达到对内存中Bitmap进行压缩。
采样压缩主要通过BitmapFactorry中的decodeFile方法实现:
public static Bitmap decodeFile (String pathName, BitmapFactory.Options opts)
该方法接收2个参数:
- pathName是图片文件路径
- opts是采样率,通过设置采样率属性,来达到根据需要压缩图片的目的。
标准使用如下:
// 获取原始图片的尺寸 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inSampleSize = 1; BitmapFactory.decodeStream(srcImg.open(), null, options); this.srcWidth = options.outWidth; this.srcHeight = options.outHeight; // 进行图片加载,此时会将图片加载到内存中 options.inJustDecodeBounds = false; options.inSampleSize = calInSampleSize(); Bitmap bitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
这里分成了2步实现:
1.设置inJustDecodeBounds为true来得到图片的宽高,此时图片不会被加载到内存中,也就不会造成OOM。
2.根据第一步的尺寸,计算inSampleSize,然后将inJustDecodeBounds设置为true,加载采样后的图片至内存中。
inSampleSize代表压缩后的一个像素点代表原图像素点的几个像素点,例如inSampleSize为2,则压缩后的图片宽高是原图的1/2,像素点是原来的1/4,呈指数型增长。
图片压缩算法总结:
实际使用中,我们一般通过先进行采样压缩,再进行质量压缩得到最终图片。
使用示例:
private void compressBitmapToFile() { Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_image2); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG,100,baos); //图片质量小于100K不压缩(该值计算不太准) if( baos.toByteArray().length /1024 > 100){ //采样压缩 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; options.inSampleSize = 1; BitmapFactory.decodeResource(getResources(),R.drawable.ic_image3,options); //计算缩放值 options.inSampleSize = computeSize(options, 720, 1028); LogUtil.e("inSampleSize-"+options.inSampleSize); options.inJustDecodeBounds = false; bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_image2,options); //压缩图片质量 像素值不变 baos = new ByteArrayOutputStream(); int options1 = 75; bitmap.compress(Bitmap.CompressFormat.JPEG,options1,baos); bitmap.recycle(); /* while(baos.toByteArray().length /1024 > 100){ baos.reset(); options1 -= 10; bitmap.compress(Bitmap.CompressFormat.JPEG,options1,baos); }*/ } //将压缩后图片写入文件 String path = getExternalCacheDir().getAbsolutePath() + "/ic_image.jpg"; File file = new File(path); if(!file.exists()){ try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } FileOutputStream fos = null; try { fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
computeSize代码如下。
private int computeSize(BitmapFactory.Options options,int reqWidth,int reqHeight) { int srcHeight = options.outHeight; int srcWidth = options.outWidth; srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth; srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight; int inSampleSize = 1; if (srcHeight > reqHeight || srcWidth > reqWidth) { final int heightRatio = Math.round((float) srcHeight / (float) reqHeight); final int widthRatio = Math.round((float) srcWidth / (float) reqWidth); inSampleSize = heightRatio <= widthRatio ? heightRatio : widthRatio;// } if (inSampleSize <= 0) { return 1; } return inSampleSize; //鲁班压缩代码 /* int longSide = Math.max(srcWidth, srcHeight); int shortSide = Math.min(srcWidth, srcHeight); float scale = ((float) shortSide / longSide); if (scale <= 1 && scale > 0.5625) { if (longSide < 1664) { return 1; } else if (longSide >= 1664 && longSide < 4990) { return 2; } else if (longSide > 4990 && longSide < 10240) { return 4; } else { return longSide / 1280 == 0 ? 1 : longSide / 1280; } } else if (scale <= 0.5625 && scale > 0.5) { return longSide / 1280 == 0 ? 1 : longSide / 1280; } else { return (int) Math.ceil(longSide / (1280.0 / scale)); }*/ }
旋转图片:
private Bitmap rotatingImage(Bitmap bitmap) { if (srcExif == null) return bitmap; Matrix matrix = new Matrix(); int angle = 0; int orientation = srcExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: angle = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: angle = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: angle = 270; break; } matrix.postRotate(angle); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); }
压缩亲测可用,图片压缩对比大致效果如下。
鲁班算法逻辑见:https://github.com/Curzibn/Luban/blob/master/DESCRIPTION.md
参考地址: