今天這篇文章咱們來了解一下兩個類:canvas
Bitmap
BitmapFactory
Bitmap
Bitmap
經過Bitmap
的源碼,咱們能夠看到它內部提供了不少.createBitmap(xxx)
的靜態方法,咱們能夠經過這些方法來得到一個Bitmap
: 數組
Bitmap
建立Bitmap
Bitmap
,該Bitmap
每一個像素點的顏色經過一個colors[]
數組指定。下面,咱們來看一下這三類方法對於Bitmap
的生產過程:bash
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對應區域
Rect srcR = new Rect(x, y, x + width, y + height);
//原始bitmap對應區域
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;
}
}
//若是不須要變換,那麼建立一個空的bitmap.
if (m == null || m.isIdentity()) {
bitmap = createBitmap(neww, newh, newConfig, source.hasAlpha());
paint = null; // not needed
} else {
//根據Matrix,對原始的bitmap進行一些變換操做.
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);
//設置canvas對應的bitmap爲返回的bitmap
canvas.setBitmap(bitmap);
//經過canvas把原始的bitmap繪製上去.
canvas.drawBitmap(source, srcR, dstR, paint);
//從新置爲空.
canvas.setBitmap(null);
return bitmap;
}
複製代碼
Bitmap
中一個不可改變的子集,返回的Bitmap
有多是原始的Bitmap
(原始的Bitmap
不可改變,而且大小和請求的新的Bitmap
大小和原來同樣),也有多是複製出來的,它和原始的Bitmap
的density
相同。source
:原始的Bitmap
x, y
:在原始的Bitmap
中的起始座標。width, height
:返回的Bitmap
的寬高,若是超過了原始Bitmap
的範圍,那麼會拋出異常。m
:Matrix
類型,表示須要的變換filter
:是否須要優化,只有當m
不僅有平移操做時纔去進行。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;
}
複製代碼
bitmap
,它的density
由傳入的DisplayMetrics
指定。display
:Bitmap
將要被繪製的Display metrics
width, height
:bitmap
的寬高config
:配置信息,對應ARGB_8888
那些。hasAlpha
:若是bitmap
的屬性是ARGB_8888
,那麼這個標誌爲能夠用來把bitmap
標誌爲透明,它會把bitmap
中的黑色像素轉換爲透明。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;
}
複製代碼
bitmap
對象,它的長寬由width/height
指定,每一個像素點的顏色經過colos[]
數組獲得,初始的density
來自於DisplayMetrics
。display
:Bitmap
將要被繪製的Display metrics
colors
:用來初始化像素點的顏色offset
:第一個像素點的顏色在數組當中跳過的個數。stride
:兩行之間須要跳過的顏色個數。width/height
:寬高。config
:對應ARGB_8888
那些。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;
}
複製代碼
bitmap
的壓縮版本寫入到某個輸出流當中,若是返回true
,那麼這個bitmap
能夠被BitmapFactory.decodeStream()
恢復。須要注意的是,並非全部的bitmap
都支持全部的格式,所以,經過BitmapFactory
恢復回來的bitmap
有可能和原來不一樣。CompressFormat
是一個枚舉類型,它的值有JPEG/PNG/WEBP
quality
對應0-100
stream
則是壓縮後結果的輸出流。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
方法主要作幾件事:ide
bitmap
關聯的native
對象mBuffer
的引用,可是這一過程不是同步的,它只是將引用置爲空,等待垃圾回收器將它回收。mRecycled
標誌位就爲true
,以後若是再調用bitmap
的方法,那麼頗有可能發生異常。bitmap
不被引用時,垃圾回收器就會自動回收它所佔用的內存。Bitmap
所佔內存getAllocationByteCount()
返回存儲這個bitmap
對象所須要的內存,當咱們對bitmap
所佔內存區域進行復用的時候,這個函數的返回結果可能要大於getByteCount
的值,不然,它和getByteCount
的值是相同的。 這個值,在bitmap
整個生命週期以內都不會改變。public final int getAllocationByteCount() {
if (mBuffer == null) {
return getByteCount();
}
return mBuffer.length;
}
複製代碼
getByteCount
表示存儲bitmap
像素所須要的最小字節,自從4.4
以後,這個就不能用來肯定bitmap
佔用的內存了,須要用getAllocationByteCount
。public final int getByteCount() {
// int result permits bitmaps up to 46,340 x 46,340
return getRowBytes() * getHeight();
}
複製代碼
density
,而後和當前
bitmap
的
density
進行比較,而後算出一個縮放的倍數,在和原來的大小相乘。目標
density
的來源有如下三個:
Canvas
的density
DisplayMetrics
的density
計算的規則爲:函數
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[]
數組InputStream
BitmapFactory.Options
類Bitmap inBitmap
若是給Options
設置了這個Bitmap
,那麼在經過這個Options
解碼的時候,解碼方法返回的bitmap
會嘗試複用這個Options
中的bitmap
,若是不能複用,那麼解碼方法會返回null
,並拋出異常,它要求複用的bitmap
是可變的。 在4.4
之後,只要求新申請的bitmap
的getByteCount()
小於等於Options
中的bitmap
的getAllocationByteCount()
就能夠。 在4.4
之前,格式必須是jpeg/png
,而且要求兩個bitmap
相同而且inSampleSize
爲1
。boolean inJustDecodeBounds
若是設爲true
,那麼解碼方法的返回值null
,可是它會設置outXXX
的值,這樣調用者就能夠在不用解碼整張圖片的前提下查詢到這個bitmap
的長寬。int inSampleSize
對原來的圖片進行採樣,若是inSampleSize
爲4
,那麼圖片的長寬會縮短爲原來的1/4
,這樣就能夠減小bitmap
佔用的內存。Bitmap.Config inPreferredConfig
圖片解碼的格式要求。inScaled
、inDensity
、inTargetDensity
、inScreenDensity
首先,只有在inScaled
爲true
的時候,縮放的機制纔會生效,這個值默認是true
的。inDensity
咱們先討論一下inDensity
,當咱們沒有給density
賦值的時候,系統會給咱們初始化它://若是沒有設置density
if (opts.inDensity == 0 && value != null) {
final int density = value.density; //這裏的TypeValue會根據存放文件夾的不一樣而不一樣.
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; //若是density爲0,那麼把density設置爲160.
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density; //不然,設置爲value中的density.
}
}
複製代碼
inTargetDensity
再來看一下inTargetDensity
,它獲得的就是屏幕的density
.if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
複製代碼
inScreenDensity
最後inScreenDensity
沒有被賦予默認值,也就是說它爲0
,若是咱們指望圖片不要被縮放,那麼就要給它設置爲手機的density
。這三者的關係是:當inDensity
不爲0
而且inTargetDensity
不爲0
,inDensity
和inScreenDensity
不相等時,會對圖片進行縮放,縮放倍數爲inTargetDensity/inDensity
。ui
這樣說可能比較抽象,咱們舉一個實際的例子,假如咱們的手機的density
是320dpi
的,那麼inTargetDensity
就等於320
,這時候咱們把某張圖片資源放在了drawable-xxxhpi
下,那麼inDensity
的值就爲640
,咱們沒有設置inScreenDensity
,那麼它的默認值是0
,這時候知足:spa
inDensity != 0 && inTargetDensity != 0 && inDensity != inScreenDensity
複製代碼
圖片就會進行縮放,縮放的倍數就爲320/640
,也就是說最終獲得的bitmap
的長寬是原來的一半。code
outXXX
這個返回的結果和inJustDecodeBounds
有關,若是inJustDecodeBounds
爲true
,那麼返回的是沒有通過縮放的大小,若是爲false
,那麼就是縮放後的大小。bitmap
方法下面是BitmapFactory
提供的方法: orm
bitmap
最終都是調用了一下四個方法
Native
方法其中之一,能夠看到它能夠從這些來源讀取:
file
byte[]
InputStream
其中有個須要注意的是Rect
,這是一個傳入的值,在讀取資源完畢後,它會寫入讀取資源的padding
,若是沒有那麼爲[-1, -1, -1,- 1]
,而若是返回的bitmap
爲空,那麼傳入的值不會改變。
Bitmap
的轉換方法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();
}
}
}
}
複製代碼