Android圖片壓縮以及優(yōu)化實(shí)例
前言
圖片壓縮在Android技術(shù)中已經(jīng)屬于爛大街,上周看了2個(gè)開源庫(kù)然后對(duì)自己項(xiàng)目的壓縮做了對(duì)比,發(fā)現(xiàn)一些新東西,記錄與此。
為何要壓縮
1、體積的原因
如果你的圖片是要準(zhǔn)備上傳的,那動(dòng)輒幾M的大小肯定不行的,況且圖片分辨率大于設(shè)備分辨率的話毫無(wú)意義。
2、內(nèi)存原因
如果圖片要顯示下Android設(shè)備上,ImageView最終是要加載Bitmap對(duì)象的,就要考慮單個(gè)Bitmap對(duì)象的內(nèi)存占用了,如何計(jì)算一張圖片的加載到內(nèi)存的占用呢?其實(shí)就是所有像素的內(nèi)存占用總和:
bitmap內(nèi)存大小 = 圖片長(zhǎng)度 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
壓縮原理
從上面可以總結(jié)出,圖片壓縮應(yīng)該從兩個(gè)方面入手同時(shí)進(jìn)行:先是降低分辨率,然后降低每個(gè)像素的質(zhì)量也就是內(nèi)存占用。
分辨率壓縮
假設(shè)有張?jiān)瓐D是3840x2400,我想壓縮成1920x1080,實(shí)際是不可能100%能壓縮這個(gè)值的。因?yàn)閳D片壓縮要保證寬高比,試想一下800x100的橫向圖可能壓成20x200豎向圖嗎? 不可能的.。這里常見的算法就是在1920x1080的范圍內(nèi)保證較短邊,然后按照比例壓縮整個(gè)圖:
這里原圖的寬高比是 3840/2400 = 1.6,目標(biāo)圖的寬高比是1920/1080 = 1.78>1.6,較短邊是高。所以就應(yīng)該按照高的比例來(lái)壓縮。
2400/1080=2.22,這樣真實(shí)目標(biāo)值就是:1728x1080,壓縮比四舍五入是:2,然后通過(guò)下面代碼進(jìn)行壓縮:
private Bitmap compressPixel(String filePath){
Bitmap bmp = null;
BitmapFactory.Options options = new BitmapFactory.Options();
//setting inSampleSize value allows to load a scaled down version of the original image
options.inSampleSize = 2;
//inJustDecodeBounds set to false to load the actual bitmap
options.inJustDecodeBounds = false;
options.inTempStorage = new byte[16 * 1024];
try {
//load the bitmap from its path
bmp = BitmapFactory.decodeFile(filePath, options);
if (bmp == null) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(filePath);
BitmapFactory.decodeStream(inputStream, null, options);
inputStream.close();
} catch (FileNotFoundException exception) {
exception.printStackTrace();
} catch (IOException exception) {
exception.printStackTrace();
}
}
} catch (OutOfMemoryError exception) {
exception.printStackTrace();
}finally {
return bmp;
}
}
看起來(lái)沒什么問(wèn)題,看看實(shí)測(cè)結(jié)果,原圖 3840*2400,大小2.2M,我選4個(gè)分辨率當(dāng)做目標(biāo)值來(lái)壓縮:

可以看出壓縮后的4張圖沒有一張達(dá)到目標(biāo)值,而且偏差較大,原因就是options.inSampleSize這個(gè)屬性,他只能是2的N次方,如果算出來(lái)是7,Android會(huì)取近似值8,以此類推導(dǎo)致這個(gè)值不能壓縮到目標(biāo)值。看了一下Compressor這個(gè)開源庫(kù)他對(duì)此做了處理,把壓縮后的圖片在Canvas上面按照目標(biāo)尺寸重繪,得到一個(gè)新的bitmap:
核心代碼:
Matrix scaleMatrix = new Matrix(); scaleMatrix.setScale(ratioX, ratioY, 0, 0); Canvas canvas = new Canvas(scaledBitmap); canvas.setMatrix(scaleMatrix); canvas.drawBitmap(bmp, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
用Compressor開源庫(kù)壓縮的圖片對(duì)比下:

可以看出每次都能壓縮到真實(shí)目標(biāo)值。(注意不是目標(biāo)值,注意區(qū)分目標(biāo)值和真實(shí)目標(biāo)值)
質(zhì)量壓縮
Bitmap有個(gè)方法 compress(CompressFormat format, int quality, OutputStream stream),quality就是壓縮質(zhì)量傳入0-100,數(shù)值越小壓縮的越厲害。
不過(guò)我們一般不直接設(shè)置這個(gè)數(shù)值,而是自定義一個(gè)壓縮后大小比如300KB,然后動(dòng)態(tài)計(jì)算這個(gè)quality,核心代碼:
//進(jìn)行有損壓縮
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options_ = 100;
actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);
//質(zhì)量壓縮方法,把壓縮后的數(shù)據(jù)存放到baos中 (100表示不壓縮,0表示壓縮到最小)
int baosLength = baos.toByteArray().length;
while (baosLength / 1024 > maxFileSize)
{
//循環(huán)判斷如果壓縮后圖片是否大于maxMemmorrySize,大于繼續(xù)壓縮
baos.reset();
//重置baos即讓下一次的寫入覆蓋之前的內(nèi)容
options_ = Math.max(0, options_ - 10);//圖片質(zhì)量每次減少10
actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);
//將壓縮后的圖片保存到baos中
baosLength = baos.toByteArray().length;
if (options_ == 0)//如果圖片的質(zhì)量已降到最低則,不再進(jìn)行壓縮
break;
}壓縮實(shí)踐
目前成熟的開源庫(kù)有Luban:https://github.com/Curzibn/Luban
這個(gè)開源庫(kù)算法比較復(fù)雜,根據(jù)效果圖前后對(duì)比逆向推算了微信朋友圈的壓縮,最后效果和微信差不多,如果你對(duì)壓縮要求很高可以使用這個(gè)。不過(guò)方法調(diào)用是異步的,回調(diào)形式反饋結(jié)果,這個(gè)不太好。。
Compressor:https://github.com/zetbaitsu/Compressor
這個(gè)開源庫(kù)就是在普通的壓縮算法上做了優(yōu)化改進(jìn),源碼很容易看懂,推薦!下面是用Compressor對(duì)三張大圖不同目標(biāo)值做的壓縮測(cè)試(BV是我們項(xiàng)目的壓縮,忽略就好),質(zhì)量參數(shù)設(shè)的是80%

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android 實(shí)現(xiàn)自定義圓形listview功能的實(shí)例代碼
這篇文章主要介紹了Android 實(shí)現(xiàn)自定義圓形listview功能的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
Android中截取當(dāng)前屏幕圖片的實(shí)例代碼
該篇文章是說(shuō)明在Android手機(jī)或平板電腦中如何實(shí)現(xiàn)截取當(dāng)前屏幕的功能,并把截取的屏幕保存到SDCard中的某個(gè)目錄文件夾下面。實(shí)現(xiàn)的代碼如下:2013-08-08
Android 基于RecyclerView實(shí)現(xiàn)的歌詞滾動(dòng)自定義控件
這篇文章主要介紹了Android 基于RecyclerView實(shí)現(xiàn)的歌詞滾動(dòng)自定義控件,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
android @override 報(bào)錯(cuò)解決方案
android @override 報(bào)錯(cuò):就是說(shuō)Java 1.5的編譯器默認(rèn)對(duì)父類的方法進(jìn)行覆蓋,采用@Override進(jìn)行說(shuō)明;但1.6已經(jīng)擴(kuò)展到對(duì)接口的方法;所以如果還是以Java 1.5的編譯器來(lái)編譯的話,會(huì)出現(xiàn)錯(cuò)誤2012-12-12
詳解Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單)
這篇文章主要介紹了Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Android NDK開發(fā)的環(huán)境搭建與簡(jiǎn)單示例
本文主要介紹Android NDK的知識(shí),這里整理了相關(guān)資料,來(lái)說(shuō)明如何搭建相應(yīng)環(huán)境和簡(jiǎn)單實(shí)例,幫助大家理解,有興趣的小伙伴可以參考下2016-09-09
Android通過(guò)自定義View實(shí)現(xiàn)隨機(jī)驗(yàn)證碼
這篇文章主要介紹了Android通過(guò)自定義View實(shí)現(xiàn)隨機(jī)驗(yàn)證碼的相關(guān)資料,需要的朋友可以參考下2016-03-03

