在我們的業務場景中,一般需要使用客戶端采集圖片上傳至服務器,為了提升性能,我們一般會對圖片進行壓縮。
在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
參考地址: