Правильное движение вращающейся фигуры с помощью мыши - PullRequest
0 голосов
/ 12 октября 2018

Следующий пример помещает Timage в форму;он создает рисунок внутри него, а затем с помощью 2 событий мыши (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 голосов
/ 13 октября 2018

Причина, по которой ваше повернутое изображение движется не так, как вам нужно, заключается в том, что значения X и Y, возвращаемые событием OnMouseMove, относятся к вашему клиенту изображения, который теперь повернут.Так, например, если у вас есть изображение с шириной 100 пикселей, поверните его на 180 градусов и затем переместите курсор мыши в левую часть изображения, значение X будет 100, а не 0, как если бы изображение не было повернуто,

Таким образом, чтобы правильно перемещать повернутое изображение, вам нужно работать с преобразованием значений X, Y в соответствии с вращением изображения, используя векторное вращение от точки вращения вашего изображения.

РЕДАКТИРОВАТЬ: Чтобы избежать преобразования векторов X и Y самостоятельно, вы можете оставить Delphi, чтобы сделать это для вас.Это можно сделать, преобразовав все позиционные значения из локальных позиций клиента в позицию Screen с помощью метода ClientToScreen, а затем, наконец, преобразовать новую вычисленную позицию обратно из позиции Screen в позицию клиента с помощью метода ScreenToClient.

Вот пример такого кода:

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: Кроме того, во время написания этого примера кода я понял, что вы можете выполнять математические операции непосредственно с точками, а не выполнять вычисления для значений X и Y отдельно.По крайней мере, вы можете сделать это в Delphi 10.2 Tokyo.Я не уверен в более старых версиях Delphi.

Поэтому в моем примере добавляются и вычитаются сами точки вместо отдельных значений X и Y.

0 голосов
/ 14 октября 2018

Наконец, я делюсь кодом с созданным мною решением.

Тестовое видео: https://1drv.ms/v/s!AqdWVn6k-HLbgqRw352kQ1HjuIJ5Hw

Решение проблемы перемещения повернутой фигуры с помощью мыши может быть решенобез векторов, создавая прозрачную маску, которая перекрывает исходное изображение.Есть некоторые ограничения, как видно из комментариев.

Процедуры перемещения мыши будут основаны на событиях в маске Timage, которая всегда остается без поворота на 0 градусов, в то время как повернутое изображение копирует расположение маскикогда это перемещено.Это на самом деле еще 2 строки кода и объявляют маску как новый пустой образ, повторяя исходный размер и положение.Я оставляю полный код, работающий правильно.

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.
0 голосов
/ 12 октября 2018
//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.
...