在介紹Android平臺的壓縮方案以前,先了解一下Bitmap的幾個主要概念。html
像素密度git
像素密度指的是每英寸像素數目,在Bitmap裏用mDensity/mTargetDensity,mDensity默認是設備屏幕的像素密度,mTargetDensity是圖片的目標像素密度,在加載圖片時就是 drawable 目錄的像素密度。github
色彩模式->色彩模式是數字世界中表示顏色的一種算法,在Bitmap裏用Config來表示。web
Bitmap的計算方式算法
memory=scaledWidth*scaledHeight*每一個像素所佔字節數
複製代碼
其中
scaledWidth : widthtargetDensity/density+0.5
scaledHeight: heighttargetDensity/density+0.5性能優化
scaledWidth
表示水平方向的像素值,width
表示屏幕寬度,targetDensity
表示手機的像素密度,這個值通常跟手機相關,density
表示decodingBitmap 的 density,這個值通常跟圖片放置的目錄有關(hdpi/xxhdpi)scaledHeight同理併發
每一個像素所佔字節數:這個值跟色彩模式相關,默認 ARGB_8888 則是4個字節,函數
在Bitmap種有兩個獲取內存佔用大小的方法post
二者的區別:性能
在不復用 Bitmap 時,getByteCount() 和 getAllocationByteCount 返回的結果是同樣的。在經過複用 Bitmap 來解碼圖片時,那麼 getByteCount() 表示新解碼圖片佔用內存的大小,getAllocationByteCount() 表示被複用 Bitmap真實佔用的內存大小(即 mBuffer 的長度)。
質量壓縮的關鍵在於Bitmap.compress()函數,該函數不會改變圖像的大小,可是能夠下降圖像的質量,從而下降存儲大小,進而達到壓縮的目的。
這裏提到的圖像的質量主要指的是圖片的色彩空間
通常圖像的色彩空間爲RGB,主要經過RGB三原色通道來描述圖片,其中又有ARGB格式,比起RGB多了一個透明度的通道。
Android下的質量壓縮主要經過下面這個函數來實現的。
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
複製代碼
三個參數
其中PNG是無損格式的,壓縮效果不太理想,而WEBP會存在兼容性的問題。出於兼容性和效果來看,通常會選擇JPEG做爲壓碎格式。
實例代碼
// R.drawable.thumb 爲 png 圖片
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.thumb);
try {
//保存壓縮圖片到本地
File file = new File(Environment.getExternalStorageDirectory(), "aaa.jpg");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fs = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, fs);
Log.i(TAG, "onCreate: file.length " + file.length());
fs.flush();
fs.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//查看壓縮以後的 Bitmap 大小
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);
byte[] bytes = outputStream.toByteArray();
Bitmap compress = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.i(TAG, "onCreate: bitmap.size = " + bitmap.getByteCount() + " compress.size = " + compress.getByteCount());
複製代碼
咱們再來看看quality
參數被設置爲50先後,兩張圖片的對比.
壓縮前的圖片
壓縮後的圖片
從上述兩圖能夠明顯圖片質量的差異,另外再經過log打印查看會壓縮先後圖片的所佔用的大小是同樣的。
即
bitmap.size = compress.size
複製代碼
**Q:**這裏可能有人就會有疑惑,爲何壓縮事後,兩張圖片的大小還會是同樣的呢?
**A:**由於圖片在內存中的存儲方式和文件中的存儲方式是不同的。圖片壓縮只會影響文件的大小,在這個例子中,壓縮事後存到磁盤的文件大小會比壓縮以前的文件大小減少不少。
內存中所佔的大小沒有變化是由於bitmap沒有變化的緣由。
文章最開始提到Bitmap的計算方式
memory=scaledWidth*scaledHeight*每一個像素所佔字節數
複製代碼
由於是壓縮的質量,全部寬高都不變,而每一個像素所佔的字節數跟色彩空間有關,默認是ARGB_8888
.寬高不變,色彩空間不從新設置,那麼bitmap所佔的大小就不會發生改變。
說道這裏可能又會有個新疑問
**Q:**bitmap佔用的大小不變,那爲何圖片質量降低了呢?這是由於圖片被壓縮過了啊!
**A:**首先要知道JPEG格式是有損壓縮的,JPEG格式的圖片是不支持透明色彩的,這也是JPEG的大小會比PNG小很大,圖片質量會比PNG差的緣由。 在通過了bitmap.compress()
這個流程時,JPEG會捨去透明屬性.這樣存放到磁盤時的文件大小就減少了.而後這個時候再經過BitmapFactory.decodeByteArray()
把圖片加載回來時,加載的是捨去了透明通道的圖片,按理說應該採用 RGB_565
或者RGB_888
這樣的色彩空間加載,可是你沒有另外設置這個參數的話,加載的色彩格式會是默認ARGB_8888
.圖片都沒有透明的色彩空間了,你再給它分配內存就只是浪費內存而已。
這也是爲何壓縮先後,bitmap所佔的大小相同,圖片質量卻有所差距的緣由。
補充一個有趣的事件,在早期的Android平臺下,對一張圖片進行屢次質量壓縮,會獲得一張變綠的圖片。詳情連接
WebP
Webp圖片格式是Google推出的一個支持alpha通道的有損壓縮格式,據Google官方代表,同質量狀況下Webp圖像要比JPEG、PNG圖像小25%~45%左右,在支持上Android4.0+版本提供原生支持,使用libwebp庫進行編解碼。
GIF
GIF圖像最普遍的應用是用於顯示動畫圖像,它具有文件小且支持alpha通道的優勢,不過它是由8位進行表示每一個像素的色彩,僅支持256色,因此在對色彩要求比較高的場合不太適合。
Stream
圖片的存儲形式從File轉到內存中時,圖片內容以字節方式存儲在Stream中,此時所佔的內存大小爲File文件大小。
Bitmap
在Android中,任何圖片資源的顯示對象都是經過bitmap來顯示的,除了xml資源則是經過Canvas來繪製的,因此,對於某些純色或者規則類的圖像,能夠經過xml進行描述或Canvas來繪製,這樣所佔用的內存比經過bitmap來顯示將少幾個等級。
Bitmap與Drawable的聯繫
關於Bitmap和Drawable的關係,能夠看官方的解釋,Drawable是一個抽象的概念,來描述某些具有可繪製的的對象,它是一個抽象類,而Bitmap是一個最簡單的Drawable實體對象,Bitmap並不繼承於Drawable,它們之間創建關聯最終是經過BitmapDrawable對象,該對象會把具體的Bitmap實例對象渲染到Canvas上。Drawable更注重描述的是某繪製的行爲,而Bitmap則是注重存儲着圖像的像素信息。
Bitmap存儲空間
隨着版本的變化以及存儲空間的變化,Bitmap的存儲空間主要有三個地方
Native Memory
Android2.3如下版本,bitmap像素數據存儲在native內存中,釋放內存需主動調用recycle()方法
Dalvik Heap
Android3.0+版本,在Android2.3版本引入了併發的垃圾回收器後,在3.0之後的版本bitmap的像素數據則存儲在虛擬機堆中,不須要主動調用recycle()來回收內存,gc會主動回收
Ashmem
匿名共享內存空間,說到這個,就會聯想起大名鼎鼎的Fresco圖片庫,它巧妙的利用了這一空間來進行Bitmap對象的存儲,對於Ashmem空間,首先想到的是與App進程空間是隔離且互不影響的,這點在Android4.4如下版本是這樣的,在Android4.4+後版本,Ashmem空間將會包含在App所佔用的內存空間中。看Fresco源碼也能夠看出,對於4.4+版本,對於Bitmap的解碼使用了另外的解碼器。在Android4.4如下版本如何使用Ashmem進行bitmap的存儲呢?經過DecodeOptions:
options.inPurgeable = true;
options.inInputShareable = true;
複製代碼
以及經過MemoryFile可將圖片的字節數據存儲在Ashmem中。
尺寸壓縮本質上就是一個從新採樣的過程,放大圖像稱爲上採樣,縮小圖像稱爲下采樣,Android提供了兩種圖像採樣方法,鄰近採樣和雙線性採樣。
鄰近採樣採用鄰近點插值算法,用一個像素點代替鄰近的像素點,
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = BitmapFactory.decodeFile("/sdcard/test.png", options);
複製代碼
其中options.inSampleSize
的值表明着壓縮後一個像素點代替原來的幾個像素點,好比options.inSampleSize=2
,一個像素點會代替原來的2個像素點,注意這裏的2個像素點僅僅指水平方向或者豎直方向上的。即原來2x2的像素,壓縮後僅使用一個像素點來代替。
網上找了張圖
壓縮前的圖片
壓縮後的圖片
壓縮前紅綠相間的圖片,通過壓縮後,徹底變成了綠色.這時由於鄰近點插值算法直接選擇其中一個像素做爲生成像素,另一個像素直接拋棄,這樣纔會形成圖片變成純綠色的狀況。
考慮到鄰近採樣的方法有些暴力,Android平臺提供了另外一種尺寸壓縮方案
雙線性採樣採用雙線性插值算法,相比鄰近採樣簡單粗暴的選擇一個像素點代替其餘像素點,雙線性採樣參考源像素相應位置周圍2x2個點的值,根據相對位置取對應的權重,通過計算獲得目標圖像。
使用實例
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true);
複製代碼
或者
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);
複製代碼
壓縮效果
壓縮前
壓縮後
能夠看出壓縮後的圖片不會像鄰近採樣那般只有純粹的一種顏色,而是參考了像素源周圍2x2個點的像素,並取其權重獲得目標圖像。
雙線性採樣相比鄰近採樣而言,圖片的保真度會高些,但壓縮的速率不及前者,由於前者不須要計算直接選擇了其中一個像素做爲生成像素。
雙立方/雙三次採樣使用的是雙立方/雙三次插值算法。雙立方/雙三次插值算法參考了源像素某點周圍 4x4 個像素。
雙立方/雙三次插值算法常常用於圖像或者視頻的縮放,它能比雙線性內插值算法保留更好的細節質量。
雙立方/雙三次插值算法在平時的軟件中是很經常使用的一種圖片處理算法,可是這個算法有一個缺點就是計算量會相對比較大,是前三種算法中計算量最大的,軟件 photoshop 中的圖片縮放功能使用的就是這個算法。
Lanczos 採樣和 Lanczos 過濾是 Lanczos 算法的兩種常見應用,它能夠用做低通濾波器或者用於平滑地在採樣之間插入數字信號,Lanczos 採樣通常用來增長數字信號的採樣率,或者間隔採樣來下降採樣率。
鄰近採樣--雙線性採樣--雙立方/雙三次採樣--Lanczos 採樣
Android平臺圖像壓縮方案
QQ音樂團隊分享:Android中的圖片壓縮技術詳解
也談圖片壓縮
爲何圖片反覆壓縮後會廣泛會變綠而不是其餘顏色
Android之優雅地加載大圖片
內存佔用/GPU渲染性能優化手記