圖片壓縮

為何要壓縮

1、體積的原因
如果你的圖片是要準備上傳的,那動輒幾M的大小肯定不行的,況且圖片分辨率大于設備分辨率的話毫無意義。
2、內存原因
如果圖片要顯示下Android設備上,ImageView最終是要加載Bitmap對象的,就要考慮單個Bitmap對象的內存占用了,如何計算一張圖片的加載到內存的占用呢?其實就是所有像素的內存占用總和:
bitmap內存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節數
起決定因素就是最后那個參數了,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個像素點4個byte,RGB_565是2個byte,一般都采用ARGB_8888這種。那么常見的10801920的圖片內存占用就是:
1920 x 1080 x 4 = 7.9M
壓縮原理
從上面可以總結出,圖片壓縮應該從兩個方面入手同時進行:先是降低分辨率,然后降低每個像素的質量也就是內存占用。
分辨率壓縮
假設有張原圖是3840x2400,我想壓縮成1920x1080,實際是不可能100%能壓縮這個值的。因為圖片壓縮要保證寬高比,試想一下800x100的橫向圖可能壓成20x200豎向圖嗎? 不可能的.。這里常見的算法就是在1920x1080的范圍內保證較短邊,然后按照比例壓縮整個圖,
質量壓縮
質量壓縮并不會改變圖片在內存中的大小,僅僅會減小圖片所占用的磁盤空間的大小,因為質量壓縮不會改變圖片的分辨率,而圖片在內存中的大小是根據width
height一個像素的所占用的字節數計算的,寬高沒變,在內存中占用的大小自然不會變,質量壓縮的原理是通過改變圖片的位深和透明度來減小圖片占用的磁盤空間大小,所以不適合作為縮略圖,可以用于想保持圖片質量的同時減小圖片所占用的磁盤空間大小。另外,由于png是無損壓縮,所以設置quality無效,以下是實現方式:
/
*

  • 質量壓縮
  • @param format 圖片格式 jpeg,png,webp
  • @param quality 圖片的質量,0-100,數值越小質量越差
    */
    public static void compress(Bitmap.CompressFormat format, int quality) {
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    originBitmap.compress(format, quality, bos);
    try {
    FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
    fos.write(bos.toByteArray());
    fos.flush();
    fos.close();
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

3、采樣率壓縮
采樣率壓縮是通過設置BitmapFactory.Options.inSampleSize,來減小圖片的分辨率,進而減小圖片所占用的磁盤空間和內存大小。
設置的inSampleSize會導致壓縮的圖片的寬高都為1/inSampleSize,整體大小變為原始圖片的inSampleSize平方分之一,當然,這些有些注意點:
1、inSampleSize小于等于1會按照1處理
2、inSampleSize只能設置為2的平方,不是2的平方則最終會減小到最近的2的平方數,如設置7會按4進行壓縮,設置15會按8進行壓縮。
具體的代碼實現方式如下:
/**

  • @param inSampleSize 可以根據需求計算出合理的inSampleSize
    */
    public static void compress(int inSampleSize) {
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    BitmapFactory.Options options = new BitmapFactory.Options();
    //設置此參數是僅僅讀取圖片的寬高到options中,不會將整張圖片讀到內存中,防止oom
    options.inJustDecodeBounds = true;
    Bitmap emptyBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);

    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;
    Bitmap resultBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    try {
    FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
    fos.write(bos.toByteArray());
    fos.flush();
    fos.close();
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

4、縮放壓縮
通過減少圖片的像素來降低圖片的磁盤空間大小和內存大小,可以用于緩存縮略圖
實現方式如下:
public void compress(View v) {
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
Bitmap bitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
//設置縮放比
int radio = 8;
Bitmap result = Bitmap.createBitmap(bitmap.getWidth() / radio, bitmap.getHeight() / radio, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
RectF rectF = new RectF(0, 0, bitmap.getWidth() / radio, bitmap.getHeight() / radio);
//將原圖畫在縮放之后的矩形上
canvas.drawBitmap(bitmap, null, rectF, null);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
result.compress(Bitmap.CompressFormat.JPEG, 100, bos);
try {
FileOutputStream fos = new FileOutputStream(new File(sdFile, "sizeCompress.jpg"));
fos.write(bos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

5、JNI調用JPEG庫
Android的圖片引擎使用的是閹割版的skia引擎,去掉了圖片壓縮中的哈夫曼算法——一種耗CPU但是能在保持圖片清晰度的情況下很大程度降低圖片的磁盤空間大小的算法,這就是為什么ios拍出的1M的照片比Android5M的還要清晰的原因。
由于筆者能力有限,暫時沒有對NDK這塊做深入研究,后期將會將此方法補全
6、其他
還有一些技巧,比如在縮放壓縮的時候,Bitmap.createBitmap(bitmap.getWidth() / radio, bitmap.getHeight() / radio, Bitmap.Config.ARGB_8888),如果不需要圖片的透明度,可以將ARGB_8888改成RGB_565,這樣之前每個像素占用4個字節,現在只需要2個字節,節省了一半的大小。
7、總結
1、使用webp格式的圖片可以在保持清晰度的情況下減小圖片的磁盤大小,是一種比較優秀的,google推薦的圖片格式
2、質量壓縮可以減小圖片占用的磁盤空間,不會減小在內存中的大小
3、采樣率壓縮可以通過改變分辨率來減小圖片所占用的磁盤空間和內存空間大小,但是采樣率只能設置2的n次方,可能圖片的最優比例在中間
4、尺寸壓縮同樣也是通過改變分辨率來減小圖片所占用的磁盤空間和內存空間大小,縮放的尺寸沒有什么限制
5、jni調用jpeg庫來彌補安卓系統skia框架的不足,也是比較優秀的解決方式(方法會在后期補充)
既然尺寸壓縮和采樣率壓縮都是通過改變圖片的分辨率來降低大小,有什么區別嗎?
其中一個原因已經說明了,采樣率的壓縮比例會受到限制,尺寸壓縮不會,但是采樣率壓縮的清晰度會比尺寸壓縮的清晰度要好一些,


另外一個 主函數中直接懟兩個方法進去,復制粘貼即可
//圖片壓縮功能獲取長寬比
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算出實際寬高和目標寬高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高
// 一定都會大于等于目標的寬和高。
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
//圖片壓縮功能
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析將inJustDecodeBounds設置為true,來獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
方法調用,哪里要圖片就塞哪里,直接把圖片給你整成100*100
imageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), imgs[position], 100, 100));

Android應用開發中三種常見的圖片壓縮方法,分別是:質量壓縮法、比例壓縮法(根據路徑獲取圖片并壓縮)和比例壓縮法(根據Bitmap圖片壓縮)。
一、質量壓縮法

private Bitmap compressImage(Bitmap image) {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質量壓縮方法,這里100表示不壓縮,把壓縮后的數據存放到baos中
    int options = 100;
    while ( baos.toByteArray().length / 1024>100) { //循環判斷如果壓縮后圖片是否大于100kb,大于繼續壓縮
        baos.reset();//重置baos即清空baos
        image.compress(Bitmap.CompressFormat.JPEG, options, baos);//這里壓縮options%,把壓縮后的數據存放到baos中
        options -= 10;//每次都減少10
    }
    ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮后的數據baos存放到ByteArrayInputStream中
    Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream數據生成圖片
    return bitmap;
}

二、圖片按比例大小壓縮方法(根據路徑獲取圖片并壓縮)

private Bitmap getimage(String srcPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此時返回bm為空

    newOpts.inJustDecodeBounds = false;
    int w = newOpts.outWidth;
    int h = newOpts.outHeight;
    //現在主流手機比較多是800*480分辨率,所以高和寬我們設置為
    float hh = 800f;//這里設置高度為800f
    float ww = 480f;//這里設置寬度為480f
    //縮放比。由于是固定比例縮放,只用高或者寬其中一個數據進行計算即可
    int be = 1;//be=1表示不縮放
    if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
        be = (int) (newOpts.outWidth / ww);
    } else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
        be = (int) (newOpts.outHeight / hh);
    }
    if (be <= 0)
        be = 1;
    newOpts.inSampleSize = be;//設置縮放比例
    //重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
    bitmap = BitmapFactory.decodeFile(srcPath, newOpts);
    return compressImage(bitmap);//壓縮好比例大小后再進行質量壓縮
}

三、圖片按比例大小壓縮方法(根據Bitmap圖片壓縮)

private Bitmap comp(Bitmap image) {

ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
if( baos.toByteArray().length / 1024>1024) {//判斷如果圖片大于1M,進行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢出
    baos.reset();//重置baos即清空baos
    image.compress(Bitmap.CompressFormat.JPEG, 50, baos);//這里壓縮50%,把壓縮后的數據存放到baos中
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
BitmapFactory.Options newOpts = new BitmapFactory.Options();
//開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
newOpts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
//現在主流手機比較多是800*480分辨率,所以高和寬我們設置為
float hh = 800f;//這里設置高度為800f
float ww = 480f;//這里設置寬度為480f
//縮放比。由于是固定比例縮放,只用高或者寬其中一個數據進行計算即可
int be = 1;//be=1表示不縮放
if (w > h && w > ww) {//如果寬度大的話根據寬度固定大小縮放
    be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {//如果高度高的話根據寬度固定大小縮放
    be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
    be = 1;
newOpts.inSampleSize = be;//設置縮放比例
//重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
isBm = new ByteArrayInputStream(baos.toByteArray());
bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
return compressImage(bitmap);//壓縮好比例大小后再進行質量壓縮

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370