Android圖片壓縮(二次采樣)

一、簡介:

在開發(fā)過程中,我們或多或少的都會接觸到Bitmap這個東西,用的不好的話就會出現(xiàn)OOM問題,同時,也會有壓縮的需求,可能有人會說,向Glide不是已經(jīng)對圖片壓縮了么,但有時向圖片上傳到服務(wù)器功能,還得需要我們手動處理,去壓縮圖片后,再上傳,否則,會造成上傳很慢,尤其是用戶網(wǎng)速不好的時候,還會浪費用戶流量,甚至上傳失敗。

二、首先了解一下關(guān)于Bitmap的Config的理解

1、)A:透明度 R:紅色 G:綠 B:藍

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5);
}

Bitmap.Config ARGB_4444:每個像素占四位,即A=4,R=4,G=4,B=4,那么一個像素點占4+4+4+4=16位

Bitmap.Config ARGB_8888:每個像素占四位,即A=8,R=8,G=8,B=8,那么一個像素點占8+8+8+8=32位

Bitmap.Config RGB_565:每個像素占四位,即R=5,G=6,B=5,沒有透明度,那么一個像素點占5+6+5=16位

Bitmap.Config ALPHA_8:每個像素占四位,只有透明度,沒有顏色。

2、)內(nèi)存計算

一張 1024 * 1024 像素,采用ARGB8888格式,一個像素32位,每個像素就是4字節(jié),占有內(nèi)存就是4M若采用RGB565,一個像素16位,每個像素就是2字節(jié),占有內(nèi)存就是2M。
Glide加載圖片默認格式RGB565,Picasso為ARGB8888,默認情況下,Glide占用內(nèi)存會比Picasso低,色彩不如Picasso鮮艷,自然清晰度就低。
通常我們優(yōu)化Bitmap時,當需要做性能優(yōu)化或者防止OOM(Out Of Memory),我們通常會使用Bitmap.Config.RGB_565這個配置,因為Bitmap.Config.ALPHA_8只有透明度,顯示一般圖片沒有意義,Bitmap.Config.ARGB_4444顯示圖片不清楚,Bitmap.Config.ARGB_8888占用內(nèi)存最多。

圖片加載
如果我們想要加載一張大圖到內(nèi)存中,如果不進行壓縮的話,那么很顯然就會出現(xiàn)OOM的崩潰,



譬如我們加載一張5440*3000的大圖到手機上面,如果不進行壓縮處理的話,那么就會出現(xiàn)OOM。
錯誤日志如下

java.lang.OutOfMemoryErrorandroid.graphics.BitmapFactory.nativeDe
codeStream(Native 
Method)android.graphics.BitmapFactory.decodeStreamInternal(Bitmap
Factory.java:703)android.graphics.BitmapFactory.decodeStream(Bitm
apFactory.java:679)android.graphics.BitmapFactory.decodeFile(Bitma
pFactory.java:446)android.graphics.BitmapFactory.decodeFile(Bitmap
Factory.java:480)com.example.ly.bitmapdemo.MainActivity.onCreate(M
ainActivity.java:21)

導致這種情況的發(fā)生的根據(jù)原因就是內(nèi)存溢出,Android給每個APP的內(nèi)存都是有限的,所以不能容忍這種情況的發(fā)生,所以我們就必須進行壓縮一下。
壓縮后的效果如下:


圖片壓縮

我們在上傳一張圖片到服務(wù)器時一般都會先進行壓縮一下,這樣不僅可以節(jié)省流量同時也可以節(jié)約上傳的時間。最近碰到項目里碰到一個問題:圖片上傳時,有時會出現(xiàn)超時的問題,客戶那邊出現(xiàn)的頻率非常高,而我的手機卻基本沒出現(xiàn)過,檢查了一下原因,大概有兩個:
客戶的網(wǎng)速不是太好,3G速度不夠,上傳圖片需要很長時間導致超時產(chǎn)生。圖片很大,占了3、4M,沒經(jīng)過壓縮直接上傳,導致超時產(chǎn)生。

針對以上兩種情況,主要對于第二種原因進行優(yōu)化。總結(jié)來說就是將圖片進行壓縮再上傳,經(jīng)過一系列的操作,發(fā)現(xiàn)超時現(xiàn)象基本不會出現(xiàn)了,原先2M的圖片,經(jīng)過壓縮只有50Kb不到,壓縮率達到60%,效果很明顯。

原理分析

如何將一張大圖壓縮到100kb以下并且保持不失真的特性?這就需要用到下面這個類了BitmapFactory.Options

BitmapFactory.Options縮放圖片主要用到inSample采樣率,

inSample = 1,采樣后圖片的寬高為原始寬高
inSample > 1,例如2,寬高均為原圖的寬高的1/2

