在介紹Android平臺(tái)的壓縮方案之前,先了解一下Bitmap的幾個(gè)主要概念捏膨。
像素密度
像素密度指的是每英寸像素?cái)?shù)目甘有,在Bitmap里用mDensity/mTargetDensity版保,mDensity默認(rèn)是設(shè)備屏幕的像素密度,mTargetDensity是圖片的目標(biāo)像素密度悉抵,在加載圖片時(shí)就是 drawable 目錄的像素密度茬贵。
色彩模式->色彩模式是數(shù)字世界中表示顏色的一種算法,在Bitmap里用Config來表示沼头。
- ARGB_8888:每個(gè)像素占四個(gè)字節(jié)爷绘,A、R进倍、G土至、B 分量各占8位,是 Android 的默認(rèn)設(shè)置猾昆;
- RGB_565:每個(gè)像素占兩個(gè)字節(jié)陶因,R分量占5位,G分量占6位毡庆,B分量占5位坑赡;
- ARGB_4444:每個(gè)像素占兩個(gè)字節(jié),A么抗、R、G亚铁、B分量各占4位蝇刀,成像效果比較差;
- Alpha_8: 只保存透明度徘溢,共8位吞琐,1字節(jié);
Bitmap的計(jì)算方式
memory=scaledWidth*scaledHeight*每個(gè)像素所占字節(jié)數(shù)
其中
scaledWidth : widthtargetDensity/density+0.5
scaledHeight: heighttargetDensity/density+0.5
-
scaledWidth
表示水平方向的像素值, -
width
表示屏幕寬度, -
targetDensity
表示手機(jī)的像素密度,這個(gè)值一般跟手機(jī)相關(guān), -
density
表示decodingBitmap 的 density,這個(gè)值一般跟圖片放置的目錄有關(guān)(hdpi/xxhdpi)
scaledHeight同理
每個(gè)像素所占字節(jié)數(shù):這個(gè)值跟色彩模式相關(guān)然爆,默認(rèn) ARGB_8888 則是4個(gè)字節(jié)站粟,
在Bitmap種有兩個(gè)獲取內(nèi)存占用大小的方法
- getByteCount():API12 加入,代表存儲(chǔ) Bitmap 的像素需要的最少內(nèi)存曾雕。
- getAllocationByteCount():API19 加入奴烙,代表在內(nèi)存中為 Bitmap 分配的內(nèi)存大小,代替了 getByteCount() 方法。
兩者的區(qū)別:
在不復(fù)用 Bitmap 時(shí)切诀,getByteCount() 和 getAllocationByteCount 返回的結(jié)果是一樣的揩环。在通過復(fù)用 Bitmap 來解碼圖片時(shí),那么 getByteCount() 表示新解碼圖片占用內(nèi)存的大小幅虑,getAllocationByteCount() 表示被復(fù)用 Bitmap真實(shí)占用的內(nèi)存大蟹峄(即 mBuffer 的長(zhǎng)度)。
圖片壓縮方式
質(zhì)量壓縮
質(zhì)量壓縮的關(guān)鍵在于Bitmap.compress()函數(shù)倒庵,該函數(shù)不會(huì)改變圖像的大小褒墨,但是可以降低圖像的質(zhì)量,從而降低存儲(chǔ)大小擎宝,進(jìn)而達(dá)到壓縮的目的貌亭。
這里提到的圖像的質(zhì)量主要指的是圖片的色彩空間
一般圖像的色彩空間為RGB,主要通過RGB三原色通道來描述圖片,其中又有ARGB格式,比起RGB多了一個(gè)透明度的通道。
Android下的質(zhì)量壓縮主要通過下面這個(gè)函數(shù)來實(shí)現(xiàn)的认臊。
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
三個(gè)參數(shù)
- CompressFormat format:壓縮格式,它有JPEG圃庭、PNG、WEBP三種選擇失晴,JPEG是有損壓縮剧腻,PNG是無損壓縮,WEBP是Google推出的圖像格式.
- int quality:0~100可選涂屁,數(shù)值越大书在,質(zhì)量越高,圖像越大拆又。
- OutputStream stream:壓縮后圖像的輸出流儒旬。
其中PNG是無損格式的,壓縮效果不太理想,而WEBP會(huì)存在兼容性的問題。出于兼容性和效果來看帖族,一般會(huì)選擇JPEG作為壓碎格式栈源。
實(shí)例代碼
// 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());
我們?cè)賮砜纯?code>quality參數(shù)被設(shè)置為50前后,兩張圖片的對(duì)比.
壓縮前的圖片
壓縮后的圖片
從上述兩圖可以明顯圖片質(zhì)量的差別,另外再通過log打印查看會(huì)壓縮前后圖片的所占用的大小是一樣的。
即
bitmap.size = compress.size
Q:這里可能有人就會(huì)有疑惑,為什么壓縮過后,兩張圖片的大小還會(huì)是一樣的呢竖般?
A:因?yàn)閳D片在內(nèi)存中的存儲(chǔ)方式和文件中的存儲(chǔ)方式是不一樣的甚垦。圖片壓縮只會(huì)影響文件的大小,在這個(gè)例子中,壓縮過后存到磁盤的文件大小會(huì)比壓縮之前的文件大小減小很多。
內(nèi)存中所占的大小沒有變化是因?yàn)閎itmap沒有變化的原因涣雕。
文章最開始提到Bitmap的計(jì)算方式
memory=scaledWidth*scaledHeight*每個(gè)像素所占字節(jié)數(shù)
因?yàn)槭菈嚎s的質(zhì)量,所有寬高都不變,而每個(gè)像素所占的字節(jié)數(shù)跟色彩空間有關(guān),默認(rèn)是ARGB_8888
.寬高不變,色彩空間不重新設(shè)置,那么bitmap所占的大小就不會(huì)發(fā)生改變艰亮。
說道這里可能又會(huì)有個(gè)新疑問
Q:bitmap占用的大小不變,那為什么圖片質(zhì)量下降了呢?這是因?yàn)閳D片被壓縮過了啊!
A:首先要知道JPEG格式是有損壓縮的,JPEG格式的圖片是不支持透明色彩的,這也是JPEG的大小會(huì)比PNG小很大,圖片質(zhì)量會(huì)比PNG差的原因挣郭。
在經(jīng)過了bitmap.compress()
這個(gè)流程時(shí),JPEG會(huì)舍去透明屬性.這樣存放到磁盤時(shí)的文件大小就減小了.然后這個(gè)時(shí)候再通過BitmapFactory.decodeByteArray()
把圖片加載回來時(shí),加載的是舍去了透明通道的圖片,按理說應(yīng)該采用 RGB_565
或者RGB_888
這樣的色彩空間加載,但是你沒有另外設(shè)置這個(gè)參數(shù)的話,加載的色彩格式會(huì)是默認(rèn)ARGB_8888
.圖片都沒有透明的色彩空間了,你再給它分配內(nèi)存就只是浪費(fèi)內(nèi)存而已迄埃。
這也是為什么壓縮前后,bitmap所占的大小相同,圖片質(zhì)量卻有所差距的原因。
補(bǔ)充一個(gè)有趣的事件,在早期的Android平臺(tái)下,對(duì)一張圖片進(jìn)行多次質(zhì)量壓縮,會(huì)得到一張變綠的圖片兑障。詳情鏈接
補(bǔ)充一些Android下各格式圖片的存儲(chǔ)方式
WebP
Webp圖片格式是Google推出的一個(gè)支持alpha通道的有損壓縮格式侄非,據(jù)Google官方表明蕉汪,同質(zhì)量情況下Webp圖像要比JPEG、PNG圖像小25%~45%左右彩库,在支持上Android4.0+版本提供原生支持肤无,使用libwebp庫(kù)進(jìn)行編解碼。
GIF
GIF圖像最廣泛的應(yīng)用是用于顯示動(dòng)畫圖像骇钦,它具備文件小且支持alpha通道的優(yōu)點(diǎn)宛渐,不過它是由8位進(jìn)行表示每個(gè)像素的色彩,僅支持256色眯搭,所以在對(duì)色彩要求比較高的場(chǎng)合不太適合窥翩。
Stream
圖片的存儲(chǔ)形式從File轉(zhuǎn)到內(nèi)存中時(shí),圖片內(nèi)容以字節(jié)方式存儲(chǔ)在Stream中鳞仙,此時(shí)所占的內(nèi)存大小為File文件大小寇蚊。
Bitmap
在Android中,任何圖片資源的顯示對(duì)象都是通過bitmap來顯示的棍好,除了xml資源則是通過Canvas來繪制的仗岸,所以,對(duì)于某些純色或者規(guī)則類的圖像借笙,可以通過xml進(jìn)行描述或Canvas來繪制扒怖,這樣所占用的內(nèi)存比通過bitmap來顯示將少幾個(gè)等級(jí)。
Bitmap與Drawable的聯(lián)系
關(guān)于Bitmap和Drawable的關(guān)系业稼,可以看官方的解釋盗痒,Drawable是一個(gè)抽象的概念,來描述某些具備可繪制的的對(duì)象低散,它是一個(gè)抽象類俯邓,而Bitmap是一個(gè)最簡(jiǎn)單的Drawable實(shí)體對(duì)象,Bitmap并不繼承于Drawable熔号,它們之間建立關(guān)聯(lián)最終是通過BitmapDrawable對(duì)象稽鞭,該對(duì)象會(huì)把具體的Bitmap實(shí)例對(duì)象渲染到Canvas上。Drawable更注重描述的是某繪制的行為跨嘉,而Bitmap則是注重存儲(chǔ)著圖像的像素信息川慌。
Bitmap存儲(chǔ)空間
隨著版本的變化以及存儲(chǔ)空間的變化,Bitmap的存儲(chǔ)空間主要有三個(gè)地方
Native Memory
Android2.3以下版本祠乃,bitmap像素?cái)?shù)據(jù)存儲(chǔ)在native內(nèi)存中,釋放內(nèi)存需主動(dòng)調(diào)用recycle()方法
Dalvik Heap
Android3.0+版本兑燥,在Android2.3版本引入了并發(fā)的垃圾回收器后亮瓷,在3.0以后的版本bitmap的像素?cái)?shù)據(jù)則存儲(chǔ)在虛擬機(jī)堆中,不需要主動(dòng)調(diào)用recycle()來回收內(nèi)存降瞳,gc會(huì)主動(dòng)回收
Ashmem
匿名共享內(nèi)存空間嘱支,說到這個(gè)蚓胸,就會(huì)聯(lián)想起大名鼎鼎的Fresco圖片庫(kù),它巧妙的利用了這一空間來進(jìn)行Bitmap對(duì)象的存儲(chǔ)除师,對(duì)于Ashmem空間沛膳,首先想到的是與App進(jìn)程空間是隔離且互不影響的,這點(diǎn)在Android4.4以下版本是這樣的汛聚,在Android4.4+后版本锹安,Ashmem空間將會(huì)包含在App所占用的內(nèi)存空間中∫幸ǎ看Fresco源碼也可以看出叹哭,對(duì)于4.4+版本,對(duì)于Bitmap的解碼使用了另外的解碼器痕貌。在Android4.4以下版本如何使用Ashmem進(jìn)行bitmap的存儲(chǔ)呢风罩?通過DecodeOptions:
options.inPurgeable = true;
options.inInputShareable = true;
以及通過MemoryFile可將圖片的字節(jié)數(shù)據(jù)存儲(chǔ)在Ashmem中。
尺寸壓縮
尺寸壓縮本質(zhì)上就是一個(gè)重新采樣的過程舵稠,放大圖像稱為上采樣超升,縮小圖像稱為下采樣,Android提供了兩種圖像采樣方法哺徊,鄰近采樣和雙線性采樣室琢。
鄰近采樣
鄰近采樣采用鄰近點(diǎn)插值算法,用一個(gè)像素點(diǎn)代替鄰近的像素點(diǎn)唉工,
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
的值代表著壓縮后一個(gè)像素點(diǎn)代替原來的幾個(gè)像素點(diǎn),比如options.inSampleSize=2
,一個(gè)像素點(diǎn)會(huì)代替原來的2個(gè)像素點(diǎn),注意這里的2個(gè)像素點(diǎn)僅僅指水平方向或者豎直方向上的研乒。即原來2x2的像素,壓縮后僅使用一個(gè)像素點(diǎn)來代替。
網(wǎng)上找了張圖
壓縮前的圖片
壓縮后的圖片
壓縮前紅綠相間的圖片,經(jīng)過壓縮后,完全變成了綠色.這時(shí)因?yàn)?strong>鄰近點(diǎn)插值算法直接選擇其中一個(gè)像素作為生成像素,另外一個(gè)像素直接拋棄,這樣才會(huì)造成圖片變成純綠色的情況淋硝。
考慮到鄰近采樣的方法有些暴力,Android平臺(tái)提供了另一種尺寸壓縮方案
雙線性采樣
雙線性采樣采用雙線性插值算法雹熬,相比鄰近采樣簡(jiǎn)單粗暴的選擇一個(gè)像素點(diǎn)代替其他像素點(diǎn),雙線性采樣參考源像素相應(yīng)位置周圍2x2個(gè)點(diǎn)的值谣膳,根據(jù)相對(duì)位置取對(duì)應(yīng)的權(quán)重竿报,經(jīng)過計(jì)算得到目標(biāo)圖像。
使用實(shí)例
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);
壓縮效果
壓縮前
壓縮后
可以看出壓縮后的圖片不會(huì)像鄰近采樣那般只有純粹的一種顏色,而是參考了像素源周圍2x2個(gè)點(diǎn)的像素继谚,并取其權(quán)重得到目標(biāo)圖像烈菌。
雙線性采樣相比鄰近采樣而言,圖片的保真度會(huì)高些,但壓縮的速率不及前者,因?yàn)榍罢卟恍枰?jì)算直接選擇了其中一個(gè)像素作為生成像素。
雙立方/雙三次采樣 (Android原生不支持)
雙立方/雙三次采樣使用的是雙立方/雙三次插值算法花履。雙立方/雙三次插值算法參考了源像素某點(diǎn)周圍 4x4 個(gè)像素芽世。
雙立方/雙三次插值算法經(jīng)常用于圖像或者視頻的縮放,它能比雙線性內(nèi)插值算法保留更好的細(xì)節(jié)質(zhì)量诡壁。
雙立方/雙三次插值算法在平時(shí)的軟件中是很常用的一種圖片處理算法济瓢,但是這個(gè)算法有一個(gè)缺點(diǎn)就是計(jì)算量會(huì)相對(duì)比較大,是前三種算法中計(jì)算量最大的妹卿,軟件 photoshop 中的圖片縮放功能使用的就是這個(gè)算法旺矾。
Lanczos 采樣 (原生不支持)###
Lanczos 采樣和 Lanczos 過濾是 Lanczos 算法的兩種常見應(yīng)用蔑鹦,它可以用作低通濾波器或者用于平滑地在采樣之間插入數(shù)字信號(hào),Lanczos 采樣一般用來增加數(shù)字信號(hào)的采樣率箕宙,或者間隔采樣來降低采樣率嚎朽。
采樣效果 從低到高依次
鄰近采樣--雙線性采樣--雙立方/雙三次采樣--Lanczos 采樣
Android平臺(tái)圖像壓縮方案
QQ音樂團(tuán)隊(duì)分享:Android中的圖片壓縮技術(shù)詳解
也談圖片壓縮
為什么圖片反復(fù)壓縮后會(huì)普遍會(huì)變綠而不是其他顏色
Android之優(yōu)雅地加載大圖片
內(nèi)存占用/GPU渲染性能優(yōu)化手記