首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

圖像層正常混合模式詳解(上):Alpha 混合算法完整推導與 C 實現

     編輯:關於C語言

圖像合成是圖像處理中最高頻操作,本質是像素顏色混合。Photoshop 中的「正常混合模式」看似簡單,實則包含多種 Alpha 通道組合情況。

基本混合公式(無 Alpha 通道)

dstRGB = srcRGB * alpha + dstRGB * (1 - alpha)

其中 alpha ∈ [0,1] 為不透明度。此公式僅適用於無 Alpha 通道的圖像。

帶 Alpha 通道的完整混合公式(預乘 Alpha - PARGB)

  1. 源像素預乘: srcRGB = srcRGB * srcAlpha * alpha / 255
  2. 目標像素預乘: dstRGB = dstRGB * dstAlpha / 255
  3. 混合: dstRGB = dstRGB + srcRGB - dstRGB * srcAlpha * alpha / 255
  4. 混合後 Alpha: dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255
  5. 還原非預乘: dstRGB = 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 時退化為基本公式。

8 種 Alpha 組合情況的 C 實現

根據 目標是否有 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

7 個核心混合函數

  • Mixer0:源無 Alpha、目標無 Alpha、alpha < 255
  • Mixer1:源無 Alpha、目標無 Alpha、alpha = 255(直接拷貝)
  • Mixer2:源無 Alpha、目標有 Alpha、alpha < 255
  • Mixer4:源有 Alpha、目標無 Alpha、alpha < 255
  • Mixer5:源有 Alpha、目標無 Alpha、alpha = 255
  • Mixer6:源有 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 情況

BCB2007 + GDI+ 調用示例

// 鎖定位圖掃描線
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 正常混合

摘自 閒人阿發伯的業余編程心得

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved