六狼论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博账号登陆

只需一步,快速开始

搜索
查看: 58|回复: 0

Delphi图像处理 -

[复制链接]

升级  3.95%

345

主题

345

主题

345

主题

探花

Rank: 6Rank: 6

积分
1079
 楼主| 发表于 2013-2-7 03:49:05 | 显示全部楼层 |阅读模式
图像旋转,是指按照给定的角度,计算出图像每个像素坐标点在新图像上的坐标位置,从而实现整个图像坐标变换的图像处理手段。其像素坐标的变换公式为:
(1)、x' = x * cos(angle) + y *sin(andle)
y' = x * -sin(angle)+y * cos(andle)
公式中,x',y'为变换后的像素坐标点,x,y为变换前像素坐标点,angle为旋转角度。
但是,按照上面的公式旋转图像,会出现多个原图像像素点对应新图像同一个点的问题,这不仅会多消耗图像旋转处理的时间,而且也会使旋转后的图像产生不必要的失真,如果是32位带Alpha通道像素格式的图像,更会因此造成图像的混乱,所以在实际的图像旋转处理代码中,是以目标图(旋转后的图像)的像素坐标点,逐点反推原图像素的位置,这样,目标图的各个像素点既不会重复,也不会遗漏。其计算公式为:
(2)、x = x' * cos(angle) - y' * sin(angle)
y = x' * sin(angle) + y' * cos(angle)
下面是Delphi图像旋转处理的代码:
过程定义:  // 获取Width * Height图像旋转Angle时,完全包含图像所需尺寸  function GetRotateSize(Width, Height: Integer; Angle: Single): TSize;  // 旋转图像,OffsetX和OffsetY分别为图像偏移量,为0时Source与Dest左上边对齐,  // Angle顺时针旋转角度,Alpha不透明度,IpMode插值方式  procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;    const Source: TImageData; Angle: Single;    Alpha: Single = 1.0; IpMode: TInterpolateMode = imDefault); overload;  // TGraphic对象旋转到Dest  procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;    const Source: TGraphic; Angle: Single;    Alpha: Single = 1.0; IpMode: TInterpolateMode = imDefault); overload;  // TGpBitmap对象旋转到Dest  procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;    const Source: TGpBitmap; Angle: Single;    Alpha: Single = 1.0; IpMode: TInterpolateMode = imDefault); overload;代码实现:type  TPointF = record    X: Single;    Y: Single;  end;  TRectF = record    X: Single;    Y: Single;    Width: Single;    Height: Single;  end;function GetTransformSize(m11, m12, m21, m22, x, y, Width, Height: Single): TRectF;  function GetTransPoint(x, y: Single): TPointF;  begin    Result.X := m11 * x + m21 * y;    Result.Y := m12 * x + m22 * y;  end;var  pf: array[0..3] of TPointF;  I: Integer;begin  // 分别计算四个角的相对坐标  pf[0] := GetTransPoint(x, y);  pf[1] := GetTransPoint(Width + x, y);  pf[2] := GetTransPoint(x, Height + y);  pf[3] := GetTransPoint(Width + x, Height + y);  // 取得左上角和右下角的坐标点  Result.X := pf[0].X;  Result.Y := pf[0].Y;  Result.Width := 0.0;  Result.Height := 0.0;  for I := 0 to 3 do  begin    if Result.X > pf[I].X then Result.X := pf[I].X    else if Result.Width < pf[I].X then Result.Width := pf[I].X;    if Result.Y > pf[I].Y then Result.Y := pf[I].Y    else if Result.Height < pf[I].Y then Result.Height := pf[I].Y;  end;  Result.Width := Result.Width - Result.X;  Result.Height := Result.Height - Result.Y;end;function GetRotateSize(Width, Height: Integer; Angle: Single): TSize;var  Rect: TRectF;  cosV, sinV: Single;begin  Angle := Angle * PI / 180.0;  cosV := Cos(Angle);  sinV := Sin(Angle);  Rect := GetTransformSize(cosV, -sinV, sinV, cosV, 0, 0, Width, Height);  Result.cx := GetInfinity(Rect.Width);  Result.cy := GetInfinity(Rect.Height);end;procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;  const Source: TImageData; Angle, Alpha: Single; IpMode: TInterpolateMode);var  dRect, sRect: TRectF;  cosV, sinV: Single;  x0, y0: Single;  CosI, SinI: Integer;  tmp, src, dst: TImageData;  up, xDown, yDown, BorderRadius: Integer;  GetColor: TInterpolateProc;  x, y, Width, Height: Integer;  dstOffset: Integer;  BorderMask: LongWord;  alphaI: Integer;  procedure DoRotate;  asm    lea       eax, dst    lea       edx, src    push      edx    call      SetScaleRegs32    mov       Height, edx    mov       Width, ecx    mov       dstOffset, ebx    mov       ecx, y    mov       edx, x    pop       ebx    pxor      mm7, mm7    mov       eax, 04040404h    movd      mm6, eax    punpcklbw mm6, mm7    cmp       alphaI, 256    jb        @@yLoop    mov       alphaI, 256@@yLoop:    push      ecx              // for (i = dst.Width; i > 0; i --)    push      edx              // {    push      Width@@xLoop:    push      ecx              //   for (j = dst.Width; j > 0; j --)    push      edx              //   {    cmp       ecx, up          //     if (y >= up && y < yDown)    jl        @@xNext          //     {    cmp       ecx, yDown    jge       @@xNext    cmp       edx, up          //       if (x >= up && x < xDown)    jl        @@xNext          //       {    cmp       edx, xDown    jge       @@xNext    mov       esi, ecx         //         y0 = y / 256    mov       eax, edx         //         x0 = x / 256    sar       esi, 8    sar       eax, 8    imul      esi, [ebx].TimageData.Stride    shl       eax, 2           //         esi = src.Scan0 + x0 * 4 +    add       esi, eax         //           y0 * src.Stride    add       esi, [ebx].TimageData.Scan0    call      GetColor         //         GetColor(src, x, y, esi)    movd      eax, mm0    shr       eax, 24    imul      eax, alphaI    shr       eax, 8    movd      mm1, [edi]    punpcklbw mm0, mm7    punpcklbw mm1, mm7    psubw     mm0, mm1    pmullw    mm0, qword ptr ArgbTable[eax*8]    psllw     mm1, 8    paddw     mm0, mm1    psrlw     mm0, 8    packuswb  mm0, mm7    movd      [edi], mm0       //         *edi = AlphaBlend(*edi, mm0)@@xNext:    pop       edx              //       }    pop       ecx    add       edi, 4           //       edi += 4    add       edx, CosI        //       x += CosI    add       ecx, SinI        //       y += SinI    dec       Width    jnz       @@xLoop          //     }    pop       Width    pop       edx    pop       ecx    add       edi, dstOffset   //   edi += dst.OffsetX    add       ecx, CosI        //   y += CosI    sub       edx, SinI        //   x += -SinI    dec       Height    jnz       @@yLoop          // }    emms  end;begin  if ImageEmpty(Source) or ImageEmpty(Dest) then Exit;  Angle := 360 - Angle;  if Round(Angle) mod 90 = 0 then    BorderMask := $FFFFFFFF  else    BorderMask := $00FFFFFF;  Angle := Angle * PI / 180.0;  cosV := Cos(Angle);  sinV := Sin(Angle);  dRect := GetTransformSize(cosV, -sinV, sinV, cosV, 0, 0, Source.Width, Source.Height);  dst := GetSubImageData(Dest, 0, 0, GetInfinity(dRect.Width) + OffsetX,    GetInfinity(dRect.Height) + OffsetY);  if dst.Scan0 = nil then Exit;  x0 := (Source.Width * cosV + Source.Height * sinV - dRect.Width) / 2 - OffsetX;  y0 := (Source.Width * -sinV + Source.Height * cosV - dRect.Height) / 2 - OffsetY;  sRect := GetTransformSize(cosV, sinV, -sinV, cosV, x0, y0, dst.Width, dst.Height);  BorderRadius := GetInterpolateProc(IpMode, GetColor);  Width := GetInfinity(sRect.Width);  Height := GetInfinity(sRect.Height);  src := GetSubData(Source, Trunc(sRect.X), Trunc(sRect.Y), Width, Height);  tmp := GetExpandData(src, BorderRadius, BorderMask);  try    src := GetSubData(tmp, BorderRadius, BorderRadius,      tmp.Width - BorderRadius shl 1, tmp.Height - BorderRadius shl 1);    if sRect.X < 0.0 then sRect.X := 0.0;    if sRect.Y < 0.0 then sRect.Y := 0.0;    up := (BorderRadius - 1) shl 7;    if cosV < 0 then Inc(Up);    x := Trunc((x0 * cosV + y0 * -sinV - sRect.X) * 256 - up);    y := Trunc((y0 * cosV + x0 * sinV - sRect.Y) * 256 - up);    CosI := Round(cosV * 256);    SinI := Round(sinV * 256);    up := 256 - (BorderRadius shl 8);    xDown := (src.Width + BorderRadius) shl 8 + up;    yDown := (src.Height + BorderRadius) shl 8 + up;    alphaI := Round(Alpha * 256);    DoRotate;  finally    FreeImageData(tmp);  end;end;procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;  const Source: TGraphic; Angle, Alpha: Single; IpMode: TInterpolateMode);var  src: TImageData;begin  src := GetImageData(Source);  ImageRotate(Dest, OffsetX, OffsetY, src, Angle, Alpha, IpMode);  FreeImageData(src);end;procedure ImageRotate(var Dest: TImageData; OffsetX, OffsetY: Integer;  const Source: TGpBitmap; Angle, Alpha: Single; IpMode: TInterpolateMode);var  src: TImageData;begin  src := GetImageData(source);  ImageRotate(Dest, OffsetX, OffsetY, src, Angle, Alpha, IpMode);  FreeImageData(src);end;
同图像缩放处理一样,图像旋转的质量,也取决图像像素的插值方式,图像旋转时的像素插值也可选择临近插值、线性插值和双立方插值等方式,缺省时线性插值方式,代码中用到的内部像素插值过程见《Delphi图像处理 -- 图像缩放》。
本文的图像旋转处理是采用放大256倍后的定点整数运算,其运算速度是浮点运算不可比拟的,同时在像素处理前按照给定的旋转角度计算好了x,y坐标点的增量,因此在具体的逐点像素坐标变换时,不需要按照前面的像素坐标变换公式进行复杂的运算,而只是直接在放大256倍后的像素坐标点上进行加减,就可达到像素坐标变换的目的,这更加加快的图像的旋转处理过程。因此,本文的图像旋转过程处理速度还是较快的。
图像旋转处理最麻烦的还是边界像素的处理。本文的图像旋处理过程和图像缩放、卷积处理过程一样,使用ImageGetExpandData过程扩展了图像边界,但和图像旋转、卷积处理过程不同的是,图像旋转后,其边界会因为角度的变化,带来明显的边缘锯齿,为了解决这个问题,本过程将图像扩展的边界部分的Alpha通道置为了0,这样,在像素插值过程中,扩展边界像素的R、G、B各分量就较融洽地融合在目标图边界像素之间的背景色中,也就相当于进行了锯齿消除。
下面是个在TImage对象上进行图像任意角度旋转的例子。
unit Main;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,  Dialogs, StdCtrls, jpeg, ExtCtrls, Menus, ImageUtils;type  TMainForm = class(TForm)    Image1: TImage;    Label1: TLabel;    Edit1: TEdit;    RadioButton1: TRadioButton;    RadioButton2: TRadioButton;    RadioButton3: TRadioButton;    Button1: TButton;    procedure Edit1KeyPress(Sender: TObject; var Key: Char);    procedure FormCreate(Sender: TObject);    procedure FormDestroy(Sender: TObject);    procedure RadioButton1Click(Sender: TObject);    procedure Button1Click(Sender: TObject);    procedure Edit1Change(Sender: TObject);  private    { Private declarations }    FData: TImageData;    FMode: TInterpolateMode;    FChanged: Boolean;  public    { Public declarations }  end;var  MainForm: TMainForm;implementation{$R *.dfm}procedure TMainForm.FormCreate(Sender: TObject);begin  FData := GetImageData(Image1.Picture.Graphic);  self.DoubleBuffered := True;end;procedure TMainForm.FormDestroy(Sender: TObject);begin  FreeImageData(FData);end;procedure TMainForm.Edit1KeyPress(Sender: TObject; var Key: Char);begin  if not (Key in ['0'..'9']) then    Key := #0;end;procedure TMainForm.Edit1Change(Sender: TObject);begin  FChanged := True;end;procedure TMainForm.RadioButton1Click(Sender: TObject);var  M: TInterpolateMode;begin  M := TInterpolateMode(TRadioButton(Sender).Tag);  if FMode <> M then  begin    FMode := M;    FChanged := True;  end;end;procedure TMainForm.Button1Click(Sender: TObject);var  Angle: Single;  Size: TSize;begin  if not FChanged then Exit;  if Edit1.Text = '' then    Angle := 0  else    Angle := StrToFloat(Edit1.Text);  Size := GetRotateSize(FData.Width, FData.Height, Angle);  Image1.Picture.Bitmap := nil;  Image1.Picture.Bitmap.Width := Size.cx;  Image1.Picture.Bitmap.Height := Size.cy;  DrawImage(Image1.Canvas, 0, 0, FData, Angle, 1.0, FMode);end;end.
运行界面图如下:


文章中使用GDI+版本下载地址和说明见《GDI+ for VCL基础 -- GDI+ 与 VCL》。
文章中所用数据类型及一些过程见《Delphi图像处理 -- 数据类型及内部过程》和《Delphi图像处理 -- 图像像素结构与图像数据转换》。
尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:
maozefa@hotmail.com
说明:本文代码于2010.5.20重新修订过,在旋转处理过程中增加了一个Alpha参数,可实现半透明形式的图像旋转。
您需要登录后才可以回帖 登录 | 立即注册 新浪微博账号登陆

本版积分规则

快速回复 返回顶部 返回列表