阅读提示:
《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 ebx end; 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 ebp end; 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 esi end; 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; interface uses 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》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《Delphi图像处理 -- 文章索引》。