一個采用ARGB8888的1024 *1024 的圖片
inSample = 1,占用內(nèi)存就 1024 *1024 *4 = 4M
inSample = 2,占用內(nèi)存就 512 *512 * 4 = 1M

BitmapFactory 給我們提供了一個解析圖片大小的參數(shù)類 BitmapFactory.Options ,把這個類的對象的 inJustDecodeBounds 參數(shù)設(shè)置為 true,這樣解析出來的 Bitmap 雖然是個 null,但是 options 中可以得到圖片的寬和高以及圖片的類型。得到了圖片實際的寬和高之后我們就可以進行壓縮設(shè)置了,主要是計算圖片的采樣率。

       //第一次采樣
        BitmapFactory.Options options = new BitmapFactory.Options();
        //該屬性設(shè)置為true只會加載圖片的邊框進來,并不會加載圖片具體的像素點,也就是說不會把圖片加載到內(nèi)存中
        options.inJustDecodeBounds = true;
        //第一次加載圖片,這時只會加載圖片的邊框進來,并不會加載圖片中的像素點
        BitmapFactory.decodeFile(filePath, options);
        //獲得原圖的寬和高
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;

接下來就需要進行選定壓縮的采樣率了。目前市場上的主流手機分辨率一般最低是720-1280了所以就按照此分辨率進行壓縮

//原始圖片的寬度與720的比值,然后向上取整這里為8 
    int wRatio = (int) Math.ceil(options.outWidth / (float) 720);
 //原始圖片的高度與1280的比值,然后向上取整這里為3
     int hRatio = (int) Math.ceil(options.outHeight / (float) 1280); //獲取采樣率 
      if (wRatio > 1 && hRatio > 1) {
      if (wRatio > hRatio) {
            options.inSampleSize = wRatio; 
           } else { 
              options.inSampleSize = hRatio; 
      } 
  }

經(jīng)過上面這個采樣率進行壓縮后的寬和高肯定是小于720-1270的,我們計算的結(jié)果是:680-375


我們來實際比較一下壓縮結(jié)果:
原先:54403000*如果采用ARGB_8888模式的話,那么如果不壓縮直接加載到內(nèi)存的話,那么它將占:
5440/1024 *3000/1024 *4 = 62.25M,不崩潰才怪呢~

那么現(xiàn)在:680375*見證奇跡的時候,680/1024 *375/1024 *4=0.9M

兩者一比較的話,那么效果還是比較明顯的,相差大約64倍,所以還是可以的。當然了經(jīng)過上面的壓縮方法,我們將壓縮后的圖片上傳到服務(wù)器的話,那么將會大大的減少流量同時也會減少上傳超時的幾率的。

當然了,如果還嫌大的話,我們可以進一步增加壓縮的比例,可以設(shè)置成480800*,那么這樣的話,質(zhì)量肯定是有所下降的。

這里是圖片二次采樣的代碼

public class BitmapUtils {
    /**
     * @param filePath   要加載的圖片路徑
     * @param destWidth  顯示圖片的控件寬度
     * @param destHeight 顯示圖片的控件的高度
     * @return
     */
    public static Bitmap getBitmap(String filePath, int destWidth, int destHeight) {
        //第一次采樣
        BitmapFactory.Options options = new BitmapFactory.Options();
        //該屬性設(shè)置為true只會加載圖片的邊框進來,并不會加載圖片具體的像素點
        options.inJustDecodeBounds = true;
        //第一次加載圖片,這時只會加載圖片的邊框進來,并不會加載圖片中的像素點
        BitmapFactory.decodeFile(filePath, options);
        //獲得原圖的寬和高
        int outWidth = options.outWidth;
        int outHeight = options.outHeight;
        //定義縮放比例
        int sampleSize = 1;
        while (outHeight / sampleSize > destHeight || outWidth / sampleSize > destWidth) {
            //如果寬高的任意一方的縮放比例沒有達到要求,都繼續(xù)增大縮放比例
            //sampleSize應(yīng)該為2的n次冪,如果給sampleSize設(shè)置的數(shù)字不是2的n次冪,那么系統(tǒng)會就近取值
            sampleSize *= 2;
        }
        /********************************************************************************************/
        //至此,第一次采樣已經(jīng)結(jié)束,我們已經(jīng)成功的計算出了sampleSize的大小
        /********************************************************************************************/
        //二次采樣開始
        //二次采樣時我需要將圖片加載出來顯示,不能只加載圖片的框架,因此inJustDecodeBounds屬性要設(shè)置為false
        options.inJustDecodeBounds = false;
        //設(shè)置縮放比例
        options.inSampleSize = sampleSize;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        //加載圖片并返回
        return BitmapFactory.decodeFile(filePath, options);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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