简体   繁体   English

如何在delphi中创建自定义绘制线

[英]how can I create a custom draw line in delphi

我想在我的 delphi 应用程序中的画布上画一条线,但需要它是一条 ****** 如何使用 * char 而不是破折号或点创建自定义线。

You can use the Skia4Delphi library for a generic solution to the problem.您可以使用Skia4Delphi库来获得该问题的通用解决方案。 Below is an example using the TSkPaintBox control and the TSkPaintBox.OnDraw event:下面是使用 TSkPaintBox 控件和 TSkPaintBox.OnDraw 事件的示例:

uses
  System.Math.Vectors, FMX.TextLayout, Skia, Skia.FMX;

procedure TForm1.SkPaintBox1Draw(ASender: TObject; const ACanvas: ISkCanvas;
  const ADest: TRectF; const AOpacity: Single);

  function GetTextPath(const AText: string): ISkPath;
  var
    LTextLayout: TTextLayout;
    LPathData: TPathData;
  begin
    LTextLayout := TTextLayoutManager.DefaultTextLayout.Create;
    try
      LTextLayout.BeginUpdate;
      try
        LTextLayout.Font.Size := 30;
        LTextLayout.Font.Style := [TFontStyle.fsBold];
        LTextLayout.Text := AText;
      finally
        LTextLayout.EndUpdate;
      end;
      LPathData := TPathData.Create;
      try
        LTextLayout.ConvertToPath(LPathData);
        Result := LPathData.ToSkPath;
      finally
        LPathData.Free;
      end;
    finally
      LTextLayout.Free;
    end;
  end;

var
  LPaint: ISkPaint;
  LTextPath: ISkPath;
  LPathBuilder: ISkPathBuilder;
begin
  LTextPath := GetTextPath('*');
  LPaint := TSkPaint.Create(TSkPaintStyle.Stroke);
  LPaint.AntiAlias := True;
  LPaint.Color := TAlphaColors.Black;
  LPaint.PathEffect := TSkPathEffect.Make1DPath(LTextPath, LTextPath.Bounds.Width + 2, 0, TSkPathEffect1DStyle.Rotate);
  LPathBuilder := TSkPathBuilder.Create;
  LPathBuilder.MoveTo(PointF(50, 100));
  LPathBuilder.LineTo(PointF(400, 290));
  ACanvas.DrawPath(LPathBuilder.Detach, LPaint);
end;

Result:结果:

行输出.png

This solution is not restricted to just asterisks and lines.此解决方案不仅限于星号和线条。 See the result using the '@' and a circle:使用“@”和一个圆圈查看结果:

at-sign-output.png

A line has an equation having the form: Y = A * X + B一条线有一个方程,其形式为: Y = A * X + B

A is the slope and B the offset at the origin. A是斜率, B是原点处的偏移量。

If you want to draw a line from point (X1, Y1) to point (X2, Y2) , you have first to determine the A and B constants of the equation:如果要从点(X1, Y1)到点(X2, Y2)画一条线,首先要确定方程的AB常数:

A = (Y2 - Y1) / (X2 - X1)

Once you have A, compute B as :有了 A 后,将 B 计算为:

B = Y1 - A * X1

Now you have A and B , you can use it to compute the intermediate points between X1 and X2 .现在您有了AB ,您可以使用它来计算X1X2之间的中间点。 A simple loop will do.一个简单的循环就可以了。 Increment X by the value you want to have you * separated.X增加您希望*分隔的值。

Note: If Y2 - Y1 is greater than X2 - X1 , you must iterate Y instead of X .注意:如果Y2 - Y1大于X2 - X1 ,则必须迭代Y而不是X

As an excercise, I let you write the code...作为练习,我让你编写代码......

I would use a parametric representation of the line, parameter being the line length so far drawn.我将使用线条的参数表示,参数是到目前为止绘制的线条长度。 This way one can draw vertical lines, and can achieve that the stars are drawn with the same distance, independent of the slope of the line.这样就可以画出垂直线,并且可以实现星星的绘制等距,与线的斜率无关。

To be more precise: To draw a line from point A to point B, compute the length of the line L, then the unit vector Dir of the direction of the line.更准确地说:要从 A 点到 B 点画一条线,计算线 L 的长度,然后计算线方向的单位向量 Dir。 The formula for a point P on the line is then P = A + t*Dir, where t runs from 0 to L. (This is pseudo-code to be read as vector notation.)在线上点 P 的公式为 P = A + t*Dir,其中 t 从 0 到 L。(这是伪代码,可作为矢量符号读取。)

Here is a simple routine which does this.这是一个执行此操作的简单例程。

