在移動應用中,圖像編輯功能已成為標配 —— 社交 APP 需要裁剪頭像,電商 APP 需要給商品圖加水印,工具 APP 需要提供濾鏡效果。看似簡單的 “裁剪”“縮放” 背后,實則涉及 Bitmap 像素操作、內存管理、性能優化等核心技術。很多開發者在實現時會遇到 “編輯后圖片模糊”“操作時卡頓”“大圖片編輯 OOM” 等問題,根源在于對圖像編輯的底層邏輯理解不足。
本文將從實際開發需求出發,系統講解 Android 圖像編輯的核心技術:從基礎的裁剪、縮放、旋轉,到進階的濾鏡、水印、圓角處理,每個功能都提供完整實現代碼和優化方案,幫你避開常見陷阱,實現高效、高質量的圖像編輯功能。
一、圖像編輯的基礎:Bitmap 像素操作原理
所有圖像編輯功能的底層都是對 Bitmap 像素的操作。Bitmap 是 Android 中唯一能直接操作像素的圖像格式,其本質是 “內存中的像素矩陣”—— 每個像素的顏色(ARGB 值)決定了圖像的顯示效果。
1.1 Bitmap 的像素存儲與顏色表示
- 像素存儲:Bitmap 的像素按行存儲在內存中,例如 100x100 的 Bitmap 有 10000 個像素,每個像素占用 2-4 字節(取決于像素格式);
- 顏色表示:每個像素用 ARGB 值表示(Alpha 透明度、Red 紅色、Green 綠色、Blue 藍色),例如0xFFFF0000表示不透明的紅色(A=0xFF,R=0xFF,G=0x00,B=0x00);
- 像素格式:
- ARGB_8888:每個像素 4 字節(最高質量,支持透明),1080x1920 的 Bitmap 約占用 8MB 內存;
- RGB_565:每個像素 2 字節(無透明通道),內存占用僅為 ARGB_8888 的一半,適合非透明圖像。
關鍵結論:圖像編輯時,應根據需求選擇像素格式(非透明圖像用 RGB_565 節省內存),并始終控制 Bitmap 的大小(避免加載超過編輯所需的大圖片)。
1.2 圖像編輯的核心步驟
無論何種編輯操作,基本流程都可概括為:
1.加載原圖:將圖像(File/Uri/Drawable)轉為可編輯的 Bitmap(需控制大小,避免 OOM);
2.創建編輯后的 Bitmap:根據編輯需求創建新的 Bitmap(如裁剪后的尺寸、旋轉后的尺寸);
3.像素操作:通過 Canvas 繪制或直接修改像素數組,實現編輯效果;
4.保存結果:將編輯后的 Bitmap 轉為目標格式(如保存為 File、顯示為 Drawable);
5.釋放資源:回收原圖 Bitmap,避免內存泄漏。
這個流程的核心是 “盡量減少中間 Bitmap 的創建” 和 “及時釋放不再使用的 Bitmap”,這是避免編輯時卡頓和 OOM 的關鍵。
二、基礎編輯功能:裁剪、縮放、旋轉
基礎編輯功能是所有圖像編輯需求的基石,實現時需兼顧 “精度” 和 “性能”—— 既要保證編輯后的圖像清晰,又要避免操作時卡頓。
2.1 圖像裁剪:保留指定區域
裁剪是最常用的編輯功能(如裁剪頭像、截取圖片中的部分內容),核心是 “從原圖中截取指定矩形區域的像素”。
(1)基本裁剪實現(按坐標裁剪)
/*** 裁剪Bitmap的指定區域* @param srcBitmap 原圖Bitmap* @param x 裁剪區域左上角x坐標(相對于原圖)* @param y 裁剪區域左上角y坐標(相對于原圖)* @param width 裁剪區域寬度* @param height 裁剪區域高度* @return 裁剪后的Bitmap(null表示裁剪失敗)*/
public static Bitmap cropBitmap(Bitmap srcBitmap, int x, int y, int width, int height) {if (srcBitmap == null) return null;// 檢查裁剪區域是否在原圖范圍內(避免越界)if (x < 0 || y < 0 || width <= 0 || height <= 0|| x + width > srcBitmap.getWidth()|| y + height > srcBitmap.getHeight()) {return null;}try {// 從原圖裁剪指定區域(創建新Bitmap,像素從原圖復制)return Bitmap.createBitmap(srcBitmap,x, y, // 起始坐標width, height // 裁剪寬高);} catch (IllegalArgumentException e) {e.printStackTrace();return null;}
}
使用場景:從圖片中心裁剪正方形區域(如頭像裁剪):
// 原圖
Bitmap originalBitmap = BitmapFactory.decodeFile("/sdcard/image.jpg");
if (originalBitmap == null) return;// 計算裁剪區域(從中心裁剪200x200的正方形)
int srcWidth = originalBitmap.getWidth();
int srcHeight = originalBitmap.getHeight();
int cropSize = Math.min(srcWidth, srcHeight); // 取寬高中的較小值
int x = (srcWidth - cropSize) / 2; // 居中x坐標
int y = (srcHeight - cropSize) / 2; // 居中y坐標// 裁剪
Bitmap croppedBitmap = cropBitmap(originalBitmap, x, y, cropSize, cropSize);
// 顯示裁剪結果
imageView.setImageBitmap(croppedBitmap);// 回收原圖(不再使用)
originalBitmap.recycle();
(2)優化:避免裁剪后圖片過大
若原圖尺寸遠大于顯示需求(如 4000x3000 的圖片裁剪后仍有 2000x2000),需在裁剪后進一步縮放:
/*** 裁剪并縮放(適合大圖片裁剪)* @param srcBitmap 原圖* @param x 裁剪x* @param y 裁剪y* @param cropWidth 裁剪寬度* @param cropHeight 裁剪高度* @param targetWidth 最終目標寬度* @param targetHeight 最終目標高度* @return 裁剪并縮放后的Bitmap*/
public static Bitmap cropAndScale(Bitmap srcBitmap, int x, int y, int cropWidth, int cropHeight, int targetWidth, int targetHeight) {// 先裁剪Bitmap cropped = cropBitmap(srcBitmap, x, y, cropWidth, cropHeight);if (cropped == null) return null;// 再縮放(若裁剪后的尺寸大于目標尺寸)if (cropped.getWidth() > targetWidth || cropped.getHeight() > targetHeight) {Bitmap scaled = scaleBitmap(cropped, targetWidth, targetHeight);cropped.recycle(); // 回收裁剪后的臨時Bitmapreturn scaled;}return cropped;
}
使用場景:裁剪頭像并限制最大尺寸為 200x200:
Bitmap croppedAndScaled = cropAndScale(originalBitmap, x, y, cropSize, cropSize, 200, 200);
2.2 圖像縮放:按比例調整大小
縮放用于 “放大圖片細節” 或 “縮小圖片尺寸”(如將大圖壓縮為縮略圖),需注意保持寬高比以避免圖片拉伸。
(1)按目標尺寸縮放(保持寬高比)
/*** 縮放Bitmap到目標尺寸(保持寬高比,避免拉伸)* @param srcBitmap 原圖* @param targetWidth 目標寬度* @param targetHeight 目標高度* @return 縮放后的Bitmap*/
public static Bitmap scaleBitmap(Bitmap srcBitmap, int targetWidth, int targetHeight) {if (srcBitmap == null) return null;// 計算縮放比例(取寬高比例中的較小值,避免超出目標尺寸)float scaleX = (float) targetWidth / srcBitmap.getWidth();float scaleY = (float) targetHeight / srcBitmap.getHeight();float scale = Math.min(scaleX, scaleY); // 保持寬高比// 計算縮放后的實際尺寸int scaledWidth = (int) (srcBitmap.getWidth() * scale);int scaledHeight = (int) (srcBitmap.getHeight() * scale);// 縮放Bitmap(使用抗鋸齒避免模糊)return Bitmap.createScaledBitmap(srcBitmap,scaledWidth,scaledHeight,true // 抗鋸齒);
}
優勢:通過計算最小縮放比例,確保縮放后的圖片能完整放入目標尺寸,且不拉伸。例如:將 1000x500 的圖片縮放到 300x300 的目標尺寸,會按 0.3 的比例縮放到 300x150(保持 2:1 的寬高比)。
(2)按縮放比例縮放(如放大 1.5 倍)
/*** 按比例縮放(如0.5f縮小一半,2.0f放大一倍)* @param srcBitmap 原圖* @param scale 縮放比例(>1放大,<1縮小)* @return 縮放后的Bitmap*/
public static Bitmap scaleBitmapByRatio(Bitmap srcBitmap, float scale) {if (srcBitmap == null || scale <= 0) return null;int newWidth = (int) (srcBitmap.getWidth() * scale);int newHeight = (int) (srcBitmap.getHeight() * scale);return Bitmap.createScaledBitmap(srcBitmap, newWidth, newHeight, true);
}
使用場景:放大圖片細節(如查看圖片局部):
// 放大1.5倍
Bitmap enlarged = scaleBitmapByRatio(originalBitmap, 1.5f);
2.3 圖像旋轉:修正方向與角度
旋轉用于 “修正圖片方向”(如相機拍攝的圖片旋轉 90 度)或 “實現特殊效果”(如旋轉 180 度翻轉圖片)。
(1)按指定角度旋轉
/*** 旋轉Bitmap指定角度* @param srcBitmap 原圖* @param degrees 旋轉角度(正為順時針,負為逆時針,如90、180、-90)* @return 旋轉后的Bitmap*/
public static Bitmap rotateBitmap(Bitmap srcBitmap, float degrees) {if (srcBitmap == null || degrees % 360 == 0) {return srcBitmap; // 0度旋轉直接返回原圖}// 創建旋轉矩陣Matrix matrix = new Matrix();matrix.postRotate(degrees);// 根據矩陣旋轉Bitmap(使用抗鋸齒)return Bitmap.createBitmap(srcBitmap,0, 0,srcBitmap.getWidth(),srcBitmap.getHeight(),matrix,true // 抗鋸齒);
}
使用場景:修正相機拍攝圖片的旋轉問題(結合 EXIF 信息):
// 讀取圖片的EXIF旋轉角度(參考前文fixBitmapRotation方法)
int exifRotation = getExifRotation(imagePath); // 如90度
// 旋轉圖片
Bitmap rotated = rotateBitmap(originalBitmap, exifRotation);
(2)旋轉后的內存優化
旋轉可能導致 Bitmap 尺寸變大(如正方形旋轉為菱形后,邊界擴大),需根據需求裁剪多余空白:
/*** 旋轉并裁剪空白區域(適合正方形旋轉為菱形后去除空白)* @param srcBitmap 原圖* @param degrees 旋轉角度* @return 旋轉并裁剪后的Bitmap*/
public static Bitmap rotateAndCrop(Bitmap srcBitmap, float degrees) {Bitmap rotated = rotateBitmap(srcBitmap, degrees);if (rotated == null) return null;// 計算裁剪區域(去除旋轉后的空白)int width = rotated.getWidth();int height = rotated.getHeight();int cropSize = Math.min(width, height);int x = (width - cropSize) / 2;int y = (height - cropSize) / 2;Bitmap cropped = cropBitmap(rotated, x, y, cropSize, cropSize);rotated.recycle();return cropped;
}
2.4 基礎編輯的性能優化
基礎編輯(尤其是裁剪、縮放)操作頻繁創建 Bitmap,易導致內存問題,需注意:
1.及時回收臨時 Bitmap:中間過程產生的 Bitmap(如裁剪后的臨時圖)使用后立即回收;
2.避免在主線程操作大圖片:超過 1000x1000 的圖片編輯需在子線程執行;
// 用Coroutine在子線程執行編輯
CoroutineScope(Dispatchers.IO).launch {Bitmap edited = cropBitmap(originalBitmap, x, y, width, height);withContext(Dispatchers.Main) {imageView.setImageBitmap(edited);}
}
3.優先使用createScaledBitmap而非Matrix縮放:前者性能更優(底層有優化);
4.大圖片先縮小再編輯:對 4000x3000 的圖片,先縮放到 1000x750 再裁剪,可減少 90% 的內存占用。
三、進階編輯功能:濾鏡、水印、圓角
進階編輯功能能提升圖像的視覺效果,實現時需平衡 “效果質量” 和 “性能開銷”—— 復雜的濾鏡效果若實現不當,會導致操作時嚴重卡頓。
3.1 圖像濾鏡:調整顏色與風格
濾鏡通過修改像素的 ARGB 值實現特殊效果(如黑白、懷舊、高亮),核心是 “遍歷像素并計算新顏色”。
(1)黑白濾鏡(基礎濾鏡)
黑白濾鏡的原理是 “將每個像素的 RGB 值轉為灰度值”(灰度 = 0.299R + 0.587G + 0.114*B)。
/*** 黑白濾鏡(將彩色圖轉為黑白圖)* @param srcBitmap 原圖* @return 黑白效果的Bitmap*/
public static Bitmap applyBlackWhiteFilter(Bitmap srcBitmap) {if (srcBitmap == null) return null;// 創建可修改的Bitmap(原圖可能是不可變的)Bitmap destBitmap = srcBitmap.copy(Bitmap.Config.ARGB_8888, true);int width = destBitmap.getWidth();int height = destBitmap.getHeight();// 遍歷所有像素for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {// 獲取當前像素的ARGB值int pixel = destBitmap.getPixel(x, y);int a = Color.alpha(pixel); // 透明度int r = Color.red(pixel); // 紅色int g = Color.green(pixel); // 綠色int b = Color.blue(pixel); // 藍色// 計算灰度值(黑白濾鏡核心)int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);// 設置新像素(灰度值賦予RGB,保持透明度)int newPixel = Color.argb(a, gray, gray, gray);destBitmap.setPixel(x, y, newPixel);}}return destBitmap;
}
優化技巧:使用getPixels批量獲取像素(比getPixel逐個獲取快 10 倍以上):
// 優化版:批量處理像素
public static Bitmap applyBlackWhiteFilterOptimized(Bitmap srcBitmap) {if (srcBitmap == null) return null;Bitmap destBitmap = srcBitmap.copy(Bitmap.Config.ARGB_8888, true);int width = destBitmap.getWidth();int height = destBitmap.getHeight();int totalPixels = width * height;// 批量獲取像素數組int[] pixels = new int[totalPixels];destBitmap.getPixels(pixels, 0, width, 0, 0, width, height);// 遍歷像素數組(比逐個getPixel快得多)for (int i = 0; i < totalPixels; i++) {int pixel = pixels[i];int a = Color.alpha(pixel);int r = Color.red(pixel);int g = Color.green(pixel);int b = Color.blue(pixel);int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);pixels[i] = Color.argb(a, gray, gray, gray);}// 將處理后的像素數組設置回BitmapdestBitmap.setPixels(pixels, 0, width, 0, 0, width, height);return destBitmap;
}
(2)懷舊濾鏡(色彩調整)
懷舊濾鏡通過降低藍色分量、提高紅色和綠色分量,模擬老照片效果:
/*** 懷舊濾鏡* @param srcBitmap 原圖* @return 懷舊效果的Bitmap*/
public static Bitmap applyNostalgiaFilter(Bitmap srcBitmap) {if (srcBitmap == null) return null;Bitmap destBitmap = srcBitmap.copy(Bitmap.Config.ARGB_8888, true);int width = destBitmap.getWidth();int height = destBitmap.getHeight();int[] pixels = new int[width * height];destBitmap.getPixels(pixels, 0, width, 0, 0, width, height);for (int i = 0; i < pixels.length; i++) {int pixel = pixels[i];int a = Color.alpha(pixel);int r = Color.red(pixel);int g = Color.green(pixel);int b = Color.blue(pixel);// 懷舊效果算法:降低藍色,提高紅綠色int newR = (int) (0.393 * r + 0.769 * g + 0.189 * b);int newG = (int) (0.349 * r + 0.686 * g + 0.168 * b);int newB = (int) (0.272 * r + 0.534 * g + 0.131 * b);// 確保顏色值在0-255范圍內newR = Math.min(255, newR);newG = Math.min(255, newG);newB = Math.min(255, newB);pixels[i] = Color.argb(a, newR, newG, newB);}destBitmap.setPixels(pixels, 0, width, 0, 0, width, height);return destBitmap;
}
(3)使用 RenderScript 加速濾鏡(大圖片必備)
遍歷像素的濾鏡在大圖片(如 2000x2000)上會非常慢(可能超過 1 秒)。RenderScript 是 Android 提供的高性能計算框架,可通過 GPU 加速像素處理,速度比 Java 遍歷快 5-10 倍。
黑白濾鏡的 RenderScript 實現:
/*** 使用RenderScript實現黑白濾鏡(高性能)* @param context 上下文* @param srcBitmap 原圖* @return 黑白效果Bitmap*/
public static Bitmap applyBlackWhiteWithRenderScript(Context context, Bitmap srcBitmap) {if (srcBitmap == null) return null;// 創建輸出BitmapBitmap destBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(),srcBitmap.getConfig());// 初始化RenderScriptRenderScript rs = RenderScript.create(context);// 創建輸入輸出分配器Allocation input = Allocation.createFromBitmap(rs, srcBitmap);Allocation output = Allocation.createFromBitmap(rs, destBitmap);// 創建黑白濾鏡腳本(需在res/raw下創建bw_filter.rs)ScriptIntrinsicColorMatrix colorMatrix = ScriptIntrinsicColorMatrix.create(rs);// 設置黑白矩陣(RGB轉為灰度)Matrix3f matrix = new Matrix3f();matrix.set(0, 0, 0.299f);matrix.set(0, 1, 0.587f);matrix.set(0, 2, 0.114f);matrix.set(1, 0, 0.299f);matrix.set(1, 1, 0.587f);matrix.set(1, 2, 0.114f);matrix.set(2, 0, 0.299f);matrix.set(2, 1, 0.587f);matrix.set(2, 2, 0.114f);colorMatrix.setColorMatrix(matrix);// 執行濾鏡colorMatrix.forEach(input, output);// 將結果復制到輸出Bitmapoutput.copyTo(destBitmap);// 釋放資源input.destroy();output.destroy();colorMatrix.destroy();rs.destroy();return destBitmap;
}
RenderScript 腳本(res/raw/bw_filter.rs):無需額外代碼,直接使用ScriptIntrinsicColorMatrix內置功能。
優勢:2000x2000 的圖片,Java 遍歷需 1.2 秒,RenderScript 僅需 0.15 秒,性能提升 8 倍。
3.2 圖像水印:添加文字或圖片標識
水印用于 “版權聲明”(如圖片添加 APP 名稱)或 “信息補充”(如拍攝時間、地點),分為文字水印和圖片水印。
(1)文字水印(指定位置和樣式)
/*** 給Bitmap添加文字水印* @param srcBitmap 原圖* @param text 水印文字(如"我的APP")* @param textSize 文字大小(sp)* @param textColor 文字顏色* @param position 水印位置(1=左上,2=右上,3=左下,4=右下)* @param padding 邊距(dp)* @return 添加水印后的Bitmap*/
public static Bitmap addTextWatermark(Bitmap srcBitmap, String text, float textSize, int textColor, int position, int padding) {if (srcBitmap == null || TextUtils.isEmpty(text)) return srcBitmap;// 創建可繪制的BitmapBitmap destBitmap = srcBitmap.copy(Bitmap.Config.ARGB_8888, true);Canvas canvas = new Canvas(destBitmap); // 創建畫布// 轉換單位(sp→px,dp→px)Resources resources = Resources.getSystem();float textSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,textSize,resources.getDisplayMetrics());int paddingPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,padding,resources.getDisplayMetrics());// 配置畫筆Paint paint = new Paint();paint.setColor(textColor);paint.setTextSize(textSizePx);paint.setAntiAlias(true); // 抗鋸齒paint.setAlpha(128); // 半透明(0-255)paint.setTextAlign(Paint.Align.LEFT);// 計算文字寬高Rect textBounds = new Rect();paint.getTextBounds(text, 0, text.length(), textBounds);int textWidth = textBounds.width();int textHeight = textBounds.height();// 計算水印位置坐標int x = 0, y = 0;int bitmapWidth = destBitmap.getWidth();int bitmapHeight = destBitmap.getHeight();switch (position) {case 1: // 左上x = paddingPx;y = paddingPx + textHeight; // y是基線位置,需加上文字高度break;case 2: // 右上x = bitmapWidth - paddingPx - textWidth;y = paddingPx + textHeight;break;case 3: // 左下x = paddingPx;y = bitmapHeight - paddingPx;break;case 4: // 右下x = bitmapWidth - paddingPx - textWidth;y = bitmapHeight - paddingPx;break;}// 繪制文字canvas.drawText(text, x, y, paint);return destBitmap;
}
使用場景:給圖片右下角添加半透明水印:
Bitmap watermarked = addTextWatermark(originalBitmap,"我的APP",14, // 14spColor.argb(128, 255, 255, 255), // 白色半透明4, // 右下16 // 16dp邊距
);
(2)圖片水印(添加 Logo)
/*** 給Bitmap添加圖片水印(如Logo)* @param srcBitmap 原圖* @param watermarkBitmap 水印圖片(Logo)* @param position 位置(同文字水印)* @param padding 邊距(dp)* @param alpha 透明度(0-255,0完全透明)* @return 添加水印后的Bitmap*/
public static Bitmap addImageWatermark(Bitmap srcBitmap, Bitmap watermarkBitmap, int position, int padding, int alpha) {if (srcBitmap == null || watermarkBitmap == null) return srcBitmap;Bitmap destBitmap = srcBitmap.copy(Bitmap.Config.ARGB_8888, true);Canvas canvas = new Canvas(destBitmap);// 轉換邊距單位(dp→px)int paddingPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,padding,Resources.getSystem().getDisplayMetrics());// 水印寬高int watermarkWidth = watermarkBitmap.getWidth();int watermarkHeight = watermarkBitmap.getHeight();// 原圖寬高int srcWidth = srcBitmap.getWidth();int srcHeight = srcBitmap.getHeight();// 計算水印位置int x = 0, y = 0;switch (position) {case 1: // 左上x = paddingPx;y = paddingPx;break;case 2: // 右上x = srcWidth - paddingPx - watermarkWidth;y = paddingPx;break;case 3: // 左下x = paddingPx;y = srcHeight - paddingPx - watermarkHeight;break;case 4: // 右下x = srcWidth - paddingPx - watermarkWidth;y = srcHeight - paddingPx - watermarkHeight;break;}// 設置透明度Paint paint = new Paint();paint.setAlpha(alpha);// 繪制水印圖片canvas.drawBitmap(watermarkBitmap, x, y, paint);return destBitmap;
}
使用場景:給圖片添加右下角半透明 Logo:
// 加載Logo圖片
Bitmap logo = BitmapFactory.decodeResource(getResources(), R.drawable.logo);
// 添加水印(透明度128=半透明)
Bitmap withLogo = addImageWatermark(originalBitmap, logo, 4, 16, 128);
3.3 圓角處理:給圖片添加圓角或圓形效果
圓角圖片用于 “頭像”“卡片設計” 等場景,實現方式有兩種:繪制圓角 Bitmap 或使用 Drawable(如RoundedBitmapDrawable)。
(1)繪制圓角 Bitmap
/*** 給Bitmap添加圓角* @param srcBitmap 原圖* @param radius 圓角半徑(dp)* @return 圓角Bitmap*/
public static Bitmap createRoundCornerBitmap(Bitmap srcBitmap, float radius) {if (srcBitmap == null) return null;// 轉換圓角半徑單位(dp→px)radius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,radius,Resources.getSystem().getDisplayMetrics());// 創建輸出Bitmap(ARGB_8888支持透明)Bitmap destBitmap = Bitmap.createBitmap(srcBitmap.getWidth(),srcBitmap.getHeight(),Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(destBitmap);// 繪制圓角矩形作為畫布裁剪區域Paint paint = new Paint();paint.setAntiAlias(true); // 抗鋸齒RectF rectF = new RectF(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());canvas.drawRoundRect(rectF, radius, radius, paint);// 設置畫筆模式為“只繪制與已有內容重疊的區域”paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 繪制原圖(僅在圓角矩形區域內顯示)canvas.drawBitmap(srcBitmap, 0, 0, paint);return destBitmap;
}
使用場景:將頭像處理為 8dp 圓角:
Bitmap roundCorner = createRoundCornerBitmap(originalBitmap, 8);
(2)創建圓形 Bitmap(頭像常用)
圓形是圓角的特殊情況(半徑為寬高的一半):
/*** 創建圓形Bitmap(如圓形頭像)* @param srcBitmap 原圖(建議正方形)* @return 圓形Bitmap*/
public static Bitmap createCircleBitmap(Bitmap srcBitmap) {if (srcBitmap == null) return null;// 取最小邊作為圓形直徑int size = Math.min(srcBitmap.getWidth(), srcBitmap.getHeight());// 裁剪為正方形(避免非正方形圖片導致橢圓)Bitmap squareBitmap = cropBitmap(srcBitmap,(srcBitmap.getWidth() - size) / 2,(srcBitmap.getHeight() - size) / 2,size, size);// 繪制圓形Bitmap circleBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(circleBitmap);Paint paint = new Paint();paint.setAntiAlias(true);// 繪制圓形canvas.drawCircle(size / 2, size / 2, size / 2, paint);// 設置混合模式paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 繪制正方形圖片canvas.drawBitmap(squareBitmap, 0, 0, paint);squareBitmap.recycle(); // 回收裁剪的正方形Bitmapreturn circleBitmap;
}
優化方案:使用RoundedBitmapDrawable(無需創建新 Bitmap):
/*** 使用RoundedBitmapDrawable創建圓形圖片(更高效)* @param context 上下文* @param srcBitmap 原圖* @return RoundedBitmapDrawable(可直接設置給ImageView)*/
public static RoundedBitmapDrawable createCircleDrawable(Context context, Bitmap srcBitmap) {if (srcBitmap == null) return null;// 裁剪為正方形int size = Math.min(srcBitmap.getWidth(), srcBitmap.getHeight());Bitmap squareBitmap = cropBitmap(srcBitmap,(srcBitmap.getWidth() - size) / 2,(srcBitmap.getHeight() - size) / 2,size, size);// 創建RoundedBitmapDrawableRoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(context.getResources(),squareBitmap);drawable.setCircular(true); // 設置為圓形drawable.setAntiAlias(true); // 抗鋸齒return drawable;
}// 使用:直接設置給ImageView,無需創建新Bitmap
RoundedBitmapDrawable circleDrawable = createCircleDrawable(this, originalBitmap);
imageView.setImageDrawable(circleDrawable);
優勢:RoundedBitmapDrawable是 Drawable,不額外占用 Bitmap 內存,性能更優。
四、圖像編輯的常見問題與解決方案
圖像編輯涉及大量 Bitmap 操作,容易出現各種問題,以下是高頻問題及解決辦法。
4.1 編輯后圖片模糊
原因:
- 縮放時未使用抗鋸齒(createScaledBitmap的filter參數設為false);
- 裁剪 / 縮放后圖片尺寸過小(如將 1000x1000 的圖片縮放到 50x50,再放大顯示);
- 像素格式選擇錯誤(如用RGB_565顯示需要透明的圖片)。
解決方案:
- 縮放 / 旋轉時始終開啟抗鋸齒(filter參數設為true);
- 編輯后的圖片尺寸不小于顯示尺寸(如 ImageView 寬 200dp,則編輯后 Bitmap 寬不小于 200dp);
- 透明圖片用ARGB_8888格式,非透明圖片用RGB_565。
4.2 編輯時卡頓或 ANR
原因:
- 在主線程處理大圖片(如 2000x2000 的 Bitmap 濾鏡操作);
- 遍歷像素時使用getPixel/setPixel(逐個操作效率極低);
- 頻繁創建和回收 Bitmap(導致 GC 頻繁觸發)。
解決方案:
- 所有編輯操作移到子線程(用 Coroutine 或 AsyncTask);
- 批量操作像素(getPixels/setPixels)或使用 RenderScript;
- 復用 Bitmap(如列表中的頭像編輯,復用 convertView 的舊 Bitmap)。
4.3 編輯大圖片時 OOM
原因:
- 加載原圖時未縮放(直接加載 4000x3000 的圖片,內存占用 48MB);
- 編輯過程中創建多個臨時 Bitmap(如裁剪→縮放→濾鏡,每個步驟都創建新 Bitmap);
- 未及時回收不再使用的 Bitmap(原圖、臨時圖占用內存)。
解決方案:
- 加載原圖時先縮放(如縮放到 1000x750 再編輯);
- 合并編輯步驟(如裁剪和縮放合并為一個步驟,減少臨時 Bitmap);
- 編輯后立即回收原圖和臨時 Bitmap(bitmap.recycle() + bitmap = null)。
4.4 旋轉后圖片有黑色背景
原因:旋轉非正方形圖片時,Bitmap 尺寸擴大,新增區域默認填充黑色(透明通道未處理)。
解決方案:
- 使用ARGB_8888格式(支持透明);
- 旋轉后裁剪多余區域(如rotateAndCrop方法);
- 繪制時設置畫布背景為透明(canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR))。
五、圖像編輯的最佳實踐
結合前文內容,圖像編輯的最佳實踐可總結為以下原則:
1.內存優先:
- 加載圖片時按編輯需求縮放(不加載大于需求的圖片);
- 及時回收中間 Bitmap(原圖、臨時處理結果);
- 優先使用 Drawable(如RoundedBitmapDrawable)而非創建新 Bitmap。
2.性能優化:
- 大圖片編輯用 RenderScript(GPU 加速);
- 批量操作像素(避免getPixel逐個處理);
- 子線程執行所有編輯操作,主線程只負責顯示結果。
3.效果保證:
- 保持寬高比(避免圖片拉伸);
- 操作時開啟抗鋸齒(避免邊緣鋸齒);
- 透明圖片用ARGB_8888格式。
4.兼容性處理:
- Android 10+ 保存圖片用MediaStore(替代直接操作 File);
- 不同密度設備(如 hdpi、xxhdpi)適配單位(dp/sp 轉 px)。
六、總結:圖像編輯的核心邏輯
Android 圖像編輯的本質是 “對 Bitmap 像素的可控修改”,無論是裁剪、濾鏡還是水印,最終都落實到像素的選擇、計算或替換。掌握圖像編輯的關鍵在于:
- 理解 Bitmap 內存模型:知道如何控制 Bitmap 大小(采樣率、縮放),避免 OOM;
- 掌握像素操作技巧:批量處理像素、使用 RenderScript 加速,避免卡頓;
- 平衡效果與性能:根據需求選擇合適的實現方式(如簡單圓角用RoundedBitmapDrawable,復雜濾鏡用 RenderScript)。
通過本文的方法,你可以實現從基礎到進階的各類圖像編輯功能,同時避開內存和性能陷阱。圖像編輯的核心不是 “實現功能”,而是 “高質量、高效率地實現功能”—— 這需要在實踐中不斷優化,根據具體場景調整方案。