為什么要壓縮
1、體積的原因
如果你的圖片是要準(zhǔn)備上傳的,那動(dòng)輒幾M的大小肯定不行的,甚至幾十兆肯定是不行的。
2、 內(nèi)存原因
如果圖片要顯示下Android設(shè)備上,ImageView最終是要加載Bitmap對(duì)象的,就要考慮單個(gè)Bitmap對(duì)象的內(nèi)存占用了,如何計(jì)算一張圖片的加載到內(nèi)存的占用呢?其實(shí)就是所有像素的內(nèi)存占用總和:
bitmap內(nèi)存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)
起決定因素就是最后那個(gè)參數(shù)了,Bitmap'常見有2種編碼方式:ARGB_8888和RGB_565,ARGB_8888每個(gè)像素點(diǎn)4個(gè)byte,RGB_565是2個(gè)byte,一般都采用ARGB_8888這種。那么常見的1080*1920的圖片內(nèi)存占用就是:
1920 x 1080 x 4 = 7.9M
其中,A代表透明度;R代表紅色;G代表綠色;B代表藍(lán)色。
- ALPHA_8
表示8位Alpha位圖,即A=8,一個(gè)像素點(diǎn)占用1個(gè)字節(jié),它沒有顏色,只有透明度 - ARGB_4444
表示16位ARGB位圖,即A=4,R=4,G=4,B=4,一個(gè)像素點(diǎn)占4+4+4+4=16位,2個(gè)字節(jié) - ARGB_8888
表示32位ARGB位圖,即A=8,R=8,G=8,B=8,一個(gè)像素點(diǎn)占8+8+8+8=32位,4個(gè)字節(jié) - RGB_565
表示16位RGB位圖,即R=5,G=6,B=5,它沒有透明度,一個(gè)像素點(diǎn)占5+6+5=16位,2個(gè)字節(jié)
壓縮的幾種方式
1、質(zhì)量壓縮 (可降低磁盤空間占用,不會(huì)降低位圖內(nèi)存占用)
InputStream inputStream = getAssetsInputStream(mContext, "img/little_img.jpg");
Bitmap bit = BitmapFactory.decodeStream(inputStream);
log("壓縮前 bitmap 大小" + (bit.getByteCount() / 1024f / 1024f)
+ "M 寬度為" + bit.getWidth() + " 高度為" + bit.getHeight());
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int quality = Integer.valueOf(editText.getText().toString());
bit.compress(Bitmap.CompressFormat.JPEG, quality, baos);
byte[] bytes = baos.toByteArray();
Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
log("壓縮后 bitmap 大小" + (bm.getByteCount() / 1024f / 1024f)
+ "M 寬度為" + bm.getWidth() + " 高度為" + bm.getHeight()
+ " bytes.length= " + (bytes.length / 1024) + "KB"
+ " quality=" + quality);
其中quality是從edittext獲取的數(shù)字,可以從0–100改變,這里出來的log是
- quality =100(不壓縮)
E: 壓縮前 bitmap 大小0.6159668M 寬度為464 高度為348
E: 壓縮后 bitmap 大小0.6159668M 寬度為464 高度為348 bytes.length= 172KB quality=100
- quality =50
E: 壓縮前 bitmap 大小0.6159668M 寬度為464 高度為348
E: 壓縮后 bitmap 大小0.6159668M 寬度為464 高度為348 bytes.length= 28KB quality=50
- quality =10
E: 壓縮前 bitmap 大小0.6159668M 寬度為464 高度為348
E: 壓縮后 bitmap 大小0.6159668M 寬度為464 高度為348 bytes.length= 10KB quality=10
從上面可以看出bitmap所占用內(nèi)存的大小是沒有變化的,這是因?yàn)椋?code>bitmap內(nèi)存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù),
bytes.length是隨著quality變小而變小的,這樣就比較適合上傳圖片,或者微信分享等傳遞bytes的場景
這里需要注意:
這里要說,如果是bit.compress(CompressFormat.PNG, quality, baos);這樣的png格式,quality就沒有作用了,bytes.length不會(huì)變化,因?yàn)閜ng圖片是無損的,不能進(jìn)行壓縮。
測試下:
1、修改成png圖片、format格式依然是CompressFormat.JPEG
//引用一個(gè)png格式的圖片
InputStream inputStream = getAssetsInputStream(mContext, "img/ic_launcher.png");
//依然使用Bitmap.CompressFormat.JPEG 模式
bit.compress(Bitmap.CompressFormat.JPEG, quality, baos);
//
//quality=100
E: 壓縮前 bitmap 大小0.07910156M 寬度為144 高度為144
E: 壓縮后 bitmap 大小0.07910156M 寬度為144 高度為144 bytes.length= 13KB quality=100
//quality=10
E: 壓縮前 bitmap 大小0.07910156M 寬度為144 高度為144
E: 壓縮后 bitmap 大小0.07910156M 寬度為144 高度為144 bytes.length= 1KB quality=10
可以看到只是改圖片格式,二不修改壓縮模式,則依然能成功壓縮bytes數(shù)組長度。(本質(zhì)上還是改變了圖片的格式,另外需要注意這里透明通道就沒了,如果我們此時(shí)有透明設(shè)置的圖片就可能出現(xiàn)問題,比如圖片有黑邊)
2、修改成png圖片、format格式依然是CompressFormat.PNG
//引用一個(gè)png格式的圖片
InputStream inputStream = getAssetsInputStream(mContext, "img/ic_launcher.png");
//使用Bitmap.CompressFormat.PNG模式
bit.compress(Bitmap.CompressFormat.PNG, quality, baos);
//
//quality=100
E: 壓縮前 bitmap 大小0.07910156M 寬度為144 高度為144
E: 壓縮后 bitmap 大小0.07910156M 寬度為144 高度為144 bytes.length= 6KB quality=100
//quality=10
E: 壓縮前 bitmap 大小0.07910156M 寬度為144 高度為144
E: 壓縮后 bitmap 大小0.07910156M 寬度為144 高度為144 bytes.length= 6KB quality=10
可以看出png 模式下的 圖片轉(zhuǎn)換的bytes數(shù)組更小,且有損壓縮不會(huì)改變bytes的長度。
結(jié)論:質(zhì)量壓縮不會(huì)減少圖片的像素(圖片寬高不變),它是在保持像素的前提下改變圖片的位深及透明度等,來達(dá)到壓縮圖片的目的,更適合應(yīng)用于圖片上傳一類的場景,而不適合針對(duì)加載到內(nèi)存中的bitmap的壓縮
2、采樣率壓縮 (可降低磁盤空間占用,和位圖內(nèi)存占用)
InputStream inputStream = getAssetsInputStream(mContext, "img/ic_launcher.png");
BitmapFactory.Options options = new BitmapFactory.Options();
int inSampleSize;
options.inSampleSize = inSampleSize = Integer.valueOf(editText.getText().toString());
Bitmap bm = BitmapFactory.decodeStream(inputStream, null, options);
log("壓縮后圖片的大小" + (bm.getByteCount() / 1024f / 1024f)
+ "M 寬度為" + bm.getWidth() + " 高度為" + bm.getHeight() + " 當(dāng)前采樣率為" + inSampleSize);
E: 壓縮后圖片的大小0.07910156M 寬度為144 高度為144 當(dāng)前采樣率為1
E: 壓縮后圖片的大小0.01977539M 寬度為72 高度為72 當(dāng)前采樣率為2
E: 壓縮后圖片的大小0.0087890625M 寬度為48 高度為48 當(dāng)前采樣率為3
E: 壓縮后圖片的大小3.8146973E-6M 寬度為1 高度為1 當(dāng)前采樣率為100
可以看到隨著采樣率的提高,圖片的寬高不斷降低,bitmap所占用的內(nèi)存也一直在降低。
例如我們設(shè)置采樣率為2的時(shí)候,則寬和高都為原來的1/2,寬高都減少了,自然內(nèi)存也降低了。
這里還有一個(gè)注意項(xiàng):上面的代碼沒用options.inJustDecodeBounds = true; 是因?yàn)槲覀儨y試需要取樣Biemap的數(shù)據(jù)做對(duì)比(此時(shí)bitmap不為空),當(dāng)inJustDecodeBounds設(shè)置為true的時(shí)候,BitmapFactory通過decodeResource或者decodeFile解碼圖片時(shí),將會(huì)返回空(null)的Bitmap對(duì)象,這樣可以避免Bitmap的內(nèi)存分配,但是它可以返回Bitmap的寬度、高度以及MimeType(均通過options獲取),以便我們在不占用內(nèi)存的情況下,計(jì)算并設(shè)置好像要的采樣率,然后在加載到內(nèi)存,防止內(nèi)存被撐爆的現(xiàn)象發(fā)生。
比如我用華為榮耀10拍攝了一張照片,如果直接轉(zhuǎn)成bitmap加載到內(nèi)存,可能就大概率出現(xiàn)OOM異常:
06-17 18:47:54.946 25888-25888/com.canzhang.sample E/ImgTestFragment: 壓縮前圖片的大小60M寬度為4608高度為3456
06-17 18:47:56.386 25888-25888/com.canzhang.sample E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.canzhang.sample, PID: 25888
java.lang.OutOfMemoryError: Failed to allocate a 63701004 byte allocation with 8388576 free bytes and 25MB until OOM
結(jié)論:采樣率壓縮會(huì)直接改變bitmap位圖的寬高,從而降低bitmap內(nèi)存的占用
3、縮放法壓縮(martix) (可降低磁盤空間占用,和位圖內(nèi)存占用)
InputStream inputStream = getAssetsInputStream(mContext, "img/ic_launcher.png");
Bitmap bm = BitmapFactory.decodeStream(inputStream);
Matrix matrix = new Matrix();
matrix.setScale(num, num);
bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),
bm.getHeight(), matrix, true);
log("壓縮后圖片的大小" + (bm.getByteCount() / 1024f / 1024f)
+ "M 寬度為" + bm.getWidth() + " 高度為" + bm.getHeight()+" 縮放比"+num);
E: 壓縮后圖片的大小0.07910156M 寬度為144 高度為144 縮放比1.0
E: 壓縮后圖片的大小0.01977539M 寬度為72 高度為72 縮放比0.5
E: 壓縮后圖片的大小7.4768066E-4M 寬度為14 高度為14 縮放比0.1
E: 壓縮后圖片的大小0.31640625M 寬度為288 高度為288 縮放比2.0
結(jié)論:從log可以看出來,不僅可以變小還可以變大,同樣可以有效的降低bitmap占用內(nèi)存的大小。不過貌似不太適合降低bitmap內(nèi)存占用的大多數(shù)場景,因?yàn)榭s放之前bitmap已經(jīng)加載到內(nèi)存中去了,再縮放也只是特殊場景的應(yīng)用了,我們平常拍照或者選擇圖片加載到內(nèi)存利用這個(gè)顯然是不太適合的
更多請(qǐng)參考:https://blog.csdn.net/nupt123456789/article/details/24600055
4、RGB_565法(沒有透明通道)
InputStream inputStream = getAssetsInputStream(mContext, "img/ic_launcher.png");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bm = BitmapFactory.decodeStream(inputStream, null, options);
log("壓縮后圖片的大小" + (bm.getByteCount() / 1024f / 1024f)
+ "M 寬度為" + bm.getWidth() + " 高度為" + bm.getHeight() );
從代碼可以看到同樣是利用BitmapFactory.Options,這也就意味著可以使用options.inJustDecodeBounds = true(不加載到內(nèi)存),這個(gè)就比較適合一般減少內(nèi)存的場景利用。
E: 壓縮后圖片的大小30.375M 寬度為4608 高度為3456 模式:RGB_565
E: 壓縮后圖片的大小30.375M 寬度為4608 高度為3456 模式:ARGB_4444
E: 壓縮后圖片的大小60.75M 寬度為4608 高度為3456 模式:ARGB_8888
注意:由于ARGB_4444的畫質(zhì)慘不忍睹,一般假如對(duì)圖片沒有透明度要求的話,可以改成RGB_565,相比ARGB_8888將節(jié)省一半的內(nèi)存開銷。
結(jié)論:我們看到圖片大小直接縮小了一半,長度和寬度也沒有變,相比argb_8888減少了一半的內(nèi)存。
本文章主要用于個(gè)人測試總結(jié),參考文章:http://blog.csdn.net/harryweasley/article/details/51955467