procedure DrawStarAt(P: TPointF; Radius: Single; aCanvas: TCanvas);
begin
  var
  r := RectF(P.X - Radius, P.Y - Radius, P.X + Radius, P.Y + Radius);
  aCanvas.FillText(r, '*', false, 1, [], TTextAlign.Center, TTextAlign.Center);
end;

procedure DrawStarLine(A, B: TPointF; aCanvas: TCanvas);
var
  // line length
  L,
  // line parameter
  t,
  // step for t
  dt,
  // Radius of the text rectangle
  Radius: Single;

  // Point to be drawn
  P,
  // unit vector for line direction
  Direction: TPointF;
  n: integer;
begin
  aCanvas.BeginScene;
  aCanvas.Fill.Color := TAlphaColorRec.Black;
  Radius := aCanvas.TextWidth('*');
  L := sqrt(sqr(B.X - A.X) + sqr(B.Y - A.Y));
  n:=trunc(L/Radius);
  //adjust dt so the last star is drawn exactly at B
  dt:=L/n;
  if L = 0 then
  begin
    DrawStarAt(A, Radius, aCanvas);
    aCanvas.EndScene;
    exit;
  end;
  Direction := PointF((B.X - A.X) / L, (B.Y - A.Y) / L);
  t := 0;
  while t < L do
  begin
    P := PointF(A.X + t * Direction.X, A.Y + t * Direction.Y);
    DrawStarAt(P, Radius, aCanvas);
    t := t + dt;
  end;
  DrawStarAt(B, Radius, aCanvas);
  aCanvas.EndScene;
end;

Computer scientist Jack Bresenham devised an algorithm to draw straight lines fast on an integer grid.计算机科学家 Jack Bresenham 设计了一种算法,可以在整数网格上快速绘制直线。 The algorithm only uses integer variables and doesn't require division or multiplication.该算法仅使用整数变量,不需要除法或乘法。
You could write the asterisk directly in the Bresenham code, but it's much neater to use a callback procedure: you call the Bresenham procedure with the callback function as extra parameter.您可以直接在 Bresenham 代码中编写星号,但使用回调过程要简洁得多:您可以使用回调函数作为额外参数调用 Bresenham 过程。 Every time the algorithm has calculated a point on the line it calls the callback procedure, passing the X and Y coordinates.每次算法计算出线上的一个点时,它都会调用回调过程,传递 X 和 Y 坐标。
The big advantage is that you could write a generic Bresenham procedure and only write a different action in the callback, depending on whether you want to draw a dot, or an asterisk.最大的优点是您可以编写一个通用的 Bresenham 过程,并且只在回调中编写不同的操作,具体取决于您是要绘制点还是星号。
It goes like this.它是这样的。
You define a callback procedure type:您定义一个回调过程类型:

type
  TCallbackProc = procedure(X, Y: Integer) of Object;

Write out the callback procedure's action.写出回调过程的动作。 Here I draw a pixel.这里我画了一个像素。 You change this line to draw an asterisk:你改变这条线来画一个星号:

procedure TForm1.DrawPixel(X, Y: Integer);
begin
  Image1.Canvas.Pixels[X, Y] := clBlack;
end;

And then the Bresenham procedure itself.然后是 Bresenham 程序本身。 Recall that the procedure finds all points on the line, but doesn't know what to do with them, so it passes them to the callback procedure:回想一下,该过程找到了线上的所有点,但不知道如何处理它们,因此它将它们传递给回调过程:

procedure TForm1.Bresenham(X1, Y1, X2, Y2: Integer; CallbackProc: TCallBackProc);
var
  Dx, Dy, Sx, Sy, Error, E2: Integer;
  Done: Boolean;
begin
  Dx := Abs(X2 - X1);
  if X1 < X2 then
    Sx := 1
  else
    Sx := -1;
  Dy := -Abs(Y2 - Y1);
  if Y1 < Y2 then
    Sy := 1
  else
    Sy := -1;
  Error := Dx + Dy;

  while True do
  begin
    if Assigned(CallbackProc) then
      CallbackProc(X1, Y1);
    if (X1 = X2) and (Y1 = Y2) then
      Exit;
    E2 := 2 * Error;
    if E2 >= Dy then
    begin
      if X1 = X2 then
      Exit;
      Error := Error + Dy;
      X1 := X1 + Sx;
    end;
    if E2 <= Dx then
    begin
      if Y1 = Y2 then
        Exit;
      Error := Error + Dx;
      Y1 := Y1 + Sy;
    end;
  end;
end;

Usage:用法:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Bresenham(100, 50, 250, 350, DrawPixel);
  Bresenham(100, 50, 250, 350, DrawDaisies);
  Bresenham(100, 50, 250, 350, DrawSquirrels);
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM