阅读提示:
《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》。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《Delphi图像处理 -- 文章索引》。