閱讀提示:
《Delphi影像處理》系列以效率為側重點,一般代碼為PASCAL,核心代碼採用BASM。
《C++影像處理》系列以代碼清晰,可讀性為主,全部使用C++代碼。
儘可能保持二者內容一致,可相互對照。
本文代碼必須包括文章《Delphi影像處理 -- 資料類型及公用過程》中的ImageData.pas單元。
本文是基於《GDI+在Delphi程式的應用 – Photoshop色相/飽和度/明度功能》一文的BASM實用性過程,有關實現原理可參見《GDI+ 在Delphi程式的應用 -- 映像飽和度調整》和《GDI+ 在Delphi程式的應用 -- 仿Photoshop的明度調整》,純PAS實現代碼和測試例子代碼見《GDI+在Delphi程式的應用 – Photoshop色相/飽和度/明度功能》。
procedure GetBrightTable(Bright: Integer; var Table: TGrayTable);asm push ebx cmp eax, -255 jge @@1 mov eax, -255 jmp @@2@@1: cmp eax, 255 jle @@2 mov eax, 255@@2: push eax mov ebx, 255 fild dword ptr[esp] fwait mov [esp], ebx fidiv dword ptr[esp]// Bright / 255 fwait xor ecx, ecx test eax, eax jg @@Loop xor ebx, ebx // mask = Bright > 0? 255 : 0@@Loop: mov [esp], ecx xor [esp], ebx fild dword ptr[esp] fmul st(0), st(1) fistp dword ptr[esp] fwait mov eax, [esp] add eax, ecx mov [edx], al // Table[i] = (i ^ mask) * Bright / 255 inc edx inc ecx cmp ecx, 256 jb @@Loop ffree st pop eax pop ebxend;procedure HSBSetBright(var Data: TImageData; const Table: TGrayTable);asm push ebp push esi push edi push ebx mov esi, edx call _SetDataRegs mov ebp, edx@@yLoop: push ecx@@xLoop: movzx eax, [edi].TARGBQuad.Blue movzx edx, [edi].TARGBQuad.Green mov al, [esi+eax] mov dl, [esi+edx] mov [edi].TARGBQuad.Blue, al mov [edi].TARGBQuad.Green, dl movzx eax, [edi].TARGBQuad.Red mov al, [esi+eax] mov [edi].TARGBQuad.Red, al add edi, 4 loop @@xLoop pop ecx add edi, ebx dec ebp jnz @@yLoop pop ebx pop edi pop esi pop ebpend;procedure HSBSetHueAndSaturation(var Data: TImageData; Hv, Sv: Integer);const _fcd5: Single = 0.5; _fc1: Single = 1.0; _fc2: Single = 2.0; _fc4: Single = 4.0; _fc6: Single = 6.0; _fc255: Single = 255.0; _fc510: Single = 510.0;var fHv, fSv: Single; width, height, datOffset: Integer;asm push esi push edi push ebx push edx push ecx call _SetDataRegs mov width, ecx mov height, edx mov datOffset, ebx pop ebx // Sv pop esi // Hv pxor xmm7, xmm7 mov eax, 60 cvtsi2ss xmm6, esi cvtsi2ss xmm0, eax divss xmm6, xmm0 movss fHv, xmm6 // fSv = Hv / 60 cvtsi2ss xmm6, ebx divss xmm6, _fc255 movss fSv, xmm6 // xmm6 = fSv = Sv / 255 test ebx, ebx jle @@1 mov eax, 255 xor eax, ebx cvtsi2ss xmm0, eax movss xmm6, _fc255 divss xmm6, xmm0 // if (Sv > 0) subss xmm6, _fc1 // xmm6 = 255 / (255 - Sv) - 1@@1: pshufd xmm6, xmm6, 0@@yLoop: push width@@xLoop: movzx ecx, [edi].TARGBQuad.Blue movzx edx, [edi].TARGBQuad.Green movzx eax, [edi].TARGBQuad.Red cmp ecx, edx // ecx = rgbMax jge @@3 // edx = rgbMin xchg ecx, edx@@3: cmp ecx, eax jge @@4 xchg ecx, eax@@4: cmp edx, eax cmova edx, eax mov eax, ecx sub eax, edx // delta = rgbMax - rgbmin jz @@next // if (delta == 0) continue@@5: movd xmm0, edx // rgbMin pinsrw xmm0, ecx, 2 // rgbMax add ecx, edx // ecx = rgbMar + rgbMin cvtsi2ss xmm3, ecx divss xmm3, _fc510 // xmm3 = L = ecx / 510 comiss xmm3, _fcd5 jb @@6 neg ecx add ecx, 510 // if (L >= 0.5) ecx = 510 - ecx@@6: cvtsi2ss xmm2, eax cvtsi2ss xmm4, ecx divss xmm2, xmm4 // xmm2 = S = delta / ecx test esi, esi jnz @@7 // if (Hv == 0) goto @@20 movd xmm0, [edi] punpcklbw xmm0, xmm7 jmp @@20@@7: cvtsi2ss xmm4, eax // delta movss xmm5, fHv // add = fHv pextrw eax, xmm0, 2 // rgbMax cmp al, [edi].TARGBQuad.Red jne @@8 // if (R == rgbMax) eax = G - B movzx eax, [edi].TARGBQuad.Green movzx edx, [edi].TARGBQuad.Blue jmp @@10@@8: cmp al, [edi].TARGBQuad.Green jne @@9 movzx eax, [edi].TARGBQuad.Blue movzx edx, [edi].TARGBQuad.Red addss xmm5, _fc2 // if (G == rgbMax) eax = B - R; add += 2 jmp @@10@@9: movzx eax, [edi].TARGBQuad.Red movzx edx, [edi].TARGBQuad.Green addss xmm5, _fc4 // if (B == rgbMax) eax = R - G; add += 4@@10: sub eax, edx cvtsi2ss xmm1, eax divss xmm1, xmm4 addss xmm1, xmm5 // H = eax / delta + add comiss xmm1, xmm7 jae @@11 addss xmm1, _fc6 // if (H < 0) H += 6 jmp @@12@@11: comiss xmm1, _fc6 jb @@12 subss xmm1, _fc6 // else if (H >= 6) H -= 6@@12: cvtss2si edx, xmm1 // index = Round(H) cvtsi2ss xmm4, edx subss xmm1, xmm4 // extra = H - index comiss xmm1, xmm7 // if (extra < 0) // 如果index發生五入 jae @@13 // { dec edx // index -- addss xmm1, _fc1 // extra ++@@13: // } test edx, 1 jz @@14 movaps xmm4, xmm1 movss xmm1, _fc1 subss xmm1, xmm4 // if (index & 1) extra = 1 - extra@@14: movaps xmm4, xmm1 movss xmm5, _fc1 subss xmm4, _fcd5 subss xmm5, xmm2 mulss xmm4, xmm5 subss xmm1, xmm4 // extra = extra - (extra - 0.5) * (1.0 - S); movaps xmm4, xmm1 // extra0 = extra movaps xmm5, xmm3 subss xmm5, _fcd5 // L0 = L - 0.5 comiss xmm5, xmm7 jb @@15 movss xmm4, _fc1 subss xmm4, xmm1 // if (L0 >= 0) extra0 = 1 - extra@@15: mulss xmm4, xmm5 mulss xmm4, _fc2 addss xmm1, xmm4 mulss xmm1, _fc255 cvtss2si eax, xmm1 // rgbMid = (extra + extra0 * L0 * 2) * 255 pinsrw xmm0, eax, 1 // xmm0 = 0000 MAX MEDIAN MIN jmp @@jmpTable[edx*4].Pointer@@jmpTable: dd offset @@H60 dd offset @@H120 dd offset @@H180 dd offset @@H240 dd offset @@H300 dd offset @@H360 dd offset @@18// 當H=6.0時,SSE判斷誤差導致index=6,實際應為0@@H120: // 60 - 119 pshuflw xmm0, xmm0, 216 jmp @@18@@H180: // 120 - 179 pshuflw xmm0, xmm0, 201 jmp @@18@@H240: // 180 - 239 pshuflw xmm0, xmm0, 198 jmp @@18@@H300: // 240 - 299 pshuflw xmm0, xmm0, 210 jmp @@18@@H360: // 300 - 359 pshuflw xmm0, xmm0, 225@@H60: // 0 - 59@@18: test ebx, ebx // if (Sv == 0) continue jz @@25@@20: punpcklwd xmm0, xmm7 cvtdq2ps xmm0, xmm0 movaps xmm1, xmm0 mulss xmm3, _fc255 pshufd xmm3, xmm3, 0 subps xmm0, xmm3 // rgb0 = rgb - L test ebx, ebx jle @@21 movaps xmm3, xmm2 // if (Sv > 0) addss xmm3, fSv // { comiss xmm3, _fc1 jb @@21 rcpss xmm2, xmm2 // if ((fSv + S) >= 1) subss xmm2, _fc1 // rgb0 = rgb0 * (1 / S - 1) pshufd xmm2, xmm2, 0 // else mulps xmm0, xmm2 // rgb0 = rgb0 * (1 / (1 - fSv) - 1) jmp @@22 // }@@21: // else mulps xmm0, xmm6 // rgb0 = rgb0 * fSv@@22: addps xmm0, xmm1 // rgb += rgb0 cvtps2dq xmm0, xmm0 packssdw xmm0, xmm7@@25: packuswb xmm0, xmm7 mov al, [edi].TARGBQuad.Alpha movd [edi], xmm0 mov [edi].TARGBQuad.Alpha, al@@next: add edi, 4 dec width jnz @@xLoop add edi, datOffset pop width dec height jnz @@yLoop pop ebx pop edi pop esiend;procedure ImageHSBAdjustment(var Data: TImageData; hValue, sValue, bValue: Integer);var BrightTab: TGrayTable;begin if hValue > 180 then hValue := 180 else if hValue < -180 then hValue := -180; if sValue > 255 then sValue := 255 else if sValue < -255 then sValue := -255; if bValue <> 0 then GetBrightTable(bValue, BrightTab); if (sValue > 0) and (bValue <> 0) then HSBSetBright(Data, BrightTab); if (hValue <> 0) or (sValue <> 0) then begin HSBSetHueAndSaturation(Data, hValue, sValue); end; if (sValue <= 0) and (bValue <> 0) then HSBSetBright(Data, BrightTab);end;
這次重新修訂,將明度部分獨立出來,採用一個256元素大小的明度表直接進行替換,大大加快了速度;色相和飽和度調整部分也進行了修改,全部採用SSE代碼,使計算精確度得以提高。
下面是個簡單的HSB示範程式:
unit main;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Gdiplus, ImageData, StdCtrls, ComCtrls, ExtCtrls;type TForm1 = class(TForm) Label1: TLabel; Label2: TLabel; Label3: TLabel; PaintBox1: TPaintBox; Button1: TButton; HBar: TTrackBar; SBar: TTrackBar; BBar: TTrackBar; HEdit: TEdit; SEdit: TEdit; BEdit: TEdit; Button2: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure PaintBox1Paint(Sender: TObject); procedure HBarChange(Sender: TObject); procedure SBarChange(Sender: TObject); procedure BBarChange(Sender: TObject); procedure HEditChange(Sender: TObject); procedure HEditKeyPress(Sender: TObject; var Key: Char); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } Bmp: TGpBitmap; tmpBmp: TGpBitmap; r: TGpRect; Lock: Boolean; public { Public declarations } end;var Form1: TForm1;implementation{$R *.dfm}procedure TForm1.BBarChange(Sender: TObject);begin if not Lock then BEdit.Text := IntToStr(BBar.Position);end;procedure TForm1.Button1Click(Sender: TObject);begin HBar.Position := 0; SBar.Position := 0; BBar.Position := 0;end;procedure TForm1.FormCreate(Sender: TObject);begin Bmp := TGpBitmap.Create('..\..\media\100_0349.jpg'); r := GpRect(0, 0, Bmp.Width, Bmp.Height); tmpBmp := Bmp.Clone(r, pf32bppARGB);end;procedure TForm1.FormDestroy(Sender: TObject);begin tmpBmp.Free; Bmp.Free;end;procedure TForm1.HBarChange(Sender: TObject);begin if not Lock then HEdit.Text := IntToStr(HBar.Position);end;procedure TForm1.HEditChange(Sender: TObject);var Data: TImageData;begin Lock := True; with TEdit(Sender) do begin if Length(Text) = 0 then Text := '0'; case Tag of 0: HBar.Position := StrToInt(Text); 1: SBar.Position := StrToInt(Text); 2: BBar.Position := StrToInt(Text); end; Lock := False; tmpBmp.Free; tmpBmp := Bmp.Clone(r, pf32bppARGB); if (HBar.Position <> 0) or (SBar.Position <> 0) or (BBar.Position <> 0) then begin Data := LockGpBitmap(tmpBmp); ImageHSBAdjustment(Data, HBar.Position, Round(SBar.Position * 255.0 / 100), Round(BBar.Position * 255.0 / 100)); UnlockGpBitmap(tmpBmp, Data); end; PaintBox1Paint(nil); end;end;procedure TForm1.HEditKeyPress(Sender: TObject; var Key: Char);begin if (Key >= #32) and not (Key in ['0'..'9']) then Key := #0;end;procedure TForm1.PaintBox1Paint(Sender: TObject);var g: TGpGraphics;begin g := TGpGraphics.Create(PaintBox1.Canvas.Handle); try g.DrawImage(tmpBmp, r); g.TranslateTransform(0, r.Height); g.DrawImage(Bmp, r); finally g.Free; end;end;procedure TForm1.SBarChange(Sender: TObject);begin if not Lock then SEdit.Text := IntToStr(SBar.Position);end;end.
運行介面如下:
《Delphi影像處理》系列使用GDI+單元和說明見文章《GDI+ for VCL基礎 -- GDI+ 與 VCL》。
因水平有限,錯誤在所難免,歡迎指正和指導。郵箱地址:maozefa@hotmail.com
這裡可訪問《Delphi影像處理 -- 文章索引》。