用鼠标正确校正旋转图形的运动

时间:2018-10-12 18:12:02

标签: delphi firemonkey

以下示例将Timage放置在表单中;它会在其中创建一个图形,然后通过图像上的两个鼠标事件(MouseDown和MouseMove),用鼠标在屏幕上正确移动图形;

现在,如果我们从代码中删除注释{MyImage.RotationAngle:= 120;}并激活图形先前的旋转加90度,则移动不正确。我看不出问题是什么。 [在我们可以通过鼠标在屏幕上移动结果的同时,必须能够多次旋转屏幕上的图形。]

unit UMoveShape;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Objects, FMX.StdCtrls, FMX.Controls.Presentation,
  FireDAC.UI.Intf, FireDAC.FMXUI.Async, FireDAC.Stan.Intf,
  FMX.DialogService.Async,System.UIConsts,System.Math.Vectors,  FireDAC.Comp.UI;

type
  TForm16 = class(TForm)
    MyImage: TImage;
    procedure FormCreate(Sender: TObject);
    procedure MyImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure MyImageMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Single);
  private
    { Private declarations }
  public
    Xdiff,Ydiff: single;
    { Public declarations }
  end;

var
  Form16: TForm16;

implementation

{$R *.fmx}

procedure TForm16.FormCreate(Sender: TObject);
Var
 MyRect1, MyRect2: TRectF;
 Path: TPathData;
begin
  MyImage.Width := 500;
  MyImage.Height := 500;
  MyImage.Bitmap.SetSize(Round(MyImage.Width), Round(MyImage.Height));
  MyRect1 := TRectF.Create(98, 100, 200, 200);
  MyRect2 := TRectF.Create(70, 90, 225, 210);
  Path := TPathData.Create;
  Path.AddEllipse(MyRect1);
  Path.AddRectangle(MyRect2, 0, 0, AllCorners);
  MyImage.Bitmap.Canvas.BeginScene;
  MyImage.Bitmap.Canvas.DrawPath(path, 200);
  MyImage.Bitmap.Canvas.EndScene;

{If we rotate the image more than 90 degrees for example, the movement of the
 image with the cursor is erratic using the mouseDown and MouseMove routines.
 On the other way, if we don't rotate the movement is perfect.}

 // MyImage.RotationAngle := 120;
end;

procedure TForm16.MyImageMouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
  Y: Single);
begin
  if (ssleft in shift) then
  begin
    Xdiff := X;
    Ydiff := y;
  end;
end;

procedure TForm16.MyImageMouseMove (Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
  if (ssleft in shift) then
  begin
    MyImage.Position.X := MyImage.Position.X + X - Xdiff;
    MyImage.Position.y := MyImage.Position.Y + Y - YDiff;
  end;

end;

end.

3 个答案:

答案 0 :(得分:2)

旋转的图像无法按照您想要的方式移动的原因是,OnMouseMove事件返回的X和Y值与现在旋转的图像客户端有关。因此,例如,如果您有一幅宽度为100像素的图像,请将其旋转180度,然后将鼠标光标移动到图像的左侧,则X值将为100而不是0,就好像该图像不会旋转一样。

因此,为了正确地旋转旋转的图像,您需要使用从图像旋转点开始的矢量旋转来相应地转换X,Y值以适应图像旋转。

编辑:为了避免自己对X和Y进行矢量变换,可以让Delphi替您完成。为此,您可以使用ClientToScreen方法将所有位置值从本地客户位置转换为Screen位置,然后最后使用ScreenToClient方法将新计算出的位置从Screen位置转换回客户位置。

以下是此类代码的示例:

var
  Form1: TForm1;
  StartDragPos: TPointF;
  Dragging: Boolean;

implementation

{$R *.fmx}

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  if (ssleft in shift) then
  begin
    StartDragPos := ClientToScreen(Point(Round(X),Round(Y)));
    Dragging := True;
  end;
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  if Dragging then
  begin
    Image1.Position.Point := ScreenToClient(ClientToScreen(Image1.Position.Point + ClientToScreen(Point(Round(X),Round(Y))) - StartDragPos));
  end;
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin
  Dragging := False;
end;

PS:同样,在编写此示例代码时,我发现您实际上可以直接使用Points进行数学运算,而不是分别计算X和Y值。至少您可以在Delphi 10.2 Tokyo中做到这一点。我不确定使用较旧的Delphi版本。

因此,我的示例为什么要自己加上和减去点,而不是单独的X和Y值。

答案 1 :(得分:1)

最后,我与创建的解决方案共享代码。

测试视频:https://1drv.ms/v/s!AqdWVn6k-HLbgqRw352kQ1HjuIJ5Hw

旋转图形移动问题的解决方案  通过鼠标,无需向量即可解析,从而创建与原始图像重叠的透明蒙版。如评论中所示,存在一些限制。

鼠标移动例程将基于蒙版Timage中的事件,该事件始终保持不旋转0度,而旋转的图像会在移动蒙版时复制蒙版的位置。实际上,这是另外两行代码,并将掩码声明为新的空Tmage,复制了原始大小和位置。我让完整的代码正常工作。

unit UMoveShape;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Objects, FMX.StdCtrls, FMX.Controls.Presentation,
  FireDAC.UI.Intf, FireDAC.FMXUI.Async, FireDAC.Stan.Intf,
  FMX.DialogService.Async, System.UIConsts, System.Math.Vectors, FireDAC.Comp.UI, FMX.Edit,
  FMX.ScrollBox, FMX.Memo;

type
  TForm16 = class (TForm)
    MyImage: TImage;
    MyImageMask: TImage;
    EditDegrees: TEdit;
    ButtonRotate: TButton;
    procedure FormCreate (Sender: TObject);
    procedure MyImageMaskMouseMove (Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure MyImageMaskMouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
      Y: Single);
    procedure ButtonRotateClick (Sender: TObject);
  private
    { Private declarations }
  public
    Xdiff, Ydiff: single;
    MyRect1, MyRect2: TRectF;
    Path: TPathData;

    { Public declarations }
  end;

var
  Form16            : TForm16;

implementation

{$R *.fmx}

procedure TForm16.ButtonRotateClick (Sender: TObject);
begin
  MyImage.RotationAngle := EditDegrees.Text.ToSingle;
end;

procedure TForm16.FormCreate (Sender: TObject);
begin
  // Original Image
  MyImage.Width := 300;
  MyImage.Height := 300;
  MyImage.Bitmap.SetSize (Round (MyImage.Width), Round (MyImage.Height));
  MyRect1 := TRectF.Create (98, 100, 200, 200);
  MyRect2 := TRectF.Create (70, 90, 225, 210);
  Path := TPathData.Create;
  Path.AddEllipse (MyRect1);
  Path.AddRectangle (MyRect2, 0, 0, AllCorners);
  MyImage.Bitmap.Canvas.BeginScene;
  MyImage.Bitmap.Canvas.DrawPath (path, 200);
  MyImage.Bitmap.Canvas.EndScene;

  MyImageMask.Width := MyImage.Width;
  MyImageMask.Height := MyImage.Height;
  MyImageMask.Position := MyImage.Position;
end;

procedure TForm16.MyImageMaskMouseDown (Sender: TObject; Button: TMouseButton; Shift: TShiftState;
  X,
  Y: Single);
begin
  if (ssleft in shift) then
  begin
    Xdiff := X;
    Ydiff := y;
  end;
end;

procedure TForm16.MyImageMaskMouseMove (Sender: TObject; Shift: TShiftState; X, Y: Single);
begin
  if (ssleft in shift) then
  begin
    MyImageMask.Position.X := MyImage.Position.X + X - Xdiff;
    MyImageMask.Position.y := MyImage.Position.Y + Y - YDiff;
    MyImage.Position.X := MyImageMask.Position.X;
    MyImage.Position.y := MyImageMask.Position.Y;
    Form16.Caption := 'X:' + MyImage.Position.X.ToString + ' Y: ' + MyImage.Position.y.ToString;
  end;
end;

end.

答案 2 :(得分:0)

//First of all load a simple Bitmap in your MyImage.

unit Unit1;

    interface

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls;

  type
  TRGB = packed record
    b: byte;
    g: byte;
    r: byte;
  end;
  PRGB = ^TRGB;

type
  TForm1 = class(TForm)
    MyImage: TImage;
    procedure FormCreate(Sender: TObject);
    procedure MyImageMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure MyImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    procedure Rotate( bmp:TBitmap; Angle: double;fillColor: TColor);
  public
    { Public declarations }
    Xdiff,Ydiff: integer;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyImage.Width := 500;
  MyImage.Height := 500;

 Rotate ( MyImage.Picture.Bitmap, 120, form1.Color);



end;

procedure TForm1.MyImageMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
 if (ssleft in shift) then
  begin
    Xdiff := X;
    Ydiff := y;
  end;
end;

procedure TForm1.MyImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
  if (ssleft in shift) then
  begin
    MyImage.left := MyImage.left + X - Xdiff;
    MyImage.top := MyImage.top + Y - YDiff;
  end;
end;
procedure TForm1.Rotate( bmp:TBitmap; Angle: double; fillColor: TColor);
var
  dst: TBitmap;

  parx1, parx2: pinteger;
  a, tsin, tcos, cxSrc, cySrc, cxDest, cyDest: Double;
  fx, fy: Integer;
  dw, dh,  x, y: Integer;
  px: pbyte;
  arx1, arx2: pintegerarray;
  ary1, ary2: Integer;
  ps, pd: pbyte;
  dw1, dh1: Integer;
  prgb_s, prgb_d: PRGB;
  srcrows: ppointerarray;
  iangle: Integer;
  prog, lprog: Integer;
  aTRGB:trgb;

  procedure Rot90(inv: Boolean);
  var
    x, y: Integer;
    mulx, muly, addx, addy: Integer;
  begin
    dw := bmp.height; dw1 := dw-1;
    dh := bmp.Width;  dh1 := dh-1;
    dst:= TBitmap.Create;
    dst.Width := dw;
    dst.height := dw;
    dst.PixelFormat:=  pf24bit;
    dst.Canvas.Brush.Color := fillColor;
    dst.Canvas.FillRect(Rect (0,0,dw ,dh)  );

    if inv then
    begin
      mulx := -1;
      muly := 1;
      addx := dw1;
      addy := 0;
    end
    else
    begin
      mulx := 1;
      muly := -1;
      addx := 0;
      addy := dh1;
    end;
    for x := 0 to dw1 do
    begin
      ps := bmp.ScanLine[addx+x*mulx];
      prgb_s := PRGB(ps);
      for y := 0 to dh1 do
      begin
        prgb_d := dst.Scanline[addy+y*muly];
        inc(prgb_d, x);
        prgb_d^ := prgb_s^;
        inc(prgb_s);
      end;
    end;
  end;

  procedure Rot180;
  var
    x, y: Integer;
  begin
    dw := bmp.width; dw1 := dw-1;
    dh := bmp.height;  dh1 := dh-1;
    dst:= TBitmap.Create;
    dst.Width := dw;
    dst.height := dw;
    dst.PixelFormat:=  pf24bit;
    dst.Canvas.Brush.Color := fillColor;
    dst.Canvas.FillRect(Rect (0,0,dw ,dh)  );
    for y := 0 to dh1 do
    begin
      pd := dst.ScanLine[dh1 - y];
      ps := bmp.Scanline[y];
      prgb_d := PRGB(pd);
      prgb_s := PRGB(ps);
      inc(prgb_s, dw1);
      for x := 0 to dw1 do
      begin
        prgb_d^ := prgb_s^;
        inc(prgb_d);
        dec(prgb_s);
      end;
    end;
  end;



begin

  if (Frac(angle) = 0) and ((trunc(angle) mod 90) = 0) then
  begin
    iangle := trunc(angle) mod 360;
    case iangle of
      90 : Rot90(false);
      180 : Rot180;
      270 : Rot90(true);
      -90 : Rot90(true);
      -180 : Rot180;
      -270 : Rot90(false);
    end;
    bmp.Assign( dst );
    FreeAndNil(dst);
    exit;
  end;

  a := angle * pi / 180;
  dw := round(abs(bmp.width * cos(a)) + abs(bmp.height * sin(a)));
  dh := round(abs(bmp.width * sin(a)) + abs(bmp.height * cos(a)));
  dw1 := dw-1;
  dh1 := dh-1;

    dst:= TBitmap.Create;
    dst.Width := dw;
    dst.height := dw;
    dst.PixelFormat:=  pf24bit;
    dst.Canvas.Brush.Color := fillColor;
    dst.Canvas.FillRect(Rect (0,0,dw ,dh)  );

  tsin := sin(a);
  tcos := cos(a);
  cxSrc := (bmp.Width - 1) / 2;
  cySrc := (bmp.Height - 1) / 2;
  cxDest := (dst.Width - 1) / 2;
  cyDest := (dst.Height - 1) / 2;
  getmem(arx1, sizeof(integer) * dst.Width);
  getmem(arx2, sizeof(integer) * dst.Width);
  for x := 0 to dst.Width - 1 do
  begin
    arx1[x] := round( cxSrc + (x - cxDest) * tcos );
    arx2[x] := round( cySrc + (x - cxDest) * tsin );
  end;

  getmem(srcrows, bmp.height*sizeof(pointer));
  for y := 0 to bmp.height-1 do
    srcrows[y] :=  bmp.ScanLine[y];

  for y := 0 to dh1 do
  begin
    px := dst.Scanline[y];
    ary1 := round( (y - cyDest) * tsin );
    ary2 := round( (y - cyDest) * tcos );

    parx1 := @arx1[0];
    parx2 := @arx2[0];

    prgb_d := prgb(px);
    for x := 0 to dw1 do
    begin
      fx := parx1^ - ary1;
      if (fx >= 0) and (fx < bmp.width )then
      begin
        fy := parx2^ + ary2;
        if (fy >= 0) and (fy < bmp.height) then
        begin
          prgb_s := srcrows[fy];
          inc(prgb_s, fx);
          prgb_d^ := prgb_s^;
        end;
      end;
      inc(prgb_d);
      inc(parx1);
      inc(parx2);
    end;


  end;

  freemem(srcrows);
  freemem(arx1);
  freemem(arx2);

  bmp.Assign( dst );
  FreeAndNil(dst);

end;

end.