Android 開發中頻繁需要在不同圖片表示之間轉換,本文整理四大核心轉換路徑,附帶完整程式碼與效能建議。
| 來源 | 目標 | 關鍵 API | 典型用途 |
|---|---|---|---|
| Drawable | Bitmap | Canvas, Bitmap.createBitmap() | 自定義 View 繪製、圖片處理 |
| Resources | Bitmap | BitmapFactory.decodeResource() | 載入 App 內嵌資源 |
| Bitmap | byte[] | Bitmap.compress(), ByteArrayOutputStream | 網路傳輸、資料庫存儲、Intent 傳遞 |
| byte[] | Bitmap | BitmapFactory.decodeByteArray() | 網路接收、資料庫讀取、檔案載入 |
public static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap(); // 已是 BitmapDrawable 直接取得
}
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
if (width <= 0 || height <= 0) return null; // 無固有尺寸(如 GradientDrawable)
Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE
? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(width, height, config);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
return bitmap;
}
注意:getIntrinsicWidth/Height() 可能回傳 -1(無固有尺寸),需處理回退邏輯。
public static Bitmap decodeResource(Resources res, @DrawableRes int resId,
int reqWidth, int reqHeight) {
// 先解碼邊界資訊
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 計算縮放比例
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 實際解碼
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(res, resId, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
if (reqWidth == 0 || reqHeight == 0) return 1;
int inSampleSize = 1;
while (options.outWidth / inSampleSize > reqWidth
|| options.outHeight / inSampleSize > reqHeight) {
inSampleSize <<= 1;
}
return inSampleSize;
}
最佳實踐:必須使用 inSampleSize 避免載入超大圖片導致 OOM,尤其在 RecyclerView/ListView 列表圖片載入。
public static byte[] bitmapToBytes(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(format, quality, baos);
return baos.toByteArray();
}
// 使用範例
byte[] pngBytes = bitmapToBytes(bitmap, Bitmap.CompressFormat.PNG, 100); // 無損
byte[] jpgBytes = bitmapToBytes(bitmap, Bitmap.CompressFormat.JPEG, 85); // 有損,品質 85%
格式選擇:PNG 無損適合圖示、透明背景;JPEG 有損適合照片,品質 75-90% 平衡體積與畫質。
public static Bitmap bytesToBitmap(byte[] data) {
if (data == null || data.length == 0) return null;
return BitmapFactory.decodeByteArray(data, 0, data.length);
}
// 含尺寸限制版本(防 OOM)
public static Bitmap bytesToBitmap(byte[] data, int maxWidth, int maxHeight) {
if (data == null || data.length == 0) return null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}
實際專案強烈建議使用成熟庫處理圖片載入、快取、轉換:
// Glide 範例
Glide.with(context)
.load(url)
.override(300, 300)
.apply(new RequestOptions().format(DecodeFormat.PREFER_ARGB_8888))
.into(imageView);
// 自定義轉換
public class CircleCropTransformation extends BitmapTransformation {
@Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
return circleCrop(pool, toTransform);
}
}
Bitmap.recycle() (API < 11) 手動回收原生記憶體inBitmap 重用記憶體(需相容尺寸與配置)LruCache 記憶體快取 + DiskLruCache 磁碟快取ExecutorService 或 Kotlin 協程