怎么把一个图像旋转任意的角度?(50分)

  • 主题发起人 主题发起人 snakeqi
  • 开始时间 开始时间
S

snakeqi

Unregistered / Unconfirmed
GUEST, unregistred user!
怎么把一个图像旋转任意的角度?
 
首先需要弄明白的是:图形的旋转和图象的旋转是完全不同的两个概念!
按照你的意思,你应该是对图象进行任意角度的旋转了;
记得在旋转图象后,图象的大小会发生变化,你需要考虑这一点。


下面是围绕图象中心点进行任意角度的旋转,^_^

设定Image1.AutoSize :=True;

procedure GraphicRotate(var Src, Dst :TBitmap; cx, cy :Integer; Angle :Extended);
type
TFColor =record
b,g,r :Byte;
end;
var
Top,Bottom,Left,Right,eww,nsw,fx,fy,wx,wy :Extended;
cAngle,sAngle :Double;
xDiff,yDiff,ifx,ify,px,py,ix,iy,x,y,LL :Integer;
nw,ne,sw,se :TFColor;
P1,P2,P3 :PByteArray;
begin
Angle :=Angle;
Angle :=-Angle*Pi/180;
sAngle :=Sin(Angle);
cAngle :=Cos(Angle);
xDiff :=(Dst.Width-Src.Width)div 2;
yDiff :=(Dst.Height-Src.Height)div 2;
LL :=Dst.Height;
for y :=0 to Dst.Height-1 do
begin
P3 :=Dst.scanline[y];
py :=2*(y-cy)+1;
for x :=0 to Dst.Width-1 do
begin
px :=2*(x-cx)+1;
fx :=(((px*cAngle-py*sAngle)-1)/ 2+cx)-xDiff;
fy :=(((px*sAngle+py*cAngle)-1)/ 2+cy)-yDiff;
ifx :=Round(fx);
ify :=Round(fy);
if(ifx>-1) and (ifx<Src.Width)and (ify>-1) and (ify<Src.Height) then
begin
eww :=fx-ifx;
nsw :=fy-ify;
iy :=TrimInt(ify+1,0,Src.Height-1);
ix :=TrimInt(ifx+1,0,Src.Width-1);
P1 :=Src.scanline[ify];
P2 :=Src.scanline[iy];
nw.r :=P1[ifx*3];
nw.g :=P1[ifx*3+1];
nw.b :=P1[ifx*3+2];
ne.r :=P1[ix*3];
ne.g :=P1[ix*3+1];
ne.b :=P1[ix*3+2];
sw.r :=P2[ifx*3];
sw.g :=P2[ifx*3+1];
sw.b :=P2[ifx*3+2];
se.r :=P2[ix*3];
se.g :=P2[ix*3+1];
se.b:=P2[ix*3+2];
Top :=nw.b+eww*(ne.b-nw.b);
Bottom :=sw.b+eww*(se.b-sw.b);
P3[x*3+2] :=IntToByte(Round(Top+nsw*(Bottom-Top)));
Top :=nw.g+eww*(ne.g-nw.g);
Bottom :=sw.g+eww*(se.g-sw.g);
P3[x*3+1] :=IntToByte(Round(Top+nsw*(Bottom-Top)));
Top :=nw.r+eww*(ne.r-nw.r);
Bottom :=sw.r+eww*(se.r-sw.r);
P3[x*3] :=IntToByte(Round(Top+nsw*(Bottom-Top)));
end;
end;
end;
end;

procedure TForm1.Button1Click(Sender: TObject); //arbitrary degree rotation
var
Angle :Extended;
S: String;
Corners :array of TPoint;
x1,x2,y1,y2 :Integer;
SrcBmp,DstBmp :TBitmap;
Center :TPoint;

function MyRotate(var p :TPoint; ang :Extended):TPoint;
begin
Result.x :=Round((p.x*cos(DegToRad(ang)))-(p.y*sin(DegToRad(ang)))); //绕坐标原点旋转
Result.y :=Round((p.y*cos(DegToRad(ang)))+(p.x*sin(DegToRad(ang))));
end;

