閱讀提示:
《Delphi影像處理》系列以效率為側重點,一般代碼為PASCAL,核心代碼採用BASM。
《C++影像處理》系列以代碼清晰,可讀性為主,全部使用C++代碼。
儘可能保持二者內容一致,可相互對照。
本文代碼必須包括文章《Delphi影像處理 -- 資料類型及公用過程》中的ImageData.pas單元。
在影像處理過程中,映像的合成操作是使用頻率最高的,像顯示、映像拷貝、映像拼接以及的圖層拼合疊加等。
映像合成,其實也就是映像像素顏色的混合,在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種情況)來分別進行代碼實現,也可完善和補充上面的文字敘述:
unit main;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end;var Form1: TForm1;implementationuses Gdiplus, ImageData, Jpeg;{$R *.dfm}procedure _SetMixerMM;asm pxor mm7, mm7 mov eax, 1011h movd mm6, eax pshufw mm6, mm6, 0 mov eax, 8 movd mm5, eax pshufw mm5, mm5, 0end;// --> mm7 4 * word = 0// --> mm6 4 * word = 0x1101// --> mm5 4 * word = 4// --> eax source alpha// --> esi source pixel (ARGB)// --> edi dest pixel (ARGB)// <-- eax dest alpha !!!// <-- mm0 dest pixelprocedure _PARGBMixer(srcAlpha: Integer);asm push edx movd mm0, [esi] movd mm1, [edi] punpcklbw mm0, mm7 punpcklbw mm1, mm7 // dest.rgb = dest.rgb * dest.alpha / 255 movzx edx, [edi].TARGBQuad.Alpha pmullw mm1, qword ptr ArgbTab[edx*8] pmulhuw mm1, mm6 paddusw mm1, mm5 psrlw mm1, 4 // dest.rgb = (dest.rgb * 255 + (source.rgb - dest.rgb) * sourec.alpha) / 255 psubw mm0, mm1 pmullw mm0, qword ptr ArgbTab[eax*8] pmullw mm1, qword ptr ArgbTab[255*8] paddw mm0, mm1 pmulhuw mm0, mm6 paddusw mm0, mm5 psrlw mm0, 4 // dest.alpha += (source.alpha - (dest.alpha * source.alpha + 127) / 255) push eax add [esp], edx imul eax, edx add eax, 127 mul dword ptr DivTab[255*4] pop eax sub eax, edx // dest.rgb = dest.rgb * 255 / dest.alpha movq mm1, mm0 psllw mm0, 8 psubw mm0, mm1 pmulhuw mm0, qword ptr MMDivTab[eax*8] packuswb mm0, mm7 pop edxend;// --> mm7 4 * word = 0// --> eax source alpha// --> esi source pixel (ARGB)// --> edi dest pixel (ARGB)// <-- mm0 dest pixel (RGB)procedure _ARGBMixer(srcAlpha: Integer);asm movd mm0, [esi] movd mm1, [edi] punpcklbw mm0, mm7 punpcklbw mm1, mm7 psubw mm0, mm1 pmullw mm0, qword ptr ArgbTab[eax*8] psllw mm1, 8 paddw mm0, mm1 psrlw mm0, 8 packuswb mm0, mm0end;procedure _DoMixer(var Dest: TImageData; const Source: TImageData; Alpha: Integer);var dstOffset, srcOffset:Integer; alphaI: Integer; dst: PImageData;asm push esi push edi push ebx mov dst, eax mov alphaI, ecx shr ecx, 8 movzx ebx, [eax].TImageData.AlphaFlag shl ebx, 1 or ecx, ebx movzx ebx, [edx].TImageData.AlphaFlag shl ebx, 2 or ecx, ebx push ecx call _SetCopyRegs mov srcOffset, eax pop eax jmp dword ptr [@@jmpTable+eax*4]@@jmpTable: dd @@mixer0, @@mixer1, @@mixer2, @@mixer3, @@mixer4, @@mixer5, @@mixer6, @@mixer7// src = 0, dst = 0, alpha = 0@@mixer0: mov eax, alphaI movq mm2, qword ptr ArgbTab[eax*8] mov eax, srcOffset pxor mm7, mm7@@yLoop0: push ecx@@xLoop0: movd mm0, [esi] movd mm1, [edi] punpcklbw mm0, mm7 punpcklbw mm1, mm7 psubw mm0, mm1 pmullw mm0, mm2 psllw mm1, 8 paddw mm0, mm1 psrlw mm0, 8 packuswb mm0, mm0 movd [edi], mm0 mov [edi].TARGBQuad.Alpha, 255 add esi, 4 add edi, 4 loop @@xLoop0 add esi, eax add edi, ebx pop ecx dec edx jnz @@yLoop0 jmp @@End// src = 0, dst = 0, alpha = 1@@mixer1:// src = 0, dst = 1, alpha = 1@@mixer3: mov eax, srcOffset@@yLoop1: push ecx rep movsd pop ecx add esi, eax add edi, ebx dec edx jnz @@yLoop1 jmp @@End mov eax, dst mov [eax].TImageData.AlphaFlag, False jmp @@Exit// src = 0, dst = 1, alpha = 0@@mixer2: call _SetMixerMM mov eax, alphaI imul eax, 255 shr eax, 8@@yLoop2: push ecx@@xLoop2: push eax call _PARGBMixer movd [edi], mm0 mov [edi].TARGBQuad.Alpha, al pop eax add esi, 4 add edi, 4 loop @@xLoop2 add esi, srcOffset add edi, ebx pop ecx dec edx jnz @@yLoop2 jmp @@End// src = 1, dst = 0, alpha = 0@@mixer4: mov dstOffset, ebx mov ebx, alphaI pxor mm7, mm7@@yLoop4: push ecx@@xLoop4: movzx eax, [esi].TARGBQuad.Alpha imul eax, ebx shr eax, 8 jz @@Next4 call _ARGBMixer movd [edi], mm0 mov [edi].TARGBQuad.Alpha, 255@@Next4: add esi, 4 add edi, 4 loop @@xLoop4 add esi, srcOffset add edi, dstOffset pop ecx dec edx jnz @@yLoop4 jmp @@End// src = 1, dst = 0, alpha = 1@@mixer5: pxor mm7, mm7@@yLoop5: push ecx@@xLoop5: movzx eax, [esi].TARGBQuad.Alpha call _ARGBMixer movd [edi], mm0 mov [edi].TARGBQuad.Alpha, 255 add esi, 4 add edi, 4 loop @@xLoop5 add esi, srcOffset add edi, ebx pop ecx dec edx jnz @@yLoop5 jmp @@End// src = 1, dst = 1, alpha = 0@@mixer6: mov dstOffset, ebx mov ebx, alphaI call _SetMixerMM@@yLoop6: push ecx@@xLoop6: movzx eax, [esi].TARGBQuad.Alpha imul eax, ebx shr eax, 8 jz @@Next6 call _PARGBMixer movd [edi], mm0 mov [edi].TARGBQuad.Alpha, al@@Next6: add esi, 4 add edi, 4 loop @@xLoop6 add esi, srcOffset add edi, dstOffset pop ecx dec edx jnz @@yLoop6 jmp @@End// src = 1, dst = 1, alpha = 1@@mixer7: call _SetMixerMM@@yLoop7: push ecx@@xLoop7: movzx eax, [esi].TARGBQuad.Alpha call _PARGBMixer movd [edi], mm0 mov [edi].TARGBQuad.Alpha, al add esi, 4 add edi, 4 loop @@xLoop7 add esi, srcOffset add edi, ebx pop ecx dec edx jnz @@yLoop7@@End: emms@@Exit: pop ebx pop edi pop esiend;procedure ImageMixer(var Dest: TImageData; const Source: TImageData; Alpha: Single);var alphaI: Integer;begin alphaI := Round(Alpha * 256); if alphaI < 0 then Exit; if alphaI > 256 then alphaI := 256; _DoMixer(Dest, Source, alphaI);end;procedure TForm1.Button1Click(Sender: TObject);var source, dest: TGpBitmap; g: TGpGraphics; src, dst: TImageData;begin source := TGpBitmap.Create('..\..\media\Apple.png'); dest := TGpBitmap.Create('..\..\media\xmas_011.png'); g := TGpGraphics.Create(Canvas.Handle); g.DrawImage(dest, 0, 0); g.DrawImage(source, dest.Width, 0); src := LockGpBitmap(source); dst := LockGpBitmap(dest); ImageMixer(dst, src, 0.75); UnlockGpBitmap(dest, dst); UnlockGpBitmap(source, src); g.DrawImage(dest, dst.Width + src.Width, 0); g.Free; dest.Free; source.Free;end;procedure TForm1.Button2Click(Sender: TObject);var source: TGpBitmap; dest: TBitmap; tmp: TJpegImage; g: TGpGraphics; src, dst: TImageData;begin dest := TBitmap.Create; tmp := TJpegImage.Create; tmp.LoadFromFile('..\..\media\IMG_9440_mf.jpg'); dest.Assign(tmp); tmp.Free; source := TGpBitmap.Create('..\..\media\xmas_011.png'); g := TGpGraphics.Create(Canvas.Handle); Canvas.Draw(0, 0, dest); g.DrawImage(source, dest.Width, 0); dst := GetBitmapData(dest); src := LockGpBitmap(source); ImageMixer(dst, src, 1); UnlockGpBitmap(source, src); Canvas.Draw(dst.Width + src.Width, 0, dest); g.Free; dest.Free; source.Free;end;end.
上面是一個完整Delphi程式,在_DoMixer過程中,定義了7個映像合成子過程,以滿足全部8種排列組合的映像合并。除了@@Mixer0和@@Mixer1子過程外,其餘子過程分別調用了 _ARGBMixer和_PARGBMixer過程,其實@@Mixer0中的處理代碼與 _ARGBMixer過程基本是相同的,也就是說,7個子過程分別使用了三種處理方式:
一是@@Mixer1的完全拷貝方式;
二是靶心圖表不含Alpha資訊,將源圖按像素的Alpha資訊或者給定的不透明度混合到靶心圖表的_ARGBMixer過程,該過程使用的是前面公式1或公式4的簡化公式。
三是靶心圖表為帶Alpha資訊的處理形式 _PARGBMixer,該過程使用前面的公式3。
如果嫌7個子過程太多,也可簡化代碼,只保留滿足上述3種情況的子過程,即@@Mixer1、@@Mixer4和@@Mixer6,對處理效率影響不大。
上面程式中包含2個例子,前一個是用GDI+位元影像開啟2張png圖片後進行處理,這個處理調用的是@@Mixer6。運行效果如下:
後一個例子是對GDI+位元影像和VCL位元影像的混合處理,其中VCL位元影像是JPEG格式檔案,這是沒有Alpha資訊的,這個例子運行效果如下:
《Delphi影像處理》系列使用GDI+單元和說明見文章《GDI+ for VCL基礎 -- GDI+ 與 VCL》。
因水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:maozefa@hotmail.com
這裡可訪問《Delphi影像處理 -- 文章索引》。