Snaking ZigZag Curve: Finally worked it out ! lazarus / 39-Graphics ---------------------------------------------------------------------- Boleeman · 2025-04-23 12:46 Hi All. I tried making a Snaking ZigZag curve using Lazarus but the curve does not seem to be working consistently to give a ZigZag geometry (As shown in the 1st attached png). Not sure why it is happening? Wonder if anyone can figure out why? (sort of seems to rest when both TSpinEdits are the same value) Finally worked it out. See last reply. ---------------------------------------------------------------------- Handoko · 2025-04-23 20:12 Bugs fixed. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,  Spin, BGRABitmap, BGRABitmapTypes, Math; type   { TForm1 }   TForm1 = class(TForm)    btnRefresh: TButton;    CheckBox1: TCheckBox;    Label1: TLabel;    Label2: TLabel;    PaintBox1: TPaintBox;    Panel1: TPanel;    seiAcross: TSpinEdit;    sejDown: TSpinEdit;    procedure btnRefreshClick(Sender: TObject);    procedure PaintBox1Paint(Sender: TObject);    procedure SpinChange(Sender: TObject);  private    procedure DrawZigZagPath(across, down: Integer; bmp: TBGRABitmap);  public   end; var  Form1: TForm1; implementation {$R *.lfm} procedure TForm1.DrawZigZagPath(across, down: Integer; bmp: TBGRABitmap);var  points, rotated: array of TPointF;  x, y, stepCount, dx, dy: Integer;  gridX, gridY: Single;  i: Integer;  minX, maxX, minY, maxY, cx, cy: Single;  Alternatif: Boolean;  Temp: TPointF;begin  bmp.Fill(BGRA(0, 0, 0));   //across := seiAcross.Value;  //down := sejDown.Value;   gridX := bmp.Width / (across + 2);  gridY := bmp.Height / (down + 2);   SetLength(points, 0);  x := 0; y := 0;  stepCount := 1;   SetLength(points, Length(points) + 1);  points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);  Inc(y);  SetLength(points, Length(points) + 1);  points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);   while True do  begin    if stepCount mod 2 = 1 then    begin      dx := stepCount;      dy := -stepCount;    end    else    begin      dx := -stepCount;      dy := stepCount;    end;   Inc(x, dx);    Inc(y, dy);    if (x < 0) or (x > across) or (y < 0) or (y > down) then Break;     SetLength(points, Length(points) + 1);    points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);     if stepCount mod 2 = 1 then      Inc(x)    else      Inc(y);     if (x < 0) or (x > across) or (y < 0) or (y > down) then Break;     SetLength(points, Length(points) + 1);    points[High(points)] := PointF((x + 1) * gridX, (y + 1) * gridY);     Inc(stepCount);  end;   // Rotate the path 180° about its center  if Length(points) > 0 then  begin    minX := points[0].X; maxX := minX;    minY := points[0].Y; maxY := minY;     for i := 1 to High(points) do    begin      if points[i].X < minX then minX := points[i].X else if points[i].X > maxX then maxX := points[i].X;      if points[i].Y < minY then minY := points[i].Y else if points[i].Y > maxY then maxY := points[i].Y;    end;     cx := (minX + maxX) / 2;    cy := (minY + maxY) / 2;     SetLength(rotated, Length(points));    for i := 0 to High(points) do    rotated[i] := PointF(2 * cx - points[i].X, 2 * cy - points[i].Y);     Alternatif := False;    if Odd(seiAcross.Value+1) and (sejDown.Value>seiAcross.Value) then      Alternatif := True;    if Odd(seiAcross.Value+1) and (sejDown.Value sejDown.Value) then      Alternatif := True;     for i := 1 to High(rotated) do    begin      if rotated[i].X = rotated[i+1].X-100 then rotated[i].X := rotated[i+1].X;      if not(Alternatif) then Continue;      if i mod 4 = 2 then      begin        Temp := rotated[i];        rotated[i] := rotated[i-1];        rotated[i-1] := Temp;      end;      if i mod 4 = 3 then      begin        Temp := rotated[i];        rotated[i] := rotated[i+1];        rotated[i+1] := Temp;      end;    end;  end;   for i := 0 to High(points) - 1 do    bmp.DrawLineAntialias(points[i].X, points[i].Y, points[i + 1].X, points[i + 1].Y, BGRA(255, 255, 0), 2);   for i := 0 to High(rotated) - 2 do    bmp.DrawLineAntialias(rotated[i].X, rotated[i].Y, rotated[i + 1].X, rotated[i + 1].Y, BGRA(0, 255, 255), 2);end;  procedure TForm1.btnRefreshClick(Sender: TObject);begin Paintbox1.Invalidate;end; procedure TForm1.PaintBox1Paint(Sender: TObject);var  bmp: TBGRABitmap;begin  bmp := TBGRABitmap.Create(PaintBox1.Width, PaintBox1.Height);  try    DrawZigZagPath(seiAcross.Value, sejDown.Value, bmp);    bmp.Draw(PaintBox1.Canvas, 0, 0, True);  finally    bmp.Free;  end;end; procedure TForm1.SpinChange(Sender: TObject);begin  if CheckBox1.Checked then    PaintBox1.Invalidate;end; end. The edited code were lines #120 - #144 and #150. ---------------------------------------------------------------------- Boleeman · 2025-04-24 10:42 Thanks Handoko for help me out. The curved path is now uniform, but the logic in my code is not quite correct. When increasing the across TSpinEdit to say 17, only 10 grid spaces are used to draw the pattern but what should happen is that the remaining 7 grid spaces also need to be used to draw the remaining lines. Also for some reason on my AMD CPU laptop I kept on getting an error when having Checkbox1.checked. After reflecting a bit today I realized I was using using grid spaces, rather than using points across and down (like in the JavaScript version. Need to go back to the drawing board and rethink the algorithm. Thanks Handoko for that updated code. Much appreciated. ---------------------------------------------------------------------- Boleeman · 2025-10-14 12:09 Ah, I finally worked it out: ---------------------------------------------------------------------- Josh · 2025-10-17 09:03 Hi Here is a small version, i am painting on tpanel using on paint, should be simple to mod for anything. using various properties of tpanel for parameters. font.color = line color borderwidth= line width tag = stepx,stepy bevelwidth= border offset Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Panel1Paint(Sender: TObject);var StepX,StepY,MaxX,MaxY,Phase:Integer;    FinishedDrawing:Boolean=false;    DrawPoints:array[0..3] of TPoint;  BorderOffset:Integer; procedure PointCalculator;begin  case Phase of  {Vertical down phases}    0,3:begin          if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);        if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;        end;  {Horizontal right phases}    1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);  end;end; begin  if sender is TPanel then  begin    with Sender as TPanel do    begin      BorderOffset:=BevelWidth;      StepX:=Tag;StepY:=Tag; {Uses tag for step amount}      DrawPoints[0]:= point(BorderOffset,BorderOffset);DrawPoints[1]:=point(BorderOffset,Bor derOffset);      DrawPoints[2]:=point(StepX+BorderOffset,BorderOffset) ;DrawPoints[3]:=point(BorderOffset,StepY+BorderOffset);      MaxX:=width;      MaxY:=height;      Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}      Canvas.Pen.Color:=Font.Color; {line color from font color}      Canvas.Pen.Width:=BorderWidth; {pen width from border width}      Phase:=0;      while not FinishedDrawing do      begin        PointCalculator;        if not FinishedDrawing then Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);        PointCalculator;        inc(Phase);        Phase:=Phase mod 4;      end;    end;  end;end; ---------------------------------------------------------------------- Josh · 2025-10-17 11:50 moded the panel paint to buffer drawing and optional support bgrabitmap, if you create a define in code {$define usingbgrabitmap}  then it uses tbgrabitmap for buffering Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Panel1Paint(Sender: TObject);var StepX,StepY,MaxX,MaxY,Phase:Integer;    FinishedDrawing:Boolean=false;    DrawPoints:array[0..3] of TPoint;  BorderOffset:Integer;    {$ifdef usingbgrabitmap}bmp:tbgrabitmap;    px,py:Integer;    {$else}    bmp:tbitmap;    {$endif} procedure PointCalculator;begin  case Phase of    {Vertical down phases}    0,3:begin          if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);        if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;        end;  {Horizontal right phases}    1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);  end;end; begin  if sender is TPanel then  begin    with Sender as TPanel do    begin      BorderOffset:=BevelWidth;      StepX:=Tag;StepY:=Tag; {Uses tag for step amount}      DrawPoints[0]:= point(BorderOffset,BorderOffset);DrawPoints[1]:=point(BorderOffset,Bor derOffset);      DrawPoints[2]:=point(StepX+BorderOffset,BorderOffset) ;DrawPoints[3]:=point(BorderOffset,StepY+BorderOffset);      MaxX:=width;      MaxY:=height;      {$ifdef usingbgrabitmap}      bmp:=tbgrabitmap.Create(MaxX,MaxY,color);      bmp.AntialiasingDrawMode:=dmSet;      px:=BorderOffset;py:=BorderOffset;      {$else}      bmp:=tbitmap.Create;      bmp.SetSize(maxX,MaxY);      bmp.Canvas.Brush.Color:=Color;      bmp.Canvas.Brush.Style:=bsSolid;    bmp.Canvas.FillRect(0,0,maxx,maxy);      bmp.Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}      bmp.Canvas.Pen.Color:=Font.Color; {line color from font color}      bmp.Canvas.Pen.Width:=BorderWidth; {pen width from border width}      {$endif}      Phase:=0;      while not FinishedDrawing do      begin      PointCalculator;        if not FinishedDrawing then        begin          {$ifdef usingbgrabitmap}          bmp.DrawLineAntialias(px,py, DrawPoints[Phase].X,DrawPoints[Phase].Y,Font.Color,BorderWidth);        px:=DrawPoints[Phase].X;          py:=DrawPoints[Phase].y;        {$else}          bmp.Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);          {$endif}        end;        PointCalculator;        inc(Phase);      Phase:=Phase mod 4;      end;      {$ifdef usingbgrabitmap}      bmp.Draw(canvas,0,0);      {$else}      canvas.Draw(0,0,bmp);      {$endif}      bmp.Free;    end;  end;end; ---------------------------------------------------------------------- Josh · 2025-10-17 13:48 final mod, added ability to makethe line drawn from ink nib setting tpanel wordwrap to true draws pen nib mode.. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.Panel1Paint(Sender: TObject);var StepX,StepY,MaxX,MaxY,Phase:Integer;    FinishedDrawing:Boolean=false;    DrawPoints:array[0..3] of TPoint;  BorderOffset:Integer;    {$ifdef usingbgrabitmap}bmp:tbgrabitmap;    px,py:Integer;    darkColor,midColor,lightColor,blended:TBGRAPixel;  i:Integer;    t,brightness:Single;    {$else}    bmp:tbitmap;    {$endif} procedure PointCalculator;begin  case Phase of    {Vertical down phases}    0,3:begin          if DrawPoints[Phase].y+StepY+BorderOffset>MaxY then inc(DrawPoints[Phase].x,StepX) else inc(DrawPoints[Phase].y,StepY);        if Phase=0 then FinishedDrawing:=DrawPoints[Phase].x+BorderOffset>MaxX;        end;  {Horizontal right phases}    1,2:if DrawPoints[Phase].x+StepX+BorderOffset>MaxX then inc(DrawPoints[Phase].y,StepY) else inc(DrawPoints[Phase].x,StepX);  end;end; {$ifdef usingbgrabitmap}function BlendColor(const c1,c2:TBGRAPixel;t:Single):TBGRAPixel;begin  Result.red:=round(c1.red+(c2.red-c1.red)*t);  Result.green:=round(c1.green+(c2.green-c1.green)*t);  Result.blue:=round(c1.blue+(c2.blue-c1.blue)*t);  Result.alpha:=round( c1.alpha+(c2.alpha-c1.alpha)*t);end;{$endif} begin  if sender is TPanel then  begin    with Sender as TPanel do    begin      BorderOffset:=BevelWidth;      StepX:=Tag;StepY:=Tag; {Uses tag for step amount}      DrawPoints[0]:=point(BorderOffset,BorderOffset);Draw Points[1]:=point(BorderOffset,BorderOffset);      DrawPoints[2]:=point (StepX+BorderOffset,BorderOffset);DrawPoints[3]:=point(BorderOffset,St epY+BorderOffset);      MaxX:=width;      MaxY:=height;      {$ifdef usingbgrabitmap}      bmp:=tbgrabitmap.Create(MaxX,MaxY,color);      bmp.AntialiasingDrawMode:=dmSet;      px:=BorderOffset;py:=BorderOffset;      {$else}      bmp:=tbitmap.Create;      bmp.SetSize(maxX,MaxY);      bmp.Canvas.Brush.Color:=Color;      bmp.Canvas.Brush.Style:=bsSolid;    bmp.Canvas.FillRect(0,0,maxx,maxy);      bmp.Canvas.MoveTo(BorderOffset,BorderOffset); {Initial Point}      bmp.Canvas.Pen.Color:=Font.Color; {line color from font color}      bmp.Canvas.Pen.Width:=BorderWidth; {pen width from border width}      {$endif}      Phase:=0;      while not FinishedDrawing do      begin      PointCalculator;        if not FinishedDrawing then        begin          {$ifdef usingbgrabitmap}          if wordwrap then          begin            darkColor:=BGRA(60,60,60);            midColor:=ColorToBGRA(Font.Color);            lightColor:=BGRA(255,255,255);            for i:=-BorderWidth div 2 to BorderWidth div 2 do            begin              t:=(i+BorderWidth/2)/BorderWidth;              brightness:=Power(t,0.6); // non-linear highlight              blended :=BlendColor(BlendColor(darkColor,lightColor,brightness),midColor, 0.4);              bmp.DrawLineAntialias(px+i,py+i,DrawPoints[Phase].X +i,DrawPoints[Phase].Y+i,blended,1);            end;          end else bmp.DrawLineAntialias(px,py,DrawPoints[Phase].X,DrawPoints[Phase].Y,Fo nt.Color,BorderWidth);          px:=DrawPoints[Phase].X;          py:=DrawPoints[Phase].y;          {$else}          bmp.Canvas.LineTo(DrawPoints[Phase].X,DrawPoints[Phase].Y);          {$endif}        end;        PointCalculator;        inc(Phase);      Phase:=Phase mod 4;      end;      {$ifdef usingbgrabitmap}      bmp.Draw(canvas,0,0);      {$else}      canvas.Draw(0,0,bmp);      {$endif}      bmp.Free;    end;  end;end; ---------------------------------------------------------------------- Boleeman · 2025-10-17 14:37 Josh I love the way you got it to resize by resizing the panel. Impressive. Was so curious that I experimented with your {$define usingbgrabitmap}  version. Wow that 3D ink line is also a nice feature. Thought it might be nice to somehow add a glow effect to the line like in my glow fractal tree: https://forum.lazarus.freepascal.org/index.php/topic,70280.msg547679.h tml#msg547679 Blown away with your coding. ---------------------------------------------------------------------- Josh · 2025-10-17 15:58 zigy glow, setting tpanel enabled will generate glow and only if glow intensity>0 ---------------------------------------------------------------------- Josh · 2025-10-17 22:58 Moded it so its a self contained procedure Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Procedure DrawigZagOnCanvas(Const ACanvas:TCanvas;AWidth,AHeight:Integer;ABackGr oundColor,APenColor:TColor;                            APenWidth:Integer;StepX,StepY:Integer;BorderSize:Integer;                          GlowEffect:Boolean;GlowStrength:Single;ANibPen:Boolean); You can then put it in onpaint event Demo left is tpanel, right is paintbox,with different StepX and StepY values,which creates a distorted diag.