圖像合成是圖像處理中最高頻操作,本質是像素顏色混合。Photoshop 中的「正常混合模式」看似簡單,實則包含多種 Alpha 通道組合情況。
dstRGB = srcRGB * alpha + dstRGB * (1 - alpha)
其中 alpha ∈ [0,1] 為不透明度。此公式僅適用於無 Alpha 通道的圖像。
srcRGB = srcRGB * srcAlpha * alpha / 255dstRGB = dstRGB * dstAlpha / 255dstRGB = dstRGB + srcRGB - dstRGB * srcAlpha * alpha / 255dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255dstRGB = dstRGB * 255 / dstAlpha簡化後得到通用公式:
dstRGB = dstRGB * dstAlpha / 255
dstRGB = dstRGB + (srcRGB - dstRGB) * srcAlpha * alpha / 255
dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255
dstRGB = dstRGB * 255 / dstAlpha
當 dstAlpha = srcAlpha = 255 時退化為基本公式。
根據 目標是否有 Alpha、源是否有 Alpha、alpha 是否為 255 三個二元條件,共 8 種情況(實際 7 種獨特)。
typedef union {
ARGB Color;
struct { BYTE Blue, Green, Red, Alpha; };
} ARGBQuad, *PARGBQuad;
typedef struct {
INT width, height;
PARGBQuad dstScan0, srcScan0;
INT dstOffset, srcOffset;
} ImageCpyData, *PImageCpyData;
typedef VOID (*MixerProc)(PImageCpyData, INT);
#define PixelAlphaFlag 0x10000
Mixer0:源無 Alpha、目標無 Alpha、alpha < 255Mixer1:源無 Alpha、目標無 Alpha、alpha = 255(直接拷貝)Mixer2:源無 Alpha、目標有 Alpha、alpha < 255Mixer4:源有 Alpha、目標無 Alpha、alpha < 255Mixer5:源有 Alpha、目標無 Alpha、alpha = 255Mixer6:源有 Alpha、目標有 Alpha、alpha < 255(完整正常混合)Mixer7:源有 Alpha、目標有 Alpha、alpha = 255每個函數逐像素處理,包含預乘、混合、Alpha 計算、還原步驟,並通過 +127 實現四捨五入優化。
VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, INT alpha) {
if (alpha <= 0) return;
if (alpha > 255) alpha = 255;
ImageCpyData data;
data.width = min(dest->Width, source->Width);
data.height = min(dest->Height, source->Height);
data.dstOffset = (dest->Stride >> 2) - data.width;
data.srcOffset = (source->Stride >> 2) - data.width;
data.dstScan0 = (PARGBQuad)dest->Scan0;
data.srcScan0 = (PARGBQuad)source->Scan0;
MixerProc proc[] = {Mixer0, Mixer1, Mixer2, Mixer1, Mixer4, Mixer5, Mixer6, Mixer7};
INT index = (alpha / 255) | ((dest->Reserved >> 16) << 1) | ((source->Reserved >> 16) << 2);
proc[index](&data, alpha);
}
if (alpha == 255 && !(source->Reserved & PixelAlphaFlag))
Mixer1(&data, alpha); // 直接拷貝
else if (dest->Reserved & PixelAlphaFlag)
Mixer6(&data, alpha); // 完整混合
else
Mixer4(&data, alpha); // 目標無 Alpha 情況
// 鎖定位圖掃描線
FORCEINLINE VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data) {
Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
BOOL hasAlpha = bmp->GetPixelFormat() & PixelFormatAlpha;
bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, data);
if (hasAlpha) data->Reserved |= PixelAlphaFlag;
}
// 解鎖
FORCEINLINE VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data) {
data->Reserved &= 0xff;
bmp->UnlockBits(data);
}
// 測試按鈕事件
void __fastcall TForm1::Button1Click(TObject *Sender) {
Gdiplus::Bitmap *dest = new Gdiplus::Bitmap(L"d:\\xmas_011.png");
Gdiplus::Bitmap *source = new Gdiplus::Bitmap(L"d:\\Apple.png");
Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
g->DrawImage(dest, 0, 0);
g->DrawImage(source, dest->GetWidth(), 0);
BitmapData dst, src;
LockBitmap(dest, &dst);
LockBitmap(source, &src);
ImageMixer(&dst, &src, 192); // 不透明度 192/255
UnlockBitmap(source, &src);
UnlockBitmap(dest, &dst);
g->DrawImage(dest, dest->GetWidth() << 1, 0);
delete g; delete source; delete dest;
}

左:目標圖 | 中:源圖 | 右:源圖以不透明度 192 正常混合
摘自 閒人阿發伯的業余編程心得