閱讀提示:
《C++影像處理》系列以代碼清晰,可讀性為主,全部使用C++代碼。
《Delphi影像處理》系列以效率為側重點,一般代碼為PASCAL,核心代碼採用BASM。
儘可能保持二者內容一致,可相互對照。
本文代碼必須包括《C++影像處理 -- 資料類型及公用函數》文章中的BmpData.h標頭檔。
在影像處理過程中,映像的合成操作是使用頻率最高的,像顯示、映像拷貝、映像拼接以及的圖層拼合疊加等。
映像合成,其實也就是映像像素顏色的混合,在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種情況)來分別進行代碼實現,也可完善和補充上面的文字敘述:
//---------------------------------------------------------------------------FORCEINLINEstatic VOID ARGBMixer(PARGBQuad pd, CONST PARGBQuad ps, INT alpha){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);}//---------------------------------------------------------------------------FORCEINLINEstatic VOID PARGBMixer(PARGBQuad pd, CONST PARGBQuad ps, INT 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) * 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 = false, dest alpha = false, alpha < 255static VOID Mixer0(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++)ARGBMixer(pd, ps, alpha);}}//---------------------------------------------------------------------------// source alpha = false, dest alpha = false, alpha = 255// source alpha = false, dest alpha = true, alpha = 255static VOID Mixer1(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, *pd ++ = *ps ++);}}//---------------------------------------------------------------------------// source alpha = false, dest alpha = true, alpha < 255static VOID Mixer2(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++)PARGBMixer(pd, ps, alpha);}}//---------------------------------------------------------------------------// source alpha = true, dest alpha = false, alpha < 255static VOID Mixer4(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++)ARGBMixer(pd, ps, (alpha * ps->Alpha + 127) / 255);}}//---------------------------------------------------------------------------// source alpha = true, dest alpha = false, alpha = 255static VOID Mixer5(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++)ARGBMixer(pd, ps, ps->Alpha);}}//---------------------------------------------------------------------------// source alpha = true, dest alpha = true, alpha < 255static VOID Mixer6(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++){INT alpha0 = (alpha * ps->Alpha + 127) / 255;if (alpha0)PARGBMixer(pd, ps, alpha0);}}}//---------------------------------------------------------------------------// source alpha = true, dest alpha = true, alpha = 255static VOID Mixer7(BitmapData *dest, CONST BitmapData *source, INT alpha){PARGBQuad pd, ps;UINT width, height;INT dstOffset, srcOffset;GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset){for (UINT x = 0; x < width; x ++, pd ++, ps ++){if (ps->Alpha)PARGBMixer(pd, ps, ps->Alpha);}}}//---------------------------------------------------------------------------typedef VOID (*MixerProc)(BitmapData*, CONST BitmapData*, INT);// 映像合成。參數:32位目標資料,32位來源資料,不透明度(0 - 1.0)VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, FLOAT alpha){INT alphaI = (INT)(alpha * 255);if (alphaI <= 0) return;if (alphaI > 255) alphaI = 255;MixerProc proc[] = {Mixer0, Mixer1, Mixer2, Mixer1, Mixer4, Mixer5, Mixer6, Mixer7};INT index = (alphaI / 255) |(HasAlphaFlag(dest) << 1) |(HasAlphaFlag(source) << 2);proc[index](dest, source, alphaI);}//---------------------------------------------------------------------------
函數ImageMixer有三個參數,分別為靶心圖表資料結構(借用GDI+的BitmapData結構)指標、源圖資料結構指標和源映像素混合比例(不透明度,取值範圍為0 - 1)。函數體中的proc數組包括了映像混合的全部8種情況的子函數,而index則按混合比例、靶心圖表Alpha資訊和源圖Alpha資訊組合成子函數調用下標值(Alpha資訊在BitmapData結構的保留欄位中)。
當然,在實際的運用中,全部8種情況似乎是多了點,可根據情況進行適當合并取捨,以兼顧代碼的複雜度和執行效率。下面是我認為比較合理的精簡版ImageMixer函數:
// 映像合成。參數:32位目標資料,32位來源資料,不透明度(0 - 1.0)VOID ImageMixer(BitmapData *dest, CONST BitmapData *source, FLOAT alpha){INT alphaI = (INT)(alpha * 255);if (alphaI <= 0) return;if (alphaI > 255) alphaI = 255;if (alphaI == 255 && !HasAlphaFlag(source))Mixer1(dest, source, alphaI);// 拷貝合成else if (HasAlphaFlag(dest))Mixer6(dest, source, alphaI);// PARGB合成elseMixer4(dest, source, alphaI);// ARGB合成}//---------------------------------------------------------------------------
這個ImageMixer函數只保留了3個調用子函數,其中,Mixer6是完全的正常混合模式,即前面公式3的實現;Mixer4為對不含Alpha資訊靶心圖表的混合,即在公式4基礎上稍稍擴充了的情況;而Mixer1則為拷貝模式。
下面是採用BCB2010和GDI+調用ImageMixer函數的例子:
//---------------------------------------------------------------------------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, 0.75);UnlockBitmap(source, &src);UnlockBitmap(dest, &dst);g->DrawImage(dest, dest->GetWidth() << 1, 0);delete g;delete source;delete dest;}//---------------------------------------------------------------------------
下面是運行效果:
左邊是靶心圖表,中間是源圖,右邊是源圖按不透明度0.75進行的正常混合。
因水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:maozefa@hotmail.com
這裡可訪問《C++影像處理 -- 文章索引》。