Delphi Graphics32如何用鼠标在图层上画线

Delphi Graphics32 how to draw a line with the mouse on a layer

谁能帮我把这种动态画线的好方法 (Photoshop style drawing line with delphi) 转换成 Graphics32?

我的意思是,我想要一个 ImgView,向其添加一个新层,然后在该层而不是窗体的 canvas.

上执行这些方法

所以我假设,我的代码应该是这样的:

 private
    FStartPoint, FEndPoint: TPoint;
    FDrawingLine: boolean;
    bm32: TBitmap32;

...

procedure TForm1.FormCreate(Sender: TObject);
begin
  bm32 := TBitmap32.Create;
  FDrawingLine := false;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  with ImgView do
  begin
    Selection := nil;
    RBLayer := nil;
    Layers.Clear;
    Scale := 1;
    Bitmap.SetSize(800, 600);
    Bitmap.Clear(clWhite32);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  B : TBitmapLayer;
  P: TPoint;
  W, H: Single;
begin
        B := TBitmapLayer.Create(ImgView.Layers);
        with B do
        try
          Bitmap.DrawMode := dmBlend;
          with ImgView.Bitmap do Location := GR32.FloatRect(0, 0, 600, 400);
          Scaled := True;
          OnMouseDown := LayerMouseDown;
          OnMouseUp := LayerMouseUp;
          OnMouseMove := LayerMouseMove;
          OnPaint := LayerOnPaint;
        except
          Free;
          raise;
        end;
end;

我假定此代码是因为这些是 link 中常规 canvas 绘图方法中使用的事件,但其余方法无法正常工作

procedure TForm1.AddLineToLayer;
begin
  bm32.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
  bm32.Canvas.LineTo(FEndPoint.X, FEndPoint.Y);
end;

procedure TForm1.SwapBuffers32;
begin
  BitBlt(imgView.Canvas.Handle, 0, 0, ClientWidth, ClientHeight,bm32.Canvas.Handle, 0, 0, SRCCOPY);
end;

procedure TForm1.SwapBuffers;
begin
  BitBlt(Canvas.Handle, 0, 0, ClientWidth, ClientHeight,
    bm.Canvas.Handle, 0, 0, SRCCOPY);
end;


procedure TForm1.LayerMouseDown(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
  FStartPoint := Point(X, Y);
  FDrawingLine := true;
end;

procedure TForm1.LayerMouseUp(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
  FDrawingLine := false;
  FEndPoint := Point(X, Y);
  AddLineToLayer;
  SwapBuffers;
end;

procedure TForm1.LayerMouseMove(Sender: TObject; Shift: TShiftState;X, Y: Integer);
begin
  if FDrawingLine then
  begin
    SwapBuffers;
    ImgView.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
    ImgView.Canvas.LineTo(X, Y);
  end;
end;

procedure TForm1.LayerOnPaint(Sender: TObject; Buffer: TBitmap32);
begin
  SwapBuffers;
end;

所以它不起作用。什么都没发生。 任何人都可以帮助我像正常 canvas 绘图一样完成这项工作吗? 我只想让一层发生这种情况,我用 Button1Click 创建的层...... (ImgView是放在窗体上的ImgView32控件,窗体上也有一个按钮)

结果看起来像这样(错误提示 Canvas 不允许绘图) 第一次出现onButtonClick错误,然后Ok it后,开始绘图,它没有擦除移动线(就像上图),然后onMouseUp再次出现Canvas错误。

我做错了什么?

如果我使用 SwapBuffers32,则不会绘制任何内容,并且 canvas 错误不断出现。

编辑: 我做了一些更改,只是为了尝试在 Tom Brunberg 的建议后让它工作,最后我得到了这个代码:

 private
    FStartPoint, FEndPoint: TPoint;
    FDrawingLine: boolean;
    bm32: TBitmap32;
    B : TBitmapLayer;
    FSelection: TPositionedLayer;
  public
    procedure AddLineToLayer;
    procedure SwapBuffers32;
    procedure LayerMouseDown(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
    procedure LayerMouseUp(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
    procedure LayerMouseMove(Sender: TObject; Shift: TShiftState;X, Y: Integer);
    procedure LayerOnPaint(Sender: TObject; Buffer: TBitmap32);
    procedure SetSelection(Value: TPositionedLayer);
    property Selection: TPositionedLayer read FSelection write SetSelection;
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
var
  P: TPoint;
  W, H: Single;
begin
   bm32 := TBitmap32.Create;
   bm32.SetSize(800,600);
      with ImgView do
        begin
          Selection := nil;
          Layers.Clear;
          Scale := 1;
          Bitmap.SetSize(800, 600);
          Bitmap.Clear(clWhite32);
        end;

        B := TBitmapLayer.Create(ImgView.Layers);
        with B do
        try
          Bitmap.DrawMode := dmBlend;
          B.Bitmap.SetSize(800,600);
          with ImgView.Bitmap do Location := GR32.FloatRect(0, 0, 800, 600);
          Scaled := True;
          OnMouseDown := LayerMouseDown;
          OnMouseUp := LayerMouseUp;
          OnMouseMove := LayerMouseMove;
          OnPaint := LayerOnPaint;
        except
          Free;
          raise;
        end;
  FDrawingLine := false;
end;

procedure TForm1.AddLineToLayer;
begin
  bm32.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
  bm32.Canvas.LineTo(FEndPoint.X, FEndPoint.Y);
end;

procedure TForm1.SwapBuffers32;
begin
//  BitBlt(imgView.Canvas.Handle, 0, 0, ClientWidth, ClientHeight,bm32.Canvas.Handle, 0, 0, SRCCOPY);
  BitBlt(B.Bitmap.Canvas.Handle, 0, 0, ClientWidth, ClientHeight,bm32.Canvas.Handle, 0, 0, SRCCOPY);
end;


procedure TForm1.LayerMouseDown(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
  FStartPoint := Point(X, Y);
  FDrawingLine := true;
end;

procedure TForm1.LayerMouseUp(Sender: TObject; Buttons: TMouseButton;Shift: TShiftState; X, Y: Integer);
begin
  FDrawingLine := false;
  FEndPoint := Point(X, Y);
  AddLineToLayer;
  SwapBuffers32;
end;

procedure TForm1.LayerMouseMove(Sender: TObject; Shift: TShiftState;X, Y: Integer);
begin
  if FDrawingLine then
  begin
    SwapBuffers32;
    ImgView.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
    ImgView.Canvas.LineTo(X, Y);
  end;
end;

procedure TForm1.LayerOnPaint(Sender: TObject; Buffer: TBitmap32);
begin
  SwapBuffers32;
end;


procedure TForm1.SetSelection(Value: TPositionedLayer);
begin
  if Value <> FSelection then
  begin
    FSelection := Value;
  end;
end;

现在,不再有 Canvas 错误,但鼠标移动线保持绘制...解决方案必须在 BitBlt 函数 (swapbuffers32) 中。有什么想法吗?

要了解无法删除不需要的行的问题,我们需要回顾一下 Anders Rejbrands 解决方案的工作原理。 内存位图 bm 是我们存储 wanted 行的位图。表单的 canvas 充当垫板,我们可以在其中捕获鼠标操作并向用户提供反馈。在 MouseDownMouseUp 事件(确定想要的起点和终点)之间,我们收到很多 MouseMove 事件。对于每个 MouseMove,我们首先调用 SwapBuffers,它 擦除 表单 canvas 中的任何垃圾(之前 MouseMove 留下的)。然后我们绘制从起点到当前鼠标位置的线。擦除是通过将 bm 的内容复制 (BitBlt) 到形式 canvas.

来完成的

由于删除不需要的行不起作用,我们需要仔细查看您代码中的 bm32。你在 FormCreate 中创建它,但你从来没有给它一个大小!这就是问题所在。 SwapBuffers32.

中没有可复制的内容

另外,由于位图没有大小,所以无法绘制。因此错误消息。

SwapBuffer 的另一个版本引用了一个 bm 变量,它没有在任何其他代码中显示,所以我根本无法对此发表评论。

更新用户代码后编辑。

在FormCreate中,设置bm32的大小后,添加

  bm32.Clear(clWhite32); // Add this line

并更改以下两行

//    with ImgView.Bitmap do Location := GR32.FloatRect(0, 0, 800, 600);
    B.Location := GR32.FloatRect(0, 0, 800, 600);
//    Scaled := True;
    Scaled := False;

最后在 FormCreate 的末尾添加

  SwapBuffers32;

在 LayerMouseMove 中用 B.BitMap

替换 ImgView
//    ImgView.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
//    ImgView.Canvas.LineTo(X, Y);
    B.Bitmap.Canvas.MoveTo(FStartPoint.X, FStartPoint.Y);
    B.Bitmap.Canvas.LineTo(X, Y);

并在 SwapBuffers32 中将 ClientWidth 和 ClienHeight 替换为 B.Bitmap

的属性
  BitBlt(B.Bitmap.Canvas.Handle, 0, 0, B.Bitmap.Width, B.Bitmap.Height,bm32.Canvas.Handle, 0, 0, SRCCOPY);

这些更改对我有用,因此 bm32 仍会收集预期的行。由于 MouseUp 的最后一次调用是对 SwapBuffers 的调用,因此 B 层将获得这些行的最终副本。 ImgView.Bitmap 不涉及任何内容,因为您希望在图层上绘制。

根据用户评论进行编辑...

我确实又做了一处改动。抱歉忘记提及。

在 FormCreate 中,在 with B...

//    Bitmap.DrawMode := dmBlend;
    Bitmap.DrawMode := dmOpaque;

在 Firemonkey 中,我使用位图从 2 个点绘制直线。

基本上,在线条开始之前(鼠标按下、事件),您可以截取要绘制线条的区域。

然后当鼠标移动时在位图副本上画一条线。每次在位图上绘制线条之前,您都将位图替换为原始屏幕截图。可能需要稍微修改一下,但似乎工作正常。 在下面的代码中,图像与您要绘制的区域的客户端对齐。

代码....

procedure TForm3.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin

  if Button = TmouseButton.mbLeft then
  begin
    startPoint := pointf(X,Y);
    endPoint := StartPoint;
    saveScreen := Image1.MakeScreenshot;
    Image1.Bitmap := saveScreen;
    Panel1.HitTest := false;
  end;
end;

procedure TForm3.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);

begin

  if ssLeft in Shift  then
  begin
    EndPoint := pointf(X,y);
    Image1.Bitmap := saveScreen;
    Image1.Bitmap.Canvas.BeginScene();
    Image1.Bitmap.Canvas.Stroke.Color := TAlphaColorRec.Green;
    Image1.Bitmap.Canvas.DrawLine(StartPoint, endPoint  ,1);
    Image1.Bitmap.Canvas.EndScene;
  end;

 end;

procedure TForm3.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin

   Image1.canvas.beginscene;
   Image1.Bitmap := saveScreen;
   Image1.canvas.endScene;
   //Panel1.HitTest := true;  ignore this for now.
end;

我认为fire monkey中可能还有另一种方法可以实现用鼠标画线,那就是在窗体上拖放一个TLine,将x,y的旋转角度设置为0。画线时创建从起点到终点的边界矩形,从起点(归一化矩形)算出边界矩形的三角形交点的旋转角度,并基本上将 TLine 的旋转角度更改为任何值。将线定位在起点,然后修改长度。无论如何的想法。可能是另一种方法。抱歉缺少这方面的代码...