begin
S :='45';
Angle :=0.0;
SrcBmp :=TBitmap.Create;
SrcBmp.Assign(Form1.Image1.Picture.Bitmap);
if InputQuery('图象旋转','旋转角度(正值顺时针,负值逆时针):',S) then
Angle :=StrToFloat(S);
SetLength(Corners,4);
Corners[0].x :=-SrcBmp.Width div 2; // 0 1
Corners[0].y :=SrcBmp.Height div 2; // 2 3
Corners[1].x :=-Corners[0].x;
Corners[1].y :=Corners[0].y;
Corners[2].x :=Corners[0].x; //原坐标系角点坐标
Corners[2].y :=-Corners[0].y;
Corners[3].x :=-Corners[0].x;
Corners[3].y :=-Corners[0].y;
//BaseAngle :=BaseAngle+Angle; //BaseAngle 是基于原始位图的旋转
Corners[0] :=MyRotate(Corners[0],Angle{BaseAngle});
Corners[1] :=MyRotate(Corners[1],Angle{BaseAngle}); //新坐标系角点坐标
Corners[2] :=MyRotate(Corners[2],Angle{BaseAngle});
Corners[3] :=MyRotate(Corners[3],Angle{BaseAngle});
x1 :=MinIntValue([Corners[0].x,Corners[1].x,Corners[2].x,Corners[3].x]);
x2 :=MaxIntvalue([Corners[0].x,Corners[1].x,Corners[2].x,Corners[3].x]);
y1 :=MinIntValue([Corners[0].y,Corners[1].y,Corners[2].y,Corners[3].y]);
y2 :=MaxIntvalue([Corners[0].y,Corners[1].y,Corners[2].y,Corners[3].y]);
Corners :=nil;
DstBmp :=TBitmap.Create;
DstBmp.PixelFormat :=pf24bit;
DstBmp.Width :=x2-x1;
DstBmp.Height :=y2-y1;
Center.x :=DstBmp.Width div 2; //新的中心点
Center.y :=DstBmp.Height div 2;
GraphicRotate(SrcBmp,DstBmp,Center.x,Center.y,Angle{BaseAngle});
Form1.Image1.Picture.Bitmap.Assign(DstBmp);
SrcBmp.Free;
DstBmp.Free;
end;
 
我指的是图象,就是有一幅bitmap,我想把它旋转45度后画到窗口上,有没有这样的函数可以直接
使用?如果没有,那有什么控件可以实现?
 
图象的旋转没有专门的函数,有控件,但是效果不好!
你想旋转的话,就用我上面的程序。
自己看看吧。
 
tyn 已经给了你明确的方法,还是仔细看一看吧。
如果想偷懒,看看我以前翻译的一篇文章,文中提到的函数只能用于NT下。

在NT中旋转一个图形(原名:Spin on a Dime… With NT!)
作者:Robert Vivrette - RobertV@mail.com
翻译:Fish

Fish的说明:本文翻译自www.undn.com 2000年第1期的一篇文章。
我不是搞图形编程的(当然程序中总有要处理图形的时候),英语也
不怎么样,有些术语可能翻译的不准确,另外,有些地方采用的是意译。
如果有不清楚的地方,请看该网站的原文,或发信给我。

许多程序员都很熟悉BitBlt这个Windows API函数。 对于那些不熟悉的人来说,它代表“位块移动”(Bit Block Transfer),是Windows用于在屏幕上绘制图形的一个简单函数。与它密切相关的函数是StrechBlt,该函数能执行类似的功能,不过它允许你延展(stretch)或收缩(shrink)你所绘制的图形。Delphi在Draw和StretchDraw方法中包装了这些函数,使得它们使用起来更方便。

除了其它几个相关的函数(包括用于掩码操作(masking operation)的MaskBlt和使用样式(pattern)绘制指定区域的PatBlt)之外,Windows还包含一个少为人知的函数PlgBlt。它较少出现在许多人的图形处理词汇表中的部分原因是因为该函数只适用于WinNT/Win2K,顺便提一句,MaskBlt也是如此。这意味着使用Win31,Win95以及Win98的人就不那么走运了。它不工作!

但是,如果你使用WinNT/Win2K,并且必须做一些关于图形的有趣工作,你会发现PlgBlt是一个难以置信的强大的工具。

简单地说,除了不强求被绘制的目的地为矩形之外,PlgBlt和StrechBlt基本上是一样的,这也是它的名字以Plg开头的原因(Plg代表平行四边形(parallelogram))。要实现这种魔术(旋转的效果),所有的PlgBlt需要在画布(canvas)上指明三个点。
你也许会说,慢着……为什么只需要三个点?是的,在旋转图形时,确实使用了四个点。但是该函数并不使用你生成的第四个点。如果你在一张纸上以特定的顺序依次画三个点,那么对于要产生的平行四边形来说,第四个点是唯一的。这里要注意的是前三个点的次序是用于判断位置的重要依据。

为了演示PlgBlt的用法,我决定编写一个快速的小程序,它将允许用户实时地旋转一个位图。正如你将在下面看到的那样,这没有什么特别高深之处,但是PlgBlt的能力确实很棒!

即使你不使用WinNT/Win2K作为开发环境,在下面的代码中你也许仍然能够找到一些有用的感兴趣的技术。Read On!

unit PlgBltU;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons, ExtCtrls, Math;
type
TForm1 = class(TForm)
Img: TImage;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
private
P : Array[0..3] of TPoint;
OAng : Array[0..3] of Double;
OverHandle : Integer;
BkBmp : TBitmap;
MidPt : TPoint;
Ang,R : Double;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
Pt : Integer;
begin
BkBmp := TBitmap.Create;
BkBmp.Width := Width;
BkBmp.Height := Height;
P[0] := Img.BoundsRect.TopLeft;
P[3] := Img.BoundsRect.BottomRight;
P[1] := P[0]; Inc(P[1].X,Img.Width);
P[2] := P[3]; Dec(P[2].X,Img.Width);
with Img do MidPt := Point(Left+Width div 2,Top + Height div 2);
with Img do R := SqRt(Sqr(Width div 2)+Sqr(Height div 2));
for Pt := 0 to 3 do with P[Pt] do
OAng[Pt]:= ArcTan2(Y-MidPt.Y,X-MidPt.X)+Pi;
OverHandle := -1;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
BkBmp.Free;
end;
procedure TForm1.FormPaint(Sender: TObject);
var
Pt : Integer;
begin
with BkBmp.Canvas do
begin
Brush.Color := clBtnFace;
FillRect(ClipRect);
if PlgBlt(Handle,P,Img.Canvas.Handle,0,0,Img.Width,Img.Height,0,0,0) then
begin
Brush.Color := clBlack;
for Pt := 0 to 3 do with P[Pt] do
FillRect(Rect(X-3,Y-3,X+3,Y+3));
end
else
TextOut(0,0,'PlgBlt currently supported only on WinNT');
end;
Canvas.Draw(0,0,BkBmp);
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);
var
Pt : Integer;
TmpRect : TRect;
begin
if ssLeft in Shift then
begin
if OverHandle = -1 then exit;
Ang := ArcTan2(Y-MidPt.Y,X-MidPt.X)-OAng[OverHandle]+Pi;
for Pt := 0 to 3 do
P[Pt] := Point(MidPt.X-Round(R*Cos(Ang+OAng[Pt])),
MidPt.Y-Round(R*Sin(Ang+OAng[Pt])));
Paint;
end
else
begin
OverHandle := -1;
for Pt := 0 to 3 do
begin
with P[Pt] do TmpRect := Rect(X-3,Y-3,X+3,Y+3);
if PtInRect(TmpRect,Point(X,Y)) then
begin
Cursor := crHandPoint;
OverHandle := Pt;
end;
end;
if OverHandle = -1 then Cursor := crDefault;
end;
end;
end.

正如你所看到的,这里只使用了四个方法。这个演示程序的目的是在窗体上放置一个Timage并为它增加一个图形,在程序运行时,允许用户用鼠标抓住图形的角部并旋转这个图形。首先,创建一个新窗体,在窗体中央放置一个Timage,并在该Timage中加载一个位图。

第一件事情当然是在FormCreate中发生的。这里将做一些设置方面的工作。前面我们已经声明了一个Tbitmap,在旋转图形时,将使用该Tbitmap来防止闪烁。但是在FormCreate中,我们必须将这个位图的宽度和高度设置成和窗体一样。
接下来,我们要设置一个关于坐标点的数组。在这个数组中有四个点(从0到3),我们将出于两个目的来使用这些点:首先会将它们传递给PlgBlt函数(记住,该函数会忽略第四个点);其次,用于在图形的四个角部绘制把柄(handle),这样我们才能看到抓住什么地方让图形旋转。在FormCreate中,我只是简单地将图形的四个角落的坐标放到这些坐标点数组中。

下一步是计算MidPt。这个Tpoint将用于保持图形旋转的中心点,它的值来自于该图形的四个角坐标的平均值。下一个定义为R的变量将保持从图形的某个角到MidPt的距离(半径)。它将用于后面的旋转计算。下面,将图形的每个角与中心点的初始角度填入到包含四个实数值(事实上是Double类型)的数组中。这个数组也将用于旋转计算中。最后,我们为OverHandle变量设置一个初始值。这个变量将用于跟踪鼠标所指的位置的把柄(Handle)编号。当它的值为-1时,表示鼠标不在任何一个把柄上。

FormDestroy方法只是简单地清除我们在FormCreate中创建的背景位图。

FormPaint是PlgBlt工作的地方。记住前面所说过的,我们将使用背景位图来避免闪烁。其实现方法是,首先在该背景位图上完成所有的绘制工作,在全部完成后,将调用一次它的Draw方法将整个内容贴到窗体的画布上。只有在同一块区域存在多个(并且相互冲突的)绘制动作时才会有闪烁。因为背景位图存在于内存之中的Tbitmap中,我们可以随意地在它上面绘制内容,在将它转移到窗体上之前,用户不会看见这个位图。并且因为只有单个的绘制事件,不会发生闪烁。

再回到FormPaint…首先,使用FillRect清除背景位图。然后调用PlgBlt。PlgBlt的参数如下所示(你可以从Win32 API的帮助中找到这些帮助信息):

hdcDest – 目的的设备上下文(device context )ID. 实际上就是目标的画布的句柄(BkBmp.Canvas.Handle)

lpPoint – 坐标点的数组,用于识别作为目标的平行四边形的前三个坐标点。源矩形的左上角映射到这个数组的第一个点,源矩形的右上角映射到这个数组的第二个点,源矩形的坐下角映射到第三个点。正如前面所提到的,函数本身会为你计算右下角的坐标。即使我们在参数中传递全部四个坐标点也诶有什么关系,PlgBlt函数会忽略最后那个点。

hdcSrc – 源的设备上下文ID.实际上就是Image的图形的画布的句柄 (Img.Canvas.Handle).
nXSrc – 源矩形的左上角x坐标
nYSrc – 源矩形的左上角y坐标
nWidth – 源矩形的宽度
nHeight – 源矩形的高度.

最后的三个参数(hbmMask,xMask,yMask)用于一个可选的用于对颜色掩码的位图,在本例中我们不使用这些参数,将它们设置为0即可。

如果调用PlgBlt函数成功,就接着使用传递给PlgBlt的数组中的四个坐标点来绘制新的四个把柄。如果调用失败,通常是因为操作系统不是WinNT平台,这是我们使用TextOut来输出一个提示信息。在所有这些完成之后,是用Draw方法将背景位图一次性转移到窗体上。

这个例子中的最后一个方法是管理鼠标在四个角落的把柄上的动作。在鼠标移动时,首先检查鼠标左键是否被按下。如果是,还要检查OverHandle变量,确保它包含的是那个被抓住的把柄的编号。如果这个条件也为真,就判断鼠标和图形中心点的角度(之前已经计算过)。我们使用这个角度来重新计算图形的四个把柄的新位置。
如果鼠标左键未被按下,将OverHandle变量复位,并使用PtInRect来判断鼠标是否在四个把柄的上面。如果在,则在OverHandle中保存该把柄的编号,并将光标的形状改成手型。

这就是这个演示程序的所有代码了。注意程序中并没有旋转图形的代码,PlgBlt函数为我们做了这一切。

Fish注:原文还带有两个演示程序运行的效果图,这里就省略了。本文所描述的例子可在下面网址下载。
http://www.undu.com/LIBS/plgblt.zip
 
procedure TForm1.Bmp_Rotate(Src, Dst: TBitmap; Angle: Extended);
var
C1X, C1Y, C2X, C2Y: Integer;
P1X, P1Y, P2X, P2Y: Integer;
Radius, N: Integer;
Alpha: Extended;
C0, C1, C2, C3: TColor;
begin
Angle := Angle * Pi / 180;
C1X := Src.Width div 2;
C1Y := Src.Height div 2;
C2X := Dst.Width div 2;
C2Y := Dst.Height div 2;
if C2X < C2Y then N := C2Y
else
N := C2X;
for P2X := 0 to N - 1 do begin
for P2Y := 0 to N - 1 do begin
if P2X = 0 then Alpha := Pi/2
else
Alpha := Arctan2(P2Y, P2X);
Radius := Round(Sqrt((P2X * P2X) + (P2Y * P2Y)));
P1X := Round(Radius * Cos(Angle + Alpha));
P1Y := Round(Radius * Sin(Angle + Alpha));

//C0 := Src.Canvas.Pixels[C1X + P1X, C1Y + P1Y];
//C1 := Src.Canvas.Pixels[C1X - P1X, C1Y - P1Y];
//C2 := Src.Canvas.Pixels[C1X + P1Y, C1Y - P1X];
//C3 := Src.Canvas.Pixels[C1X - P1Y, C1Y + P1X];

Dst.Canvas.Pixels[C2X + P2X, C2Y + P2Y] := C0;
Dst.Canvas.Pixels[C2X - P2X, C2Y - P2Y] := C1;
Dst.Canvas.Pixels[C2X + P2Y, C2Y - P2X] := C2;
Dst.Canvas.Pixels[C2X - P2Y, C2Y + P2X] := C3;
end;
end;
Application.ProcessMessages;
end;
 
后退
顶部