• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Delphi图像处理 -- 图像旋转

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

图像旋转,是指按照给定的角度,计算出图像每个像素坐标点在新图像上的坐标位置,从而实现整个图像坐标变换的图像处理手段。其像素坐标的变换公式为:

(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; interface uses 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图像处理 -- 图像像素结构与图像数据转换》。

尽管我十分努力,但水平有限,错误在所难免,欢迎指正和指导。邮箱地址:

[email protected]

说明:本文代码于2010.5.20重新修订过,在旋转处理过程中增加了一个Alpha参数,可实现半透明形式的图像旋转。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
MATLAB编程实现方程选择的解 【例】发布时间:2022-07-18
下一篇:
Matlab随笔之求解线性方程发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap