學(xué)習(xí)資料:
android 開發(fā)藝術(shù)探索
- Bitmap api
1.關(guān)于 Bitmap
在Android
中Bitamp
指的就是一張圖片,一般是png
和jpeg
格式。
Bitmap
類中有一個(gè)enum
類型的Config
,其中有4個(gè)值
ALPHA_8
8位位圖;1 個(gè)字節(jié),只有透明度,沒有顏色值RGB_565
16位位圖;2 個(gè)字節(jié),r = 5,g = 6,b = 5,一個(gè)像素點(diǎn) 5+6+5 = 16ARGB_4444
16位位圖;2 個(gè)字節(jié),a = 4,r = 4,g = 4,b = 4,一個(gè)像素點(diǎn) 4+4+4+4 = 16ARGB_8888
32 位位圖; 4個(gè)字節(jié),a = 8,r = 8,g = 8, b = 8,一個(gè)像素點(diǎn) 8 + 8 + 8 + 8 = 32
每8位一個(gè)字節(jié)
以上主要摘自:關(guān)于ARGB_8888、ALPHA_8、ARGB_4444、RGB_565的理解
并不理解a,r,g,b
對像素的影響,主要了解一下,不同的類型格式,占用內(nèi)存情況
一張 1024 * 1024 像素,采用ARGB8888
格式,一個(gè)像素32位,每個(gè)像素就是4字節(jié),占有內(nèi)存就是4M
若采用RGB565
,一個(gè)像素16位,每個(gè)像素就是2字節(jié),占有內(nèi)存就是2M
Glide
加載圖片默認(rèn)格式RGB565
,Picasso
為ARGB8888
,默認(rèn)情況下,Glide
占用內(nèi)存會比Picasso
低,色彩不如Picasso
鮮艷,自然清晰度就低
BitmaFactory
Creates Bitmap objects from various sources, including files, streams, and byte-arrays.
通過BitmapFactory
從文件系統(tǒng),資源,輸入流,字節(jié)數(shù)組中加載得到一個(gè)Bitmap
對象。
decodeByteArray()
decodeFile()
decodeResource()
decodeStream()
decodeFileDescriptor()
decodeResourceStream()
BitmapFactory
所有public method
都是靜態(tài)方法。一共也就6個(gè)方法,后兩個(gè)用的幾率不如前4個(gè)高 :)
2.Bitmap 的高效加載
核心思想: 利用BitmapFactory.Options
來加載實(shí)際所需的尺寸
2.1 BitmapFactory.Options
這個(gè)類中只有一個(gè)方法requestCancelDecode()
,剩下全是一些常量值
BitmapFactory.Options
縮放圖片主要用到inSample
采樣率
inSample = 1
,采樣后圖片的寬高為原始寬高
inSample > 1
,例如2,寬高均為原圖的寬高的1/2
一個(gè)采用ARGB8888
的1024 * 1024
的圖片
inSample = 1
,占用內(nèi)存就 1024 * 1024 * 4 = 4M
inSample = 2
,占用內(nèi)存就 512 * 512 * 4 = 1M
縮小規(guī)律就是:1 /(inSample ^ 2)
inSample
的值最小為1,低于1時(shí)無效的。inSample
的值最好為2,4,8,16,2的指數(shù)。在某些時(shí)候,系統(tǒng)會向下取整,例如為3時(shí),系統(tǒng)會用2來代替。2 的指數(shù),可以一定程度上避免圖片拉伸變形。
2.2 獲取采樣率的流程
以讀取資源文件為例:
- 創(chuàng)建
BitmapFactory.Options
對象options
- 將
options
的inJustDecodeBounds
參數(shù)設(shè)為true
,然后使用BitmapFactory.decodeResource(res,resId,options)
加載圖片 - 利用
options
取出圖片的原始寬高信息,outWidth,outHeight
- 根據(jù)采樣率的規(guī)則并結(jié)合實(shí)際需要顯示的寬高計(jì)算出
inSample
- 將
options
的inJustDecodeBounds
參數(shù)設(shè)為false
,并再次使用BitmapFactory.decodeResource(res,resId,options)
返回采樣后的Bitmap
inJustDecodeBounds
設(shè)為true
,BitmapFactory
只會解析圖片的原始信息,并不會真正的加載圖片
BitmapFactory
讀取圖片的寬高的信息受圖片所在drawable
文件夾和設(shè)備屏幕本身的像素密度影響。
2.3 壓縮圖片簡單實(shí)踐
直接百度了一張imac
的5k分辨率5120 * 2880
大小為5.97M
的壁紙,直接加載我的手機(jī)百分百會出現(xiàn)oom
java
代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
final ImageView iv = (ImageView) findViewById(R.id.iv_main);
iv.post(new Runnable() {
@Override
public void run() {
int width = iv.getWidth();
int height = iv.getHeight();
iv.setImageBitmap(decodeBitmap(getResources(),R.drawable.test,width,height));
}
});
}
/**
* 對圖片進(jìn)行壓縮
*
* @param res
* @param resId
* @param targetWidth
* @param targetHeight
* @return
*/
private Bitmap decodeBitmap(Resources res , int resId, int targetWidth, int targetHeight){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
34行 options.inPreferredConfig = Bitmap.Config.RGB_565;//將Config設(shè)為RGB565
BitmapFactory.decodeResource(res,resId,options);
options.inSampleSize = calculateInSample(options,targetWidth,targetWidth);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res,resId,options);
}
/**
* 計(jì)算inSample
*
* @param options
* @param targetWidth
* @param targetHeight
* @return
*/
private int calculateInSample(BitmapFactory.Options options, int targetWidth, int targetHeight) {
final int rawWidth = options.outWidth;
final int rawHeight = options.outHeight;
int inSample = 1;
54行 if (rawWidth > targetWidth || rawHeight > targetHeight){
final int halfWidth = rawWidth / 2;//為了避免過分壓縮 例如 圖片大小為 250 * 250 view 200 * 200
final int halfHeight = rawHeight / 2;
57行 while((halfWidth / intSample) >= targetWidth && (halfHeight / intSample) >= targetHeight){
inSample *= 2;
}
}
return inSample;
}
}
代碼就是按照流程走的。 只是加入了34行Bitmap
色彩格式的修改
34行,通過options
把Bitmap
的格式設(shè)為RGB565
。設(shè)置成RGB565
后,占用內(nèi)存會少一半,也會減少OOM
。個(gè)人感覺,除非是專門的圖像處理app,大部分時(shí)候都可以用RGB565
代替ARGB8888
,犧牲圖像的清晰度,換來一半的占用內(nèi)存,個(gè)人感覺還是比較劃算的。并且,清晰度的差別,不同時(shí)放在一起比較是不會有很大直觀差別的。
Bitmap
中有一個(gè)setConfig()
方法,源碼中調(diào)用了reconfigure(int width, int height, Config config)
,這個(gè)方法是創(chuàng)建一個(gè)新的bitmap
用的。看了看不是很理解。這個(gè)方法不能用于目前已經(jīng)存在的Bitmap
。修改config
還是利用Options
類
如果已經(jīng)得到了Bitmap
,想要修改Bitmap
的Config
值,可以使用3.1Bitmap.cropress()
和3.2Bitmap.copy()
方法
在calculateInSample()
方法中,final int halfWidth = rawWidth / 2
這行代碼的目的在于防止過度壓縮。因?yàn)?4行已經(jīng)做了判斷,到了57行條件必然滿足,當(dāng)要顯示的目標(biāo)大小和圖像的實(shí)際大小比較接近時(shí),會產(chǎn)生過度沒必要的壓縮。
例如,ImageView
的大小為200 * 200
,而圖像的大小為為250 * 250
,如果不進(jìn)行除以2,到了57行,條件成立,此時(shí)inSample
的值會再次乘以2,根據(jù)縮小規(guī)律縮小 = inSample ^ 2
,就會又次縮小4倍。
這只是我的分析,代碼是從
Android開發(fā)藝術(shù)探索
學(xué)到的,若分析的不對,請指出
這時(shí)已經(jīng)可以大大減少oom
的發(fā)生,若還發(fā)生,可以把57行的&&
改為||
,這樣改后,final int halfWidth = rawWidth / 2
的作用就會受到影響。可能會出現(xiàn)過度壓縮
其他從文件,流中讀取也差不太多,流程沒啥變化,細(xì)節(jié)不同
3.Bitmap中的方法
主要是查看api文檔,想了解下都有哪些方法
3.1compress方法
compress(Bitmap.CompressFormat format, int quality, OutputStream stream)
Write a compressed version of the bitmap to the specified outputstream.<p>
If this returns true, the bitmap can be reconstructed by passing a corresponding inputstream to BitmapFactory.decodeStream(). Note: not all Formats support all bitmap configs directly, so it is possible that the returned bitmap from BitmapFactory could be in a different bitdepth, and/or may have lost per-pixel alpha (e.g. JPEG only supports opaque pixels).<p>
@param format The format of the compressed image
@param quality Hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some formats, like PNG which is lossless, will ignore the quality setting
@param stream The outputstream to write the compressed data.
@return true if successfully compressed to the specified stream.
將bitmap
數(shù)據(jù)質(zhì)量壓縮并轉(zhuǎn)換成流,若format
參數(shù)設(shè)置為了png
格式,quality
設(shè)置無效
-
format
圖片的格式,支持3種JPEG,PNG,WEBP
-
quality
壓縮質(zhì)量壓縮率,0-100,0表示壓縮程度最大,100為原質(zhì)量,但png
無效 -
stream
輸出流 - 返回值,
boolean
簡單使用:
private void initView() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.cc);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1024 * 8);
bitmap.compress(Bitmap.CompressFormat.PNG,100,outputStream);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
bitmap =BitmapFactory.decodeByteArray(outputStream.toByteArray(),0,outputStream.size(),options);
Log.e(TAG,"++"+outputStream.size()+","+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
}
在變換成輸出流的過程中,把Bitmap
的Config
變?yōu)榱?code>RGB565,這里遇到個(gè)情況,mipmap
文件夾下的圖片,這種方法并不能改變其Config
,一直都是默認(rèn)ARGB8888
3.2 copy方法
copy(Bitmap.Config config, boolean isMutable)
Tries to make a new bitmap based on the dimensions of this bitmap,setting the new bitmap's config to the one specified, and then copying this bitmap's pixels into the new bitmap. If the conversion is not supported, or the allocator fails, then this returns NULL. The returned bitmap initially has the same density as the original.<p>
@param config The desired config for the resulting bitmap
@param isMutable True if the resulting bitmap should be mutable (i.e.its pixels can be modified)
@return the new bitmap, or null if the copy could not be made.
拷貝一個(gè)Bitmap
的像素到一個(gè)新的指定信息配置的Bitmap
-
config
配置信息 -
isMutable
是否支持可改變可寫入 - 返回值,
bitmap
,成功返回一個(gè)新的bitmap
,失敗就null
簡單使用:
private void initView() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.m);
bitmap = bitmap.copy(Bitmap.Config.RGB_565,true);
Log.e(TAG,"++"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
}
方法中isMutable
這個(gè)參數(shù)暫時(shí)不了解具體作用和使用場景
3.3 createBitmap方法
這個(gè)方法一共有9個(gè)重載方法
createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter)
-
source
資源bitmap
-
x
資源bitmap
的第一個(gè)像素的x
坐標(biāo) -
y
資源bitmap
的第一個(gè)像素的y
坐標(biāo) -
m
矩陣 -
filter
是否過濾資源bitmap
- 返回值 一個(gè)不可變的
btimap
由資源bitmap
根據(jù)坐標(biāo)系截取創(chuàng)建一個(gè)新的bitmap
createBitmap(Bitmap src)
createBitmap(Bitmap source, int x, int y, int width, int height)
1->2,2->本方法
本方法簡單使用:
private void initView() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.m);
Matrix matrix = new Matrix();
Bitmap b = bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
}
需要注意:
x
+ width
<= 資源bitmap.getWidth()
y
+ height
<= 資源bitmap.getHeight()
createBitmap(int width, int height, Config config)
createBitmap(DisplayMetrics display, int width,int height, Config config)
private void initView() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.m);
Bitmap b = bitmap.createBitmap(new DisplayMetrics(),10,10, Bitmap.Config.RGB_565);
Bitmap b2 = bitmap.createBitmap(10,10, Bitmap.Config.RGB_565);
Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
Log.e(TAG,"==b2--->"+b2.getConfig().name()+","+b2.getByteCount()+","+b2.getHeight()+","+b2.getWidth());
}
這兩個(gè)方法最終都調(diào)用了一個(gè)沒有對外公開的private
方法。返回值是一個(gè)可變的bitmap
createBitmap(DisplayMetrics display, int colors[],int offset, int stride, int width, int height, Config config)
-
display
DisplayMetrics
對象,指定初始密度 -
colors
初始化顏色的數(shù)組,數(shù)組的長度至少大于width*height
-
offset
偏移量 -
stride
Number of colors in the array between rows (must be >=width or <= -width)
并不理解作用 -
width
bitmap
的寬 -
height
bitmap
的高 -
config
格式 - 返回值 一個(gè)不可變的
bitmap
createBitmap(int colors[], int width, int height, Config config)
createBitmap(DisplayMetrics display, int colors[], int width, int height, Config config)
createBitmap(int colors[], int offset, int stride, int width, int height, Config config)
1,2,3調(diào)用了本方法
本方法簡單使用:
private void initView() {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.m);
Bitmap b = bitmap.createBitmap(new DisplayMetrics(),new int[]{10,20,30,40,50},0,1,1,1, Bitmap.Config.RGB_565);
Log.e(TAG,"==bitmap-->"+bitmap.getConfig().name()+","+bitmap.getByteCount()+","+bitmap.getHeight()+","+bitmap.getWidth());
Log.e(TAG,"==b--->"+b.getConfig().name()+","+b.getByteCount()+","+b.getHeight()+","+b.getWidth());
}
這個(gè)方法并沒有使用過。對參數(shù)要求比較多,使用時(shí)在源碼中看一下參數(shù)要求。這個(gè)方法目前不清楚使用場景,只能遇到時(shí),再次學(xué)習(xí)
3.4其他方法
方法 | 作用 |
---|---|
recycle() |
釋放bitmap 所占的內(nèi)存 |
isRecycled() |
判斷是否回收內(nèi)存 |
getWidth() |
得到寬 |
getHeight |
得到高 |
isMutable() |
是否可以改變 |
sameAs(Bitmap other) |
判斷兩個(gè)bitmap 大小,格式,像素信息是否相同 |
其他的用到再學(xué)習(xí)了。
4.BitmapFactory.Options類
屬性 | 作用 |
---|---|
boolean inJustDecodeBounds |
是否只掃描輪廓 |
int inSample |
采樣率 |
Bitmap.Config inPreferredConfig |
格式,色彩模式 |
int outWidth |
bitmap 的寬 |
int outHeight |
bitmap 的高 |
boolean inDither |
防抖動,默認(rèn)false
|
int inDensity |
像素密度 |
boolean inScaled |
是否可以縮放,默認(rèn)true
|
boolean inMutable |
是否可變,設(shè)為ture ,decode 轉(zhuǎn)換方法返回的結(jié)果全部可改變 |
其他更不常見的用到再學(xué)習(xí)
5 最后
bitmap
基礎(chǔ)知識大概就了解這些,深入的知識就隨著工作再學(xué)習(xí)。
明天星期天,不學(xué)習(xí)了。打算去圖書館看妹紙去 :)
周末愉快。共勉 :)