程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 圖像(層)正常混合模式詳解(上)

圖像(層)正常混合模式詳解(上)

編輯:關於C語言

 在圖像處理過程中,圖像的合成操作是使用頻率最高的,如圖像顯示、圖像拷貝、圖像拼接以及的圖層拼合疊加等。
    圖像合成,其實也就是圖像像素顏色的混合,在Photoshop中,顏色混合是個很復雜的東西,不同的混合模式,將產生不同的合成效果,如果將之全部研究透徹,估計就得寫一本書。因此,本文只談談最基本的圖像合成,也就是Photoshop中的正常混合模式。
    只要接觸過圖像處理的,都知道有個圖像像素混合公式:
    1)dstRGB = srcRGB * alpha + dstRGB * (1 - alpha)
    其中,dstRGB為目標圖像素值;srcRGB為源圖像素值;alpha為源圖像素值混合比例(不透明度,范圍0 - 1)。
    其實,這個像素混合公式有很大局限性,只適合不含Alpha信息的圖像。
    要處理包括帶Alpha通道圖像(層)的混合,其完整的公式應該是:
    2-1)srcRGB = srcRGB * srcAlpha * alpha / 255      (源圖像素預乘轉換為PARGB)
    2-2)dstRGB = dstRGB * dstAlpha / 255    (目標圖像素預乘轉換為PARGB)
    2-3)dstRGB = dstRGB + srcRGB - dstRGB * srcAlpha * alpha / 255    (源圖像素值與目標圖像素值混合)
    2-4)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255    (混合後的目標圖Alpha通道值)
    2-5)dstRGB = dstRGB * 255 / dstAlpha    (混合後的目標圖像素轉換為ARGB)
    其中,dstRGB為目標圖像素值;srcRGB為源圖像素值;dstAlpha為目標圖Alpha通道值;srcAlpha為源圖Alpha通道值;dstARGB為含Alpha目標圖像素值;alpha為源圖像素值混合比例(不透明度,范圍0 - 1)。
    將公式2中的2-1式代入2-3式,簡化可得:
    3-1)dstRGB = dstRGB * dstAlpha / 255
    3-2)dstRGB = dstRGB +  (srcRGB - dstRGB) * srcAlpha * alpha / 255
    3-3)dstAlpha = dstAlpha + srcAlpha * alpha - dstAlpha * srcAlpha * alpha / 255
    3-4)dstRGB = dstRGB * 255 / dstAlpha
    當dstAlpha=srcAlpha=255時,公式3中3-1式、3-3式和3-4式沒有意義,3-2式也變化為:
   4)dstRGB = dstRGB +  (srcRGB - dstRGB) * alpha
    不難看出,公式4是公式1的變形。因此,公式1只是公式3(或者公式2)在目標圖和源圖都不含Alpha信息(或者Alpha=255)情況下的一個特例而已。
    當公式4中的alpha=1時,目標圖像素等於源圖像素,所以,本文前面說圖像拷貝其實也是圖像合成的范疇。
    通過上面較詳細的分析,可以看出,即使是最基本正常圖像混合模式也是很復雜的。其實,上面還不是完整的分析,因為按照目標圖Alpha信息、源圖Alpha信息以及源圖合成比例等三個要素的完全的排列組合,最多可以派生8個公式。
    下面就按正常混合模式的全部8種情況(有2項重合,實際為7種情況)來分別進行代碼實現,也可完善和補充上面的文字敘述:

//--------------------------------------------------------------------------- 
 
// 定義ARGB像素結構 
typedef union 

    ARGB Color; 
    struct 
    { 
        BYTE Blue; 
        BYTE Green; 
        BYTE Red; 
        BYTE Alpha; 
    }; 
}ARGBQuad, *PARGBQuad; 
 
typedef struct 

    INT width; 
    INT height; 
    PARGBQuad dstScan0; 
    PARGBQuad srcScan0; 
    INT dstOffset; 
    INT srcOffset; 
}ImageCpyData, *PImageCpyData; 
 
typedef VOID (*MixerProc)(PImageCpyData, INT); 
 
#define PixelAlphaFlag  0x10000 
//--------------------------------------------------------------------------- 
// source alpha = false, dest alpha = false, alpha < 255 
static VOID Mixer0(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            pd->Blue += (((ps->Blue - pd->Blue) * alpha + 127) / 255); 
            pd->Green += (((ps->Green - pd->Green) * alpha + 127) / 255); 
            pd->Red += (((ps->Red - pd->Red) * alpha + 127) / 255); 
        } 
    } 

//--------------------------------------------------------------------------- 
// source alpha = false, dest alpha = false, alpha = 255 
// source alpha = false, dest alpha = true, alpha = 255 
static VOID Mixer1(PImageCpyData cpyData, INT alpha) 

    ARGB *pd = (ARGB*)cpyData->dstScan0; 
    ARGB *ps = (ARGB*)cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, *pd ++ = *ps ++); 
    } 

//--------------------------------------------------------------------------- 
// source alpha = false, dest alpha = true, alpha < 255 
static VOID Mixer2(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            pd->Blue = (pd->Blue * pd->Alpha + 127) / 255; 
            pd->Green = (pd->Green * pd->Alpha + 127) / 255; 
            pd->Red = (pd->Red * pd->Alpha + 127) / 255; 
 
            pd->Blue += (((ps->Blue - pd->Blue) * alpha + 127) / 255); 
            pd->Green += (((ps->Green - pd->Green) * alpha + 127) / 255); 
            pd->Red += (((ps->Red - pd->Red) * alpha + 127) / 255); 
            pd->Alpha += (alpha - (pd->Alpha * alpha + 127) / 255); 
 
            pd->Blue = pd->Blue * 255 / pd->Alpha; 
            pd->Green = pd->Green * 255 / pd->Alpha; 
            pd->Red = pd->Red * 255 / pd->Alpha; 
        } 
    } 

//--------------------------------------------------------------------------- 
// source alpha = true, dest alpha = false, alpha < 255 
static VOID Mixer4(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            INT alpha0 = (alpha * ps->Alpha + 127) / 255; 
            pd->Blue += (((ps->Blue - pd->Blue) * alpha0 + 127) / 255); 
            pd->Green += (((ps->Green - pd->Green) * alpha0 + 127) / 255); 
            pd->Red += (((ps->Red - pd->Red) * alpha0 + 127) / 255); 
        } 
    } 

//--------------------------------------------------------------------------- 
// source alpha = true, dest alpha = false, alpha = 255 
static VOID Mixer5(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            pd->Blue += (((ps->Blue - pd->Blue) * ps->Alpha + 127) / 255); 
            pd->Green += (((ps->Green - pd->Green) * ps->Alpha + 127) / 255); 
            pd->Red += (((ps->Red - pd->Red) * ps->Alpha + 127) / 255); 
        } 
    } 

//--------------------------------------------------------------------------- 
// source alpha = true, dest alpha = true, alpha < 255 
static VOID Mixer6(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            INT alpha0 = (alpha * ps->Alpha + 127) / 255; 
            if (alpha0) 
            { 
                pd->Blue = (pd->Blue * pd->Alpha + 127) / 255; 
                pd->Green = (pd->Green * pd->Alpha + 127) / 255; 
                pd->Red = (pd->Red * pd->Alpha + 127) / 255; 
 
                pd->Blue += (((ps->Blue - pd->Blue) * alpha0 + 127) / 255); 
                pd->Green += (((ps->Green - pd->Green) * alpha0 + 127) / 255); 
                pd->Red += (((ps->Red - pd->Red) * alpha0 + 127) / 255); 
                pd->Alpha += (alpha0 - (pd->Alpha * alpha0 + 127) / 255); 
 
                pd->Blue = pd->Blue * 255 / pd->Alpha; 
                pd->Green = pd->Green * 255 / pd->Alpha; 
                pd->Red = pd->Red * 255 / pd->Alpha; 
            } 
        } 
    } 

//--------------------------------------------------------------------------- 
// source alpha = true, dest alpha = true, alpha = 255 
static VOID Mixer7(PImageCpyData cpyData, INT alpha) 

    PARGBQuad pd = cpyData->dstScan0; 
    PARGBQuad ps = cpyData->srcScan0; 
    for (INT y = 0; y < cpyData->height; y ++, pd += cpyData->dstOffset, ps += cpyData->srcOffset) 
    { 
        for (INT x = 0; x < cpyData->width; x ++, pd ++, ps ++) 
        { 
            if (ps->Alpha) 
            { 
                pd->Blue = (pd->Blue * pd->Alpha + 127) / 255; 
                pd->Green = (pd->Green * pd->Alpha + 127) / 255; 
                pd->Red = (pd->Red * pd->Alpha + 127) / 255; 
 
                pd->Blue += (((ps->Blue - pd->Blue) * ps->Alpha + 127) / 255); 
                pd->Green += (((ps->Green - pd->Green) * ps->Alpha + 127) / 255); 
                pd->Red += (((ps->Red - pd->Red) * ps->Alpha + 127) / 255); 
                pd->Alpha += (ps->Alpha - (pd->Alpha * ps->Alpha + 127) / 255); 
 
                pd->Blue = pd->Blue * 255 / pd->Alpha; 
                pd->Green = pd->Green * 255 / pd->Alpha; 
                pd->Red = pd->Red * 255 / pd->Alpha; 
            } 
        } 
    } 

//--------------------------------------------------------------------------- 
 
VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, INT alpha) 

    if (alpha <= 0) return; 
    if (alpha > 255) alpha = 255; 
 
    ImageCpyData data; 
    data.width = (INT)(dest->Width < source->Width? dest->Width : source->Width); 
    data.height = (INT)(dest->Height < source->Height? 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); 

//--------------------------------------------------------------------------- 
    函數ImageMixer有三個參數,分別為目標圖數據結構(借用GDI+的BitmapData結構)指針、源圖數據結構指針和源圖像素混合比例(不透明度,取值范圍為0 - 255,前面的公式中的取值范圍0 - 1是方便描述)。函數體中的proc數組包括了圖像混合的全部8種情況的子函數,而index則按混合比例、目標圖Alpha信息和源圖Alpha信息組合成子函數調用下標值(Alpha信息在BitmapData結構的保留字段中)。
    當然,在實際的運用中,全部8種情況似乎是多了點,可根據情況進行適當合並取捨,以兼顧代碼的復雜度和執行效率。下面是我認為比較合理的精簡版ImageMixer函數:

VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, INT alpha) 

    if (alpha <= 0) return; 
    if (alpha > 255) alpha = 255; 
 
    ImageCpyData data; 
    data.width = (INT)(dest->Width < source->Width? dest->Width : source->Width); 
    data.height = (INT)(dest->Height < source->Height? 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; 
 
    if (alpha == 255 && !(source->Reserved & PixelAlphaFlag)) 
        Mixer1(&data, alpha); 
    else if (dest->Reserved & PixelAlphaFlag) 
        Mixer6(&data, alpha); 
    else 
        Mixer4(&data, alpha); 

//--------------------------------------------------------------------------- 
    這個ImageMixer函數只保留了3個調用子函數,其中,Mixer6是完全的正常混合模式,即前面公式3的實現;Mixer4為對不含Alpha信息目標圖的混合,即在公式4基礎上稍稍擴充了的情況;而Mixer1則為拷貝模式。
    下面是采用BCB2007和GDI+調用ImageMixer函數的例子:

//--------------------------------------------------------------------------- 
 
// 鎖定GDI+位位圖掃描線到data 
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; 

//--------------------------------------------------------------------------- 
 
// GDI+位圖掃描線解鎖 
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); 
    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