一铲敛、概述
今天這篇文章我們來了解一下兩個(gè)類:
Bitmap
BitmapFactory
二界弧、Bitmap
2.1 創(chuàng)建Bitmap
通過Bitmap
的源碼场刑,我們可以看到它內(nèi)部提供了很多.createBitmap(xxx)
的靜態(tài)方法证芭,我們可以通過這些方法來獲得一個(gè)Bitmap
:
上述的方法最終可以分為以下三類:
- 通過一個(gè)已有的
Bitmap
創(chuàng)建 - 創(chuàng)建一個(gè)空的
Bitmap
- 創(chuàng)建一個(gè)新的
Bitmap
朦肘,該Bitmap
每個(gè)像素點(diǎn)的顏色通過一個(gè)colors[]
數(shù)組指定。
下面损趋,我們來看一下這三類方法對(duì)于Bitmap
的生產(chǎn)過程:
第一類
public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,
Matrix m, boolean filter) {
checkXYSign(x, y);
checkWidthHeight(width, height);
//新的bitmap范圍不能大于原始的bitmap
if (x + width > source.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
//如果滿足下面這些條件患久,那么直接返回原始的bitmap
if (!source.isMutable() && x == 0 && y == 0 && width == source.getWidth() &&
height == source.getHeight() && (m == null || m.isIdentity())) {
return source;
}
int neww = width;
int newh = height;
Canvas canvas = new Canvas();
Bitmap bitmap;
Paint paint;
//生成bitmap對(duì)應(yīng)區(qū)域
Rect srcR = new Rect(x, y, x + width, y + height);
//原始bitmap對(duì)應(yīng)區(qū)域
RectF dstR = new RectF(0, 0, width, height);
Config newConfig = Config.ARGB_8888;
//獲得原始bitmap的config
final Config config = source.getConfig();
// GIF files generate null configs, assume ARGB_8888
if (config != null) {
switch (config) {
case RGB_565:
newConfig = Config.RGB_565;
break;
case ALPHA_8:
newConfig = Config.ALPHA_8;
break;
//noinspection deprecation
case ARGB_4444:
case ARGB_8888:
default:
newConfig = Config.ARGB_8888;
break;
}
}
//如果不需要變換胡本,那么創(chuàng)建一個(gè)空的bitmap.
if (m == null || m.isIdentity()) {
bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
//根據(jù)Matrix共啃,對(duì)原始的bitmap進(jìn)行一些變換操作.
final boolean transformed = !m.rectStaysRect();
RectF deviceR = new RectF();
m.mapRect(deviceR, dstR);
neww = Math.round(deviceR.width());
newh = Math.round(deviceR.height());
bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
transformed || source.hasAlpha());
canvas.translate(-deviceR.left, -deviceR.top);
canvas.concat(m);
paint = new Paint();
paint.setFilterBitmap(filter);
if (transformed) {
paint.setAntiAlias(true);
}
}
//返回bitmap的這些屬性和原始bitmap相同
bitmap.mDensity = source.mDensity;
bitmap.setHasAlpha(source.hasAlpha());
bitmap.setPremultiplied(source.mRequestPremultiplied);
//設(shè)置canvas對(duì)應(yīng)的bitmap為返回的bitmap
canvas.setBitmap(bitmap);
//通過canvas把原始的bitmap繪制上去.
canvas.drawBitmap(source, srcR, dstR, paint);
//重新置為空.
canvas.setBitmap(null);
return bitmap;
}
- 方法作用:返回原始的
Bitmap
中一個(gè)不可改變的子集,返回的Bitmap
有可能是原始的Bitmap
(原始的Bitmap
不可改變夫嗓,并且大小和請(qǐng)求的新的Bitmap
大小和原來一樣)桐玻,也有可能是復(fù)制出來的篙挽,它和原始的Bitmap
的density
相同。 - 參數(shù)說明:
-
source
:原始的Bitmap
-
x, y
:在原始的Bitmap
中的起始坐標(biāo)镊靴。 -
width, height
:返回的Bitmap
的寬高铣卡,如果超過了原始Bitmap
的范圍,那么會(huì)拋出異常偏竟。 -
m
:Matrix
類型煮落,表示需要的變換 -
filter
:是否需要優(yōu)化,只有當(dāng)m
不只有平移操作時(shí)才去進(jìn)行踊谋。
第二類
private static Bitmap createBitmap(DisplayMetrics display, int width, int height, Config config, boolean hasAlpha) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
if (display != null) {
bm.mDensity = display.densityDpi;
}
bm.setHasAlpha(hasAlpha);
if (config == Config.ARGB_8888 && !hasAlpha) {
nativeErase(bm.mNativePtr, 0xff000000);
}
return bm;
}
- 方法作用:返回一個(gè)可變的
bitmap
蝉仇,它的density
由傳入的DisplayMetrics
指定。 - 參數(shù)說明:
-
display
:Bitmap
將要被繪制的Display metrics
-
width, height
:bitmap
的寬高 -
config
:配置信息,對(duì)應(yīng)ARGB_8888
那些轿衔。 -
hasAlpha
:如果bitmap
的屬性是ARGB_8888
沉迹,那么這個(gè)標(biāo)志為可以用來把bitmap
標(biāo)志為透明,它會(huì)把bitmap
中的黑色像素轉(zhuǎn)換為透明害驹。
第三類
public static Bitmap createBitmap(DisplayMetrics display, int colors[],
int offset, int stride, int width, int height, Config config) {
checkWidthHeight(width, height);
if (Math.abs(stride) < width) {
throw new IllegalArgumentException("abs(stride) must be >= width");
}
int lastScanline = offset + (height - 1) * stride;
int length = colors.length;
if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
(lastScanline + width > length)) {
throw new ArrayIndexOutOfBoundsException();
}
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
}
Bitmap bm = nativeCreate(colors, offset, stride, width, height,
config.nativeInt, false);
if (display != null) {
bm.mDensity = display.densityDpi;
}
return bm;
}
- 方法作用:返回一個(gè)不可變的
bitmap
對(duì)象鞭呕,它的長(zhǎng)寬由width/height
指定,每個(gè)像素點(diǎn)的顏色通過colos[]
數(shù)組得到宛官,初始的density
來自于DisplayMetrics
葫松。 - 方法參數(shù):
-
display
:Bitmap
將要被繪制的Display metrics
-
colors
:用來初始化像素點(diǎn)的顏色 -
offset
:第一個(gè)像素點(diǎn)的顏色在數(shù)組當(dāng)中跳過的個(gè)數(shù)。 -
stride
:兩行之間需要跳過的顏色個(gè)數(shù)底洗。 -
width/height
:寬高进宝。 -
config
:對(duì)應(yīng)ARGB_8888
那些。
2.2 壓縮bitmap
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
throw new NullPointerException();
}
if (quality < 0 || quality > 100) {
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
}
- 方法作用:把當(dāng)前這個(gè)
bitmap
的壓縮版本寫入到某個(gè)輸出流當(dāng)中枷恕,如果返回true
党晋,那么這個(gè)bitmap
可以被BitmapFactory.decodeStream()
恢復(fù)。需要注意的是徐块,并不是所有的bitmap
都支持所有的格式未玻,因此,通過BitmapFactory
恢復(fù)回來的bitmap
有可能和原來不同胡控。 - 方法參數(shù):
-
CompressFormat
是一個(gè)枚舉類型扳剿,它的值有JPEG/PNG/WEBP
-
quality
對(duì)應(yīng)0-100
-
stream
則是壓縮后結(jié)果的輸出流。
2.3 回收bitmap
public void recycle() {
if (!mRecycled && mNativePtr != 0) {
if (nativeRecycle(mNativePtr)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
}
}
recycle
方法主要做幾件事:
- 釋放和這個(gè)
bitmap
關(guān)聯(lián)的native
對(duì)象 - 清除像素?cái)?shù)據(jù)
mBuffer
的引用昼激,但是這一過程不是同步的庇绽,它只是將引用置為空,等待垃圾回收器將它回收橙困。 - 在調(diào)用這個(gè)方法之后瞧掺,
mRecycled
標(biāo)志位就為true
,之后如果再調(diào)用bitmap
的方法凡傅,那么很有可能發(fā)生異常辟狈。 - 一般情況下,我們不需要手動(dòng)調(diào)用這個(gè)方法夏跷,因?yàn)楫?dāng)這個(gè)
bitmap
不被引用時(shí)哼转,垃圾回收器就會(huì)自動(dòng)回收它所占用的內(nèi)存。
2.4 獲取Bitmap
所占內(nèi)存
-
getAllocationByteCount()
返回存儲(chǔ)這個(gè)bitmap
對(duì)象所需要的內(nèi)存槽华,當(dāng)我們對(duì)bitmap
所占內(nèi)存區(qū)域進(jìn)行復(fù)用的時(shí)候壹蔓,這個(gè)函數(shù)的返回結(jié)果可能要大于getByteCount
的值,否則猫态,它和getByteCount
的值是相同的佣蓉。
這個(gè)值煮纵,在bitmap
整個(gè)生命周期之內(nèi)都不會(huì)改變。
public final int getAllocationByteCount() {
if (mBuffer == null) {
return getByteCount();
}
return mBuffer.length;
}
-
getByteCount
表示存儲(chǔ)bitmap
像素所需要的最小字節(jié)偏螺,自從4.4
之后,這個(gè)就不能用來確定bitmap
占用的內(nèi)存了匆光,需要用getAllocationByteCount
套像。
public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
2.5 獲取縮放后大小
我們可以通過上面這六個(gè)方法獲得縮放后的寬高,它們的原理就是傳入一個(gè)目標(biāo)的
density
终息,然后和當(dāng)前bitmap
的density
進(jìn)行比較夺巩,然后算出一個(gè)縮放的倍數(shù),在和原來的大小相乘周崭。目標(biāo)density
的來源有以下三個(gè):
- 直接傳入
-
Canvas
的density
-
DisplayMetrics
的density
計(jì)算的規(guī)則為:
static public int scaleFromDensity(int size, int sdensity, int tdensity) {
if (sdensity == DENSITY_NONE || tdensity == DENSITY_NONE || sdensity == tdensity) {
return size;
}
// Scale by tdensity / sdensity, rounding up.
return ((size * tdensity) + (sdensity >> 1)) / sdensity;
}
三柳譬、BitmapFactory
BitmapFactory
用來從多種不同的來源獲得Bitmap
:
- 文件、文件描述符
- 資源文件
Resource
-
byte[]
數(shù)組 - 輸入流
InputStream
3.1 BitmapFactory.Options
類
-
Bitmap inBitmap
如果給Options
設(shè)置了這個(gè)Bitmap
续镇,那么在通過這個(gè)Options
解碼的時(shí)候美澳,解碼方法返回的bitmap
會(huì)嘗試復(fù)用這個(gè)Options
中的bitmap
,如果不能復(fù)用摸航,那么解碼方法會(huì)返回null
制跟,并拋出異常,它要求復(fù)用的bitmap
是可變的酱虎。
在4.4
以后雨膨,只要求新申請(qǐng)的bitmap
的getByteCount()
小于等于Options
中的bitmap
的getAllocationByteCount()
就可以。
在4.4
以前读串,格式必須是jpeg/png
聊记,并且要求兩個(gè)bitmap
相同并且inSampleSize
為1
。 -
boolean inJustDecodeBounds
如果設(shè)為true
恢暖,那么解碼方法的返回值null
排监,但是它會(huì)設(shè)置outXXX
的值,這樣調(diào)用者就可以在不用解碼整張圖片的前提下查詢到這個(gè)bitmap
的長(zhǎng)寬杰捂。 -
int inSampleSize
對(duì)原來的圖片進(jìn)行采樣社露,如果inSampleSize
為4
,那么圖片的長(zhǎng)寬會(huì)縮短為原來的1/4
琼娘,這樣就可以減少bitmap
占用的內(nèi)存峭弟。 -
Bitmap.Config inPreferredConfig
圖片解碼的格式要求。 - 縮放相關(guān)的標(biāo)志:
inScaled
脱拼、inDensity
瞒瘸、inTargetDensity
、inScreenDensity
首先熄浓,只有在inScaled
為true
的時(shí)候情臭,縮放的機(jī)制才會(huì)生效省撑,這個(gè)值默認(rèn)是true
的。 -
inDensity
我們先討論一下inDensity
俯在,當(dāng)我們沒有給density
賦值的時(shí)候竟秫,系統(tǒng)會(huì)給我們初始化它:
//如果沒有設(shè)置density
if (opts.inDensity == 0 && value != null) {
final int density = value.density; //這里的TypeValue會(huì)根據(jù)存放文件夾的不同而不同.
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; //如果density為0,那么把density設(shè)置為160.
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density; //否則跷乐,設(shè)置為value中的density.
}
}
-
inTargetDensity
再來看一下inTargetDensity
肥败,它得到的就是屏幕的density
.
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
-
inScreenDensity
最后inScreenDensity
沒有被賦予默認(rèn)值,也就是說它為0
愕提,如果我們期望圖片不要被縮放馒稍,那么就要給它設(shè)置為手機(jī)的density
。
這三者的關(guān)系是:當(dāng)inDensity
不為0
并且inTargetDensity
不為0
浅侨,inDensity
和inScreenDensity
不相等時(shí)纽谒,會(huì)對(duì)圖片進(jìn)行縮放,縮放倍數(shù)為inTargetDensity/inDensity
如输。
這樣說可能比較抽象鼓黔,我們舉一個(gè)實(shí)際的例子,假如我們的手機(jī)的density
是320dpi
的不见,那么inTargetDensity
就等于320
请祖,這時(shí)候我們把某張圖片資源放在了drawable-xxxhpi
下,那么inDensity
的值就為640
脖祈,我們沒有設(shè)置inScreenDensity
肆捕,那么它的默認(rèn)值是0
,這時(shí)候滿足:
inDensity != 0 && inTargetDensity != 0 && inDensity != inScreenDensity
圖片就會(huì)進(jìn)行縮放盖高,縮放的倍數(shù)就為320/640
慎陵,也就是說最終得到的bitmap
的長(zhǎng)寬是原來的一半。
-
outXXX
這個(gè)返回的結(jié)果和inJustDecodeBounds
有關(guān)喻奥,如果inJustDecodeBounds
為true
席纽,那么返回的是沒有經(jīng)過縮放的大小,如果為false
撞蚕,那么就是縮放后的大小润梯。
3.2 獲取bitmap
方法
下面是BitmapFactory
提供的方法:
所有的獲取
bitmap
最終都是調(diào)用了一下四個(gè)方法Native
方法其中之一,可以看到它可以從這些來源讀壬谩:
file
byte[]
InputStream
其中有個(gè)需要注意的是Rect
纺铭,這是一個(gè)傳入的值,在讀取資源完畢后刀疙,它會(huì)寫入讀取資源的padding
舶赔,如果沒有那么為[-1, -1, -1,- 1]
,而如果返回的bitmap
為空谦秧,那么傳入的值不會(huì)改變竟纳。
四撵溃、Bitmap
的轉(zhuǎn)換方法
public class BitmapConvertUtils {
public static Bitmap fromResourceIdAutoScale(Resources resources, int resourceId, BitmapFactory.Options options) {
return BitmapFactory.decodeResource(resources, resourceId, options);
}
public static Bitmap fromResourceIdNotScale(Resources resources, int resourceId, Rect rect, BitmapFactory.Options options) {
InputStream resourceStream = null;
Bitmap bitmap = null;
try {
resourceStream = resources.openRawResource(resourceId);
bitmap = BitmapFactory.decodeStream(resourceStream, rect, options);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (resourceStream != null) {
resourceStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
public static Bitmap fromAssert(Context context, String assertFilePath, Rect rect, BitmapFactory.Options options) {
Bitmap bitmap = null;
InputStream assertStream = null;
AssetManager assetManager = context.getAssets();
try {
assertStream = assetManager.open(assertFilePath);
bitmap = BitmapFactory.decodeStream(assertStream, rect, options);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (assertStream != null) {
assertStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return bitmap;
}
public static Bitmap fromByteArray(byte[] byteArray, int offset, int length, BitmapFactory.Options options) {
return BitmapFactory.decodeByteArray(byteArray, offset, length, options);
}
public static Bitmap fromFile(String filePath, BitmapFactory.Options options) {
return BitmapFactory.decodeFile(filePath, options);
}
public static Bitmap fromDrawable(Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
if (bitmap != null) {
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
return bitmap;
}
return null;
}
public static Bitmap fromView(View view) {
view.clearFocus();
view.setPressed(false);
boolean willNotCache = view.willNotCacheDrawing();
view.setWillNotCacheDrawing(false);
int color = view.getDrawingCacheBackgroundColor();
view.setDrawingCacheBackgroundColor(color);
if (color != 0) {
view.destroyDrawingCache();
}
view.buildDrawingCache();
Bitmap cacheBitmap = view.getDrawingCache();
if (cacheBitmap == null) {
return null;
}
Bitmap bitmap = Bitmap.createBitmap(cacheBitmap);
view.destroyDrawingCache();
view.setWillNotCacheDrawing(willNotCache);
view.setDrawingCacheBackgroundColor(color);
return bitmap;
}
public static Bitmap fromInputStream(InputStream inputStream) {
return BitmapFactory.decodeStream(inputStream);
}
public static byte[] toByteArray(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
byte[] bytes = null;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(format, quality, outputStream);
bytes = outputStream.toByteArray();
try {
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return bytes;
}
public static Drawable toDrawable(Resources resources, Bitmap bitmap) {
return new BitmapDrawable(resources, bitmap);
}
public static void toFile(Bitmap bitmap, Bitmap.CompressFormat format, int quality, String path) {
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(path);
bitmap.compress(format, quality, fileOutputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}