在行動裝置上處理圖像時,彩色轉灰度是基礎且高頻的操作。考慮到早期手機 CPU 無浮點運算單元(FPU),如何用整數運算高效實現灰度轉換,曾是 J2ME 開發者必修的功課。本文系統梳理從經典心理學公式到位移優化的完整演進過程。
人眼對 RGB 三原色敏感度不同,國際標準灰度公式為:
Gray = R × 0.299 + G × 0.587 + B × 0.114
此公式基於人視覺感知權重,轉換效果最自然。
為避免低速浮點運算,將係數放大 1000 倍轉為整數運算:
Gray = (R × 299 + G × 587 + B × 114 + 500) / 1000
其中 +500 實現四捨五入。RGB 各 8 位,運算過程屬 32 位整數範圍。另一常見變種放大 100 倍:
Gray = (R × 30 + G × 59 + B × 11 + 50) / 100
雖然前者屬 32 位運算,但在 x86 架構下可用 16 位乘除指令完成,且現代處理器普及 32/64 位,推薦使用 1000 倍係數版本精度更高。
除法指令週期遠大於移位,將係數放大為 2 的冪次(如 216 = 65536),即可用右移 16 位替代除法:
Gray = (R × 19595 + G × 38469 + B × 7472) >> 16
係數推導採用「誤差累積尾捨法」而非簡單四捨五入,能有效控制累積誤差。
從 2 位到 20 位精度的係數對照表顯示,部分鄰近精度實際效果相近(如 3/4、7/8、10/11、13/14、19/20 位)。在 16 位運算環境下,7 位精度公式性價比最高:
Gray = (R × 38 + G × 75 + B × 15) >> 7
精度優於 100 倍係數版本,速度更快。極簡 2 位精度可全移位優化:
Gray = (R + (G << 1) + B) >> 2
誤差較大,但遊戲場景切換快、用戶難察覺色差,常用於遊戲實時灰度化(如角色死亡全屏變灰)。
public static int[] toGrayscale(Image image) {
int[] rgb = new int[image.getWidth() * image.getHeight()];
image.getRGB(rgb, 0, image.getWidth(), 0, 0, image.getWidth(), image.getHeight());
for (int i = 0; i < rgb.length; i++) {
int r = (rgb[i] & 0x00FF0000) >> 16;
int g = (rgb[i] & 0x0000FF00) >> 8;
int b = rgb[i] & 0x000000FF;
// 跳過全白像素(可選優化)
if (rgb[i] == 0x00FFFFFF) continue;
// 7位精度算法
int gray = (r * 38 + g * 75 + b * 15) >> 7;
rgb[i] = (gray << 16) | (gray << 8) | gray | 0xFF000000;
}
return rgb;
}
此方法配合 Graphics.drawRGB() 可高效實現全屏/局部灰度特效,並在遊戲引擎中靈活切換彩色與灰度模式。