Android-圖片壓縮(二)-純乾貨

TMusketeer發表於2023-05-03
  1. Android - 圖片壓縮(一)- 專案中取圖片轉bitmap
  2. Android - 圖片壓縮(二)- 純乾貨
前言:讓我們手擼一個圖片壓縮庫,對壓縮工具魯班進行升級改造。
在平常開發當中,我們一般是使用第三方的工具,不僅快,而且上手簡單,基本不用我們動腦子,壓縮我們用魯班壓縮,載入圖片我們用Glide,這兩大工具我想在座的基本上都用吧,不過我們要想提升自己,就要搞清楚深一點的東西,這裡我將會帶著大家做一款壓縮工具,功能媲美魯班壓縮,而且還帶有魯班沒有的功能,如輸出指定格式等,在這個過程中我們要學會如何獲取bitmap,第一篇已經羅列了多種從專案中取圖片轉bitmap,這裡就不多說了。當我們拿到bitmap我們要知道的事情還有很多,比如圖片的色彩模式,一個畫素點大小,什麼是色深,位深等。絕對讓你有收穫,乾貨滿滿,衝呀。
 目錄
1、圖片相關概念
   1.1 ARGB介紹    
  1.2 Bitmap概念
  1.3 色彩模式
  1.4 色深和位深
  1.5 記憶體中Bitmap的大小
  1.6 系統一般分配的大小
2、圖片壓縮方式
3、壓縮的常見方式
  3.1、質量壓縮
  3.2、取樣壓縮
    3.2.1 臨近取樣(臨近點插值演演算法)
    3.2.2 雙線性取樣(雙線性內插值演演算法)
    3.2.3 雙線性取樣對比鄰近取樣的優勢在於:
4、微信和魯班壓縮對比
 

1、圖片相關概念

1.1 ARGB介紹

 
ARGB顏色模型:最常見的顏色模型,裝置相關,四種通道,取值均為[0,255],即轉化成二進位制位0000 0000 ~ 1111 1111。
 
A:Alpha (透明度) R:Red (紅) G:Green (綠) B:Blue (藍)
 

1.2 Bitmap概念

 
Bitmap物件本質是一張圖片的內容在手機記憶體中的表達形式。它將圖片的內容看做是由儲存資料的有限個畫素點組成;每個畫素點儲存該畫素點位置的ARGB值。每個畫素點的ARGB值確定下來,這張圖片的內容就相應地確定下來了。
 

1.3 色彩模式

Bimap.Config下
  1. ALPHA_8 =>8位(1B)
  2. RGB_565 =>16位(2B)
  3. ARGB_4444 =>16位(2B)
  4. ARGB_8888 =>32位元位(4位元組,4B)
  5. RGBA_F16 =>64位(8B)
Bitmap.Config是Bitmap的一個列舉內部類,它表示的就是每個畫素點對ARGB通道值的儲存方案。取值有以下四種:
 
ALPHA_8:每個畫素佔8位(1個位元組),儲存透明度資訊,沒有顏色資訊。
 
RGB_565:沒有透明度,R=5,G=6,B=5,,那麼一個畫素點佔5+6+5=16位(2位元組),能表示2^16種顏色。
 
ARGB_4444:由4個4位組成,即A=4,R=4,G=4,B=4,那麼一個畫素點佔4+4+4+4=16位 (2位元組),能表示2^16種顏色。
 
ARGB_8888:由4個8位組成,即A=8,R=8,G=8,B=8,那麼一個畫素點佔8+8+8+8=32位(4位元組),能表示2^24種顏色。
 
注意:
1 byte(位元組) = 8bit(位)
1 KB = 1024 byte(位元組)
 

1.4 色深和位深

  • 色深:每一個畫素點用多少bit來儲存ARGB值
  • 位深:每個畫素點在計算機中所使用的二進位制數值位數叫做位深度,主要用於儲存
 

1.5 記憶體中Bitmap的大小

從網路下載,asset,sd卡取的圖片計算公式
解析度 * 每個畫素點的大小(如1080*1920*4B = xxx ) (32/8=4)
從res內不同資源目錄下
decodeResource()會經過一次解析度的轉換,再計算大小
新解析度 = 原圖橫向解析度 * (裝置的 dpi / 目錄對應的 dpi ) * 原圖縱向解析度 * (裝置的 dpi / 目錄對應的 dpi )。
 

1.6 系統一般分配的大小

maxMemory() 返回Java虛擬機器將嘗試使用的最大記憶體量。如果沒有固有的限制,則值為Long。將返回MAX_VALUE。
返回:
虛擬機器將嘗試使用的最大記憶體量,以位元組為單位
 
// 獲取應用程式最大可用記憶體(以位元組為單位,/1024/1024=M  華為p20 pro是384M)
		int maxMemory = (int) Runtime.getRuntime().maxMemory();
		int cacheSize = maxMemory / 8;
  // 設定圖片快取大小為程式最大可用記憶體的1/8
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				return bitmap.getByteCount();
			}

----------------------其他獲取方式--------------------
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//最大分配記憶體,單位M
int memoryClass = activityManager.getMemoryClass();
Log.e("memoryclass=",String.valueOf(memoryClass));
//最大分配記憶體獲取方法2
float maxMemory = (float) (Runtime.getRuntime().maxMemory() * 1.0/ (1024 * 1024));
//當前分配的總記憶體
float totalMemory = (float) (Runtime.getRuntime().totalMemory() * 1.0/ (1024 * 1024));
//剩餘記憶體
float freeMemory = (float) (Runtime.getRuntime().freeMemory() * 1.0/ (1024 * 1024));
Log.e("memoryclass=maxMemory",String.valueOf(maxMemory));
Log.e("memoryclass=totalMemory",String.valueOf(totalMemory));
Log.e("memoryclass=freeMemory",String.valueOf(freeMemory));

2、圖片壓縮方式

    • 設定圖片格式
      • png:無損的壓縮圖片格式
      • jpeg:有損的壓縮圖片格式,不能透明設定。
      • webp:同時提供了有損和無損圖片格式。無損的格式要比png小26%,有損的比jpeg大25%-30%。
    • 質量壓縮
    • 取樣率壓縮
    • 縮放壓縮
      • 減少圖片的畫素,縮圖
    • JNI呼叫JPEG庫
 
Android目前常用的圖片格式有png,jpeg和webp,
 
png:無損壓縮圖片格式,支援Alpha通道,Android切圖素材多采用此格式
 
jpeg:有失真壓縮圖片格式,不支援背景透明,適用於照片等色彩豐富的大圖壓縮,不適合logo
 
webp:是一種同時提供了有失真壓縮和無失真壓縮的圖片格式,派生自影片編碼格式VP8,從谷歌官網來看,無損webp平均比png小26%,有損的webp平均比jpeg小25%~34%,無損webp支援Alpha通道,有損webp在一定的條件下同樣支援,有損webp在Android4.0(API 14)之後支援,無損和透明在Android4.3(API18)之後支援
採用webp能夠在保持圖片清晰度的情況下,可以有效減小圖片所佔有的磁碟空間大小
 
Android中Bitmap所佔記憶體大小計算方式:圖片長度 x 圖片寬度 x 一個畫素點佔用的位元組數
 

3、壓縮的常見方式

  • 質量壓縮:降低儲存體積(不改變記憶體中Bitmap的大小)
  • 取樣壓縮:降低圖片的尺寸,(當然儲存體積和記憶體佔用都會降低)
質量壓縮:主要是透過編解碼來達到縮小體積。
取樣壓縮:主要是透過取樣率,畫素點個數等來達到縮小體積
 

3.1、質量壓縮

一般用到Bitmap.compress(Bitmap.CompressFormat.JPEG,quality,outputStream)
 
Bitmap.compress(Bitmap.CompressFormat.JPEG,quality,outputStream)
其中
Bitmap.CompressFormat.JPEG
Bitmap.CompressFormat.PNG  無損,無法再質量壓縮?
Bitmap.CompressFormat.WEBP  可最佳化30%,比JPEG更加省空間

quality:0-100,1最小體積,100 最高質量,體積也是最大
outputStream:  ByteArrayOutputStream 一個輸出流,壓縮後的流,如果要儲存成圖片檔案,就儲存此流

3.2、取樣壓縮

  • 臨近取樣(臨近點插值演演算法)
  • 雙線性取樣(雙線性內插值演演算法)

3.2.1 臨近取樣(臨近點插值演演算法)

使用了BitmapFactory.options  下的
inSampleSize:取樣壓縮係數  如果是2,直接縮放寬高為原先 二分之一 (1/2)
該方式比較粗暴,2個畫素取一個。寬高都減少了,自然記憶體也降低了。
------------------------------------------------------------------------------
    public static void ljCom(InputStream open){
        BitmapFactory.Options options = new BitmapFactory.Options();
//或者 inDensity 搭配 inTargetDensity 使用,演演算法和 inSampleSize 一樣
        options.inSampleSize = 2; //設定圖片的縮放比例(寬和高) , google推薦用2的倍數:
//        Bitmap bitmap = BitmapFactory.decodeFile("app/src/main/assets/wx.png");
//        Bitmap compress = BitmapFactory.decodeFile("app/src/main/assets/wx.png", options);

        Bitmap mbitmap = BitmapFactory.decodeStream(open, null, options);

        Log.e("雙線性取樣","----壓縮後----》"
                +"\n記憶體大小》"+mbitmap.getByteCount()
                +"\n寬度》"+mbitmap.getWidth()
                +"\n高度》"+mbitmap.getHeight());

    }

3.2.2 雙線性取樣(雙線性內插值演演算法)

雙線性取樣(Bilinear Resampling)在 Android 中的使用方式一般有兩種:
Bitmap bitmap = BitmapFactory.decodeFile("xxx.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
或者直接使用 matrix 進行縮放

Bitmap bitmap = BitmapFactory.decodeFile("xxx.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
看原始碼可以知道createScaledBitmap函式最終也是使用第二種方式的matrix進行縮放,雙線性取樣使用的是雙線性內插值演演算法,這個演演算法不像鄰近點插值演演算法一樣,直接粗暴的選擇一個畫素,而是參考了源畫素相應位置周圍2x2個點的值,根據相對位置取對應的權重,經過計算之後得到目標影像。
 
雙線性內插值演演算法在影像的縮放處理中具有抗鋸齒功能, 是最簡單和常見的影像縮放演演算法,當對相鄰2x2個畫素點採用雙線性內插值演演算法時,所得表面在鄰域處是吻合的,但斜率不吻合,並且雙線性內插值演演算法的平滑作用可能使得影像的細節產生退化,這種現象在上取樣時尤其明顯。
 
filter:
當進行的不只是平移變換時,filter引數為true可以進行濾波處理,有助於改善新影像質量;flase時,計算機不做過濾處理。
使用方式
/**
 * 雙線性取樣
 * */
public static void sxxCon(Bitmap bitmap){
    Log.e("雙線性取樣","----壓縮前----》"
            +"\n記憶體大小》"+bitmap.getByteCount()
            +"\n原圖寬度》"+bitmap.getWidth()
            +"\n原圖高度》"+bitmap.getHeight());
    //等比例縮放壓縮
    Bitmap mbitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
    Log.e("雙線性取樣","----壓縮後----》"
            +"\n記憶體大小》"+mbitmap.getByteCount()
            +"\n寬度》"+mbitmap.getWidth()
            +"\n高度》"+mbitmap.getHeight());
}
/**
 * 雙線性取樣
 * */
public static void sxxCon2(Context context,Bitmap bitmap){
    Log.e("雙線性取樣","----壓縮前----》"
            +"\n記憶體大小》"+bitmap.getByteCount()
            +"\n寬度》"+bitmap.getWidth()
            +"\n高度》"+bitmap.getHeight());
    Matrix matrix = new Matrix();
    matrix.setScale(0.6f, 0.6f);
    Bitmap mbitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    Log.e("雙線性取樣","----壓縮後----》\n"
            +"\n記憶體大小》"+mbitmap.getByteCount()
            +"\n寬度》"+mbitmap.getWidth()
            +"\n高度》"+mbitmap.getHeight());
    saveBitmapAsPng2(context,mbitmap,"aaq");
}

3.2.3 雙線性取樣對比鄰近取樣的優勢在於:

 
它的係數可以是小數,而不一定是整數,在某些壓縮限制下,效果尤為明顯
處理文字比較多的圖片在展示效果上的差別,雙線性取樣效果要更好
 

4、微信和魯班壓縮對比

 
微信
LuBan
1
取樣率壓縮
 
2
進行寬高的等比壓縮(微信對原圖和縮圖限制了最大長寬或者最小長寬)
進行寬高的等比壓縮
3
對圖片的質量進行壓縮(一般75或者70)
應該說的是quality=75
對圖片的質量進行壓縮
4
採用webP的格式
 
 
 
 
 

相關文章