Delphi图像处理 -- 色相/饱和度调整

阅读提示:

    《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图像处理 -- 文章索引》。

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值