[轉(zhuǎn)]圖片的壓縮

1.概述

在開發(fā)中,對于圖片的操作掠剑,稍有不慎屈芜,可能就會消耗大量的內(nèi)存,導致程序崩潰,所以了解一種通用的技術(shù)去處理和加載圖片井佑,同時保證UI流暢避免OOM現(xiàn)象属铁,是非常有必要的。那么為什么在Android中對于圖片的處理會如此棘手呢毅糟?主要有以下一些原因:

  • .通常情況下红选,移動設(shè)備的內(nèi)存資源是有限的,Android系統(tǒng)會根據(jù)手機的屏幕大小和密度姆另,為每個程序設(shè)置一個最大內(nèi)存限制喇肋,應用程序消耗的內(nèi)存不能超過這個最大內(nèi)存限制,否則就會出現(xiàn)OOM現(xiàn)象迹辐。當然蝶防,這個內(nèi)存限制是跟手機配置相關(guān)聯(lián)的。
  • 圖片的操作會消耗大量的內(nèi)存明吩,特別是細節(jié)豐富的圖片间学,例如照片。以Galaxy Nexus相機為例子印荔,它拍攝一張2592x1936像素的照片低葫,如果使用的位圖配置是ARGB_8888(默認從Android 2.3開始),那么這張照片加載到內(nèi)存仍律,大約會消耗19MB的內(nèi)存(2592 x 1936 x 4字節(jié))嘿悬,僅僅是圖片消耗內(nèi)存的數(shù)值可能已經(jīng)超過了某些設(shè)備的內(nèi)存限制
  • Android的UI經(jīng)常會一次加載多張圖片,例如水泉,ListView善涨、GridView、ViewPager等等

圖片有各種形狀和大小草则。通常情況下钢拧,它們普遍比設(shè)備所需要的圖片要大一些,例如手機相冊顯示手機拍攝的照片炕横,而手機的相機分辨率大多時候是要高于手機屏幕的分辨率源内。鑒于手機的內(nèi)存有限,我們只需要在內(nèi)存中加載一個低分辨率的照片版本就可以了看锉,而這個低分辨率的照片應該與顯示它的控件相匹配姿锭,這就需要對圖片進行壓縮處理了。

Android中有兩種壓縮圖片的方法伯铣。

  • 第一種是針對圖片的長寬進行壓縮呻此,在將圖片加載到內(nèi)存過程中將圖片的長寬進行壓縮,獲取長寬壓縮版的的圖片
  • 第二種是針對圖片的像素進行壓縮腔寡,圖片加載到內(nèi)存后焚鲜,針對圖片質(zhì)量進行壓縮,會導致圖片質(zhì)量下降。

2. 圖片長寬壓縮

2.1 獲取加載圖片的屬性

Android 中的BitmapFactory 類提供了一些解碼方法忿磅,decodeByteArray()糯彬、decodeFile()decodeResource()等等葱她,根據(jù)不通的圖片源選擇不同的解碼方法加載圖片創(chuàng)建出Bitmap撩扒。這些方法中都會傳入一個BitmapFactory.Options 實例化對象,通過這個對象吨些,可以更改一些加載圖片的設(shè)置搓谆。由于這些解碼方法用于解碼加載圖片,會占用內(nèi)存構(gòu)建 Bitmap豪墅,因此很容易導致 OOM 的異常泉手。
如果將 options.inJustDecodeBounds 設(shè)置為 true,在解碼過程中就不會申請內(nèi)存去創(chuàng)建 Bitmap偶器,返回的是一個空的 Bitmap斩萌,但是可以獲取圖片的一些屬性,例如圖片寬高屏轰,圖片類型等等颊郎。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;      // 設(shè)置為true,不將圖片解碼到內(nèi)存中
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);//將解碼出來的bitmap的屬性給了option
int imageHeight = options.outHeight;    // 圖片高度
int imageWidth = options.outWidth;      // 圖片寬度
String imageType = options.outMimeType; // 圖片類型

一般來說霎苗,為了避免OOM的異常袭艺,在加載圖片到內(nèi)存之前,會先檢查圖片的尺寸叨粘,除非你能確保圖片源不會導致OOM。

2.2 縮小圖片的長寬來壓縮圖片

我們知道圖片的大小之后瘤睹,就可以決定是否將完整的圖片加載到內(nèi)存或者加載壓縮版的圖片到內(nèi)存升敲。可以基于以下幾點做出決定:

  • 估計完整圖片加載到內(nèi)存中所使用內(nèi)存
  • 可分配給加載圖片的內(nèi)存
  • 用于顯示圖片的控件的大小
  • 當前設(shè)備的屏幕大小和密度
    例如轰传,如果顯示圖片的控件大小為128x96像素驴党,就沒有必要將一個1024x768像素的圖片加載到內(nèi)存中。

設(shè)置 options.inSampleSize 的數(shù)值获茬,來控制壓縮圖片程度港庄。例如,將 options.inSampleSize 設(shè)置為4恕曲,將一個2048x1536像素的圖片解碼加載到內(nèi)存后產(chǎn)生的 Bitmap 大約為512x384像素鹏氧,如果使用的位圖配置是ARGB_8888,那么僅僅需要0.75M 就加載了縮小版的圖片到內(nèi)存佩谣,而加載完整的圖片需要 12M把还。

也就是說,如果我們設(shè)置 inSampleSize == 2,解碼出來的位圖的寬高是原圖的1/2吊履,圖片所占用內(nèi)存縮小了1/4(1/2 x 1/2)安皱。如果 inSampleSize設(shè)置的值小于等1,都會當做inSampleSize == 1來解碼加載圖片艇炎。

于是我們可以在加載圖片的時候酌伊,根據(jù)控件的大小(顯示到屏幕上的大凶鹤佟)來計算出加壓縮版圖片的 inSampleSize值居砖。

 /**
     * 計算inSampleSize值
     *
     * @param options
     *          用于獲取原圖的長寬
     * @param reqWidth
     *          要求壓縮后的圖片寬度
     * @param reqHeight
     *          要求壓縮后的圖片長度
     * @return
     *          返回計算后的inSampleSize值
     */
    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 halfHeight = height / 2;
            final int halfWidth = width / 2;


            // 計算inSampleSize值
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

有人可能會疑問為什么每次 inSampleSize 都是乘以2,指數(shù)增長辜贵。這是因為在加載圖片過程中悯蝉,解析器使用的inSampleSize都是2的指數(shù)倍,如果 inSampleSize是其他值托慨,則找一個離這個值最近的2的指數(shù)值鼻由。

上面已經(jīng)獲取了inSampleSize,然后就可以根據(jù)這個值來加載壓縮版的圖片了

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {
    // 先將inJustDecodeBounds設(shè)置為true來獲取圖片的長寬屬性
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // 計算inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 加載壓縮版圖片
    options.inJustDecodeBounds = false;
    // 根據(jù)具體情況選擇具體的解碼方法
    return BitmapFactory.decodeResource(res, resId, options);
}

獲取到了壓縮版的 Bitmap 之后就可以直接設(shè)置到屏幕的控件上了厚棵。

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

3. 圖片質(zhì)量壓縮

3.1 方法介紹

上面一種方法是通過縮放圖片的大小來達到壓縮效果蕉世,基本不會對圖片的顯示效果有影響。但是現(xiàn)在介紹的這一種方法婆硬,可能會導致圖片質(zhì)量下降狠轻。

使用的是下面這個方法來進行壓縮。

Bitmap.compress(CompressFormat format, int quality, OutputStream stream)

這個方法有三個參數(shù)彬犯,是布爾類型的返回值

  • CompressFormat 指定的 Bitmap 被壓縮成的圖片格式向楼,只支持 JPEG,PNG谐区,WEBP 三種
  • quality 圖片壓縮質(zhì)量的控制湖蜕,范圍為 0~100,0表示壓縮后體積最小宋列,但是質(zhì)量也是最差昭抒,100表示壓縮后體積最大,但是質(zhì)量也是最好的(個人認為相當于未壓縮)炼杖,有些格式灭返,例如 png,它是無損的坤邪,所以會忽略這個值熙含。
  • OutputStream 壓縮后的數(shù)據(jù)會寫入這個字節(jié)流中
    返回值表示返回的字節(jié)流是否可以使用 BitmapFactory.decodeStream()解碼成 Bitmap,至于返回值是怎么得到的艇纺,因為是Native的代碼婆芦,沒法找到邏輯怕磨。

3.2 色位深度介紹

接下來說說為什么用這個方法可能會導致圖片質(zhì)量下降。在 Bitmap 中有一個 Config 的屬性消约,這個屬性是用來描述每個像素被儲存的大小肠鲫。目前 Config有四個值:ALPHA_8、RGB_565或粮、ARGB_4444导饲、ARGB_8888。這個說明一下(我個人的理解氯材,真心不好解釋)渣锦,每一個像素會可能由四個屬性組成,R(Red紅色通道)氢哮、G(Green綠色通道)袋毙、B(Blue藍色通道)、A(Alpha透明度通道)冗尤。

Config 每個像素占用的字節(jié) 說明
ALPHA_8 1 bytes 每個像素僅僅儲存透明度通道
RGB_565 2 bytes 每個像素的RGB通道會保存听盖,透明度不會保存,紅色通道5位裂七,有25=32種表現(xiàn)形式皆看;綠色通道6位,有26=64種表現(xiàn)形式背零;藍色通道5位腰吟,有2^5=32種表現(xiàn)形式
ARGB_4444 2 bytes 每個像素的ARGB通道都會保存,透明度/紅色/綠色/藍色通道4位徙瓶,有2^4=16種表現(xiàn)形式
ARGB_8888 4 bytes 每個像素的ARGB通道都會保存毛雇,透明度/紅色/綠色/藍色通道8位,有2^8=256種表現(xiàn)形式

有什么區(qū)別呢侦镇?最簡單的禾乘,當一個顏色表現(xiàn)形式越多,那么畫面整體的色彩就會更豐富虽缕,圖片質(zhì)量就會越高,當然蒲稳,圖片占用的儲存空間也越大氮趋。

3.3 圖片質(zhì)量下降的原因

前面提到過調(diào)用Bitmap.compress()方法時候,會傳入一個壓縮后的圖片格式江耀,但是由于并不是所有的圖片格式都支持上面說的 Config的所有通道剩胁,比如說,JPEG格式的圖片祥国,是不支持Alpha(透明度)屬性的昵观,這樣將壓縮后返回的字節(jié)流通過 BitmapFactory.decodeStream()轉(zhuǎn)換成Bitmap的過程中晾腔,會將透明度屬性給丟棄,導致圖片質(zhì)量下降啊犬。

3.4 壓縮過程介紹

壓縮過程如下灼擂,通過依次減少圖片質(zhì)量,將圖片大小控制在限制值范圍內(nèi)觉至。

/**
 * 壓縮圖片
 * 
 * @param bitmap
 *          被壓縮的圖片
 * @param sizeLimit
 *          大小限制
 * @return
 *          壓縮后的圖片
 */
private Bitmap compressBitmap(Bitmap bitmap, long sizeLimit) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int quality = 100;
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);

    // 循環(huán)判斷壓縮后圖片是否超過限制大小
    while(baos.toByteArray().length / 1024 > sizeLimit) {
        // 清空baos
        baos.reset();
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        quality -= 10;
    }

    Bitmap newBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(baos.toByteArray()), null, null);

    return newBitmap;
}

4. 進一步優(yōu)化

上面提到的很多壓縮方法剔应,如果是在UI線程執(zhí)行的話,很有可能阻塞到主線程语御,這是在開發(fā)過程中非常不愿意見到的事情峻贮,所以我們需要在后臺線程去執(zhí)行這些壓縮圖片比較耗時的操作,然后獲取到壓縮后的圖片应闯,設(shè)置到屏幕中纤控。使用AsyncTask可以幫助我們很好的實現(xiàn)。

/**
 * 壓縮圖片
 * 
 * @param bitmap
 *          被壓縮的圖片
 * @param sizeLimit
 *          大小限制
 * @return
 *          壓縮后的圖片
 */
private Bitmap compressBitmap(Bitmap bitmap, long sizeLimit) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int quality = 100;
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);

    // 循環(huán)判斷壓縮后圖片是否超過限制大小
    while(baos.toByteArray().length / 1024 > sizeLimit) {
        // 清空baos
        baos.reset();
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        quality -= 10;
    }

    Bitmap newBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(baos.toByteArray()), null, null);

    return newBitmap;
}

5. 總結(jié)

圖片的處理碉纺,時刻都需要注意船万,因為機型配置的不同,以及現(xiàn)場設(shè)備內(nèi)存使用的情況惜辑,都有可能導致OOM的現(xiàn)象唬涧,上述提到了壓縮方法,基本適用與大部分圖片壓縮情況盛撑。當然如果對圖片畫質(zhì)顯示有要求碎节,可能就需要特殊的處理了,這個就不在大部分場景的考慮內(nèi)抵卫。

友情鏈接 : Android實現(xiàn)圖片壓縮(bitmap的六種壓縮方式)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狮荔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子介粘,更是在濱河造成了極大的恐慌殖氏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姻采,死亡現(xiàn)場離奇詭異雅采,居然都是意外死亡,警方通過查閱死者的電腦和手機慨亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門婚瓜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刑棵,你說我怎么就攤上這事巴刻。” “怎么了蛉签?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵胡陪,是天一觀的道長沥寥。 經(jīng)常有香客問我,道長柠座,這世上最難降的妖魔是什么邑雅? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮愚隧,結(jié)果婚禮上蒂阱,老公的妹妹穿的比我還像新娘。我一直安慰自己狂塘,他們只是感情好录煤,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荞胡,像睡著了一般妈踊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泪漂,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天廊营,我揣著相機與錄音,去河邊找鬼萝勤。 笑死露筒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的敌卓。 我是一名探鬼主播慎式,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼趟径!你這毒婦竟也來了瘪吏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蜗巧,失蹤者是張志新(化名)和其女友劉穎掌眠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幕屹,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蓝丙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了望拖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺尘。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖靠娱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掠兄,我是刑警寧澤像云,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布锌雀,位于F島的核電站,受9級特大地震影響迅诬,放射性物質(zhì)發(fā)生泄漏腋逆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一侈贷、第九天 我趴在偏房一處隱蔽的房頂上張望惩歉。 院中可真熱鬧,春花似錦俏蛮、人聲如沸撑蚌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽争涌。三九已至,卻和暖如春辣恋,著一層夾襖步出監(jiān)牢的瞬間亮垫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工伟骨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饮潦,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓携狭,卻偏偏與公主長得像继蜡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暑中,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容