TFPCustomCanvas Arc Methode, Verständnisfrage

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Hallo,

Ich sehe zwei Arc Methoden

die erste: Arc(left,top,right,bottom, Angle16Deg,Angle16DegLength: Integer);

die zweite: Arc(left,top,right,bottom,SX,SY,EX,EY: integer);

Welche Bedeutung haben in der zweiten Methode die Parameter SX,EX,SY,EY???

Sollten das in diesem Fall die Schnittpunkte des Bogens mit dessenn Enden sein, wie berechne ich die dann?

Ich habe folgende Ellipsengleichung:

Code: Alles auswählen

procedure PaintEllipse(Canv: TFPCustomCanvas; xm, ym, xR, yR, stangle, endangle: Extended; numSteps: Integer; c: TFPColor);
   var
    t, tStart, tEnd, tStep: Extended;
    i,j: Integer;
    x, y: Extended;
    MyPutPix: TPutPixelProc;

  procedure DrawPixel(Canv: TFPCustomCanvas; x,y: Longword);
  begin
      //für meine Frage uninteressant
  end;

 begin
      if numSteps < 1 then Exit; // Sicherheitsprüfung

      // Winkel in Radiant umrechnen
      tStart := DegToRad(stangle);
      tEnd := DegToRad(endangle);

      // Normierter Parameter t von tStart bis tEnd
      tStep := (tEnd - tStart) / numSteps;

      with canv.pen do
        case mode of
          pmMask : MyPutPix := @PutPixelAnd;
          pmMerge : MyPutPix := @PutPixelOr;
          pmXor : MyPutPix := @PutPixelXor;
        else MyPutPix := @PutPixelCopy;
      end;


      // Zeichne Punkte entlang der Ellipse
      for i := 0 to numSteps do
      begin
        t := tStart + i * tStep;
        // Parametrische Darstellung der Ellipse
        x := xm + xR * Cos(t); if t=tStart  then xs:=x; if t=tEnd then xe:=x; //ist das richtig, dann hätte ich die Positionen von SX,SY,EX,EY
        y := ym + yR * Sin(t); if t=tStart  then ys:=y; if t=tEnd then ye:=y;
        DrawPixel(Canv, Round(x), Round(y)); // Zeichne Pixel

      end;
   end;

Dann aber müsste ich dies Parameter umständlich vorher berechnen, wenn ich diese Methode benutzen will. Gibt es eine elegantere Berechnungsmrthode dafür oder interpretiere ich SX,SY,EX,EY ohnehin falsch? Leider sind diese Parameter in der FPCanvas Doku auch nicht erklärt

wp_xyz
Beiträge: 5221
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Nein, das ist einfach nur irgendein Punkt in der Ebene. Es wird intern der Mittelpunkt der Ellipse mit diesem Punkt verbunden, und dort, wo die Linie die Ellipse schneidet, beginnt/endet der Bogen; die Rechnung muss du selbst nicht ausführen.

Das ist wahrscheinlich deswegen so gemacht, weil mit diesen Argumenten die Endpunkte klar definiert sind. Nimmt man dagegen die andere Variante mit den Winkel, ist unklar welcher Winkel gemeint ist. Ist es der Winkel, den so eine Verbindungslinie mit der x-Achse hat? Oder ist es der Winkel in den Ellipsengleichungen x = a * cos(phi), y = b * sin(phi)? Beide sind nicht identisch! Ich habe dazu vor kurzem einen Bug bearbeitet (https://gitlab.com/freepascal.org/lazar ... sues/41748), in dem sich eine Inkonsistenz der Arc-Methode zwischen win32 und den Linux-Widgetsets herausgestellt hat. In Laz/main wird künftig die zweite Definition (die mit den Ellipsengleichungen) verwendet.

Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Danke für die schnelle Antwort. Habe mir die .zip Datei runter geladen aber eine Frage ist noch offen:

So wie ich das verstehe, sind die Punkte SX,SY,EX,EY bisher beliebige Punkte der Ebene gewesen, deren Verbindungslinie vom Mittelpunkt der Ellipse zum Schnittpunkt Ellipse Verbindungslinie Mittelpunkt<-> Schnittpunkt auch diese Pukte SX,SY,EX,EY schnneiden, Nun aber sind das keine beliebigen Ounkte mehr sondern werden nach der Ellipsengleichung berechnet. Also wie in der von mir aufgeführten Methode. Da tritt aber ein neues Problem auf. wie kommt der Multiplikator 1000 zustande? C ist nach meinem Verständnis der Mittelpunkt der Ellipse ,P1, P2, die beiden Schnittpunkte mit der Ellipse, bei mie SX,SY,EX und EY und damit Endpunkte des Bogens. Warum aber muss da der Multiplikator, hier 1000 hinzugefügt werden? Warum gerade die 1000? ist das sowas wie in meiner Version die Anzahl der Steps?

Und unten in der PaintBox der Summand +/-5 ist mir in der Bedeutung auch unklar. Oder ist das eine Linienstärke?

Hier:

Code: Alles auswählen

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  R: TRect;
  C, P1, P2: TPoint;
  phi1, phi2: Double;
begin
  R := Rect(10, 10, 210, 110);
  C := Point((R.Left + R.Right) div 2, (R.Top + R.Bottom) div 2);

  Paintbox1.Canvas.Brush.Style := bsClear;
  Paintbox1.Canvas.Pen.Style := psDash;
  Paintbox1.Canvas.Pen.Color := clSilver;
  Paintbox1.Canvas.Rectangle(R);

  Paintbox1.Canvas.Pen.Style := psSolid;
  Paintbox1.Canvas.Pen.Color := clRed;
  Paintbox1.Canvas.Brush.Color := clRed;
  {$IFDEF FPC}
  if RadioButton2.Checked or RadioButton3.Checked then
    Paintbox1.Canvas.RadialPie(R.Left, R.Top, R.Right, R.Bottom, Scrollbar1.Position * 16, Scrollbar2.Position * 16);
  if RadioButton1.Checked or RadioButton3.Checked then
  begin
    Paintbox1.Canvas.Pen.Color := clBlue;
    Paintbox1.Canvas.Pen.Width := 3;
    Paintbox1.Canvas.Arc(R.Left, R.Top, R.Right, R.Bottom, Scrollbar1.Position * 16, Scrollbar2.Position * 16);
    Paintbox1.Canvas.Pen.Width := 1;
  end;
  {$ELSE}
  phi1 := Scrollbar1.Position * pi/180;
  phi2 := (Scrollbar1.Position + Scrollbar2.Position) * pi/180;
  P1 := C + Point(round(cos(phi1) * 1000), round(-sin(phi1) * 1000));
  P2 := C + Point(round(cos(phi2) * 1000), round(-sin(phi2) * 1000));
  Paintbox1.Canvas.Arc(R.Left, R.Top, R.Right, R.Bottom, P1.X, P1.Y, P2.X, P2.Y);
  {$ENDIF}

  // Draw angle lines (lines from center to far-away point in direction defined by angle w.r.t x axis)
  phi1 := Scrollbar1.Position * pi/180;
  phi2 := (Scrollbar1.Position + Scrollbar2.Position) * pi/180;
  P1 := C + Point(round(cos(phi1) * 1000), round(-sin(phi1) * 1000));
  P2 := C + Point(round(cos(phi2) * 1000), round(-sin(phi2) * 1000));
  Paintbox1.Canvas.Pen.Color := clSilver;
  Paintbox1.Canvas.MoveTo(C.X, C.Y);
  Paintbox1.Canvas.LineTo(P1.X, P1.Y);
  Paintbox1.Canvas.MoveTo(C.X, C.Y);
  Paintbox1.Canvas.LineTo(P2.X, P2.Y);

  // Calculate ellipse points (points on ellipsis at angles)
  P1 := Point(round(cos(phi1) * ((R.Right - R.Left) div 2)) + C.X, round(-sin(phi1) * ((R.Bottom - R.Left) div 2)) + C.Y);
  P2 := Point(round(cos(phi2) * ((R.Right - R.Left) div 2)) + C.X, round(-sin(phi2) * ((R.Bottom - R.Left) div 2)) + C.Y);
  Paintbox1.Canvas.Brush.Style := bsSolid;
  Paintbox1.Canvas.Brush.Color := clWhite;
  Paintbox1.Canvas.Ellipse(P1.X - 5, P1.Y - 5, P1.X + 5, P1.Y + 5);
  Paintbox1.Canvas.Ellipse(P2.X - 5, P2.Y - 5, P2.X + 5, P2.Y + 5);

end;

wp_xyz
Beiträge: 5221
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Du beziehst dich auf das "arc_radialpie.zip"-File? Der Faktor 1000 ist der Abstand der Punkte (SX,SY) und (EX,EY) vom Ellipsenmittelpunkt; der Wert ist völlig egal, er sollte nur nicht zu klein sein, weil der Abstand ja mit sin und cos des Winkels multipliziert wird, und da erhält man nach dem Runden auf Integer bei zu kleinem Abstand einfach zu ungenaue Werte.

Das +/-5 hat nichts mit dem Zeichnen des Arcs zu tun. Es soll da nur ein kleiner Kreis gezeichnet werden, um die Endpukte des Bogens hervorzuheben.

Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Ich habe das Projekt gerade in der Bearbeitung, Ich versteh noch nicht warum der Winkel zwischen den Kreisen die die Bogenenden markieren, je nach Stellung der Scrollbars auch mal auf dem Bogen liegen, nicht an den Enden. Wenn ich den Scrollbar für StartAngle verändere liegen die kleinen Kreise auch mal außerhalb des Bogens. Ich nehme an, der rote Bereich markiert den eingeschlossenen Winkel. Der wird mit dem ScrollBar "Sweep Angle" verändert, der unterscheidet sich aber immer vom Winkel zwischen den beiden anderen Strecken ( Strahlen), die vom Ellipsenmittelpunkt aus über das Ellipsenrechteck (das die Ellipse umschließende Rechteck) hinaus gehen. Warum ist das so?
Du schreibst ja selber oben dass der Mittelpunkt der Ellppse mit den Punkten SX,SY, EX,EY verbunden wird, Dann werden die Schnittpunkte mit der Ellipse also intern berechnet. Soweit Ok, aber warum habe ich dann im Besidpileprojekt das hier oben in meiner Frage genannte Phänomen?

wp_xyz
Beiträge: 5221
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Ich habe nochmal ein kleines Programm geschrieben, das die Verhältnisse darstellen soll. Leider ist es unter Windows nicht sehr aussagekräftig, weil eine der beiden Arc-Routinen in den Lazarus-Release-Versionen da einen Bug hat. Ich hänge daher auch noch einen Screenshot an, der mit Laz/main gemacht wurde, wo der Fehler behoben ist (oder du arbeitest mit Linux, wo das Problem gar nicht auftritt).

Die linke Paintbox in dem Screenshot zeigt den Ellipsenbogen, der mit der Arc-Prozedur gezeichnet wurde, die die SX, SY, EX, EY-Argumente verwendet. Die Punkte (SX,SY) und (EX,EY) (im Programm P1 und P2) sind so gewählt, dass sie vom Ellipsenmittelpunkt 1000 Einheiten entfernt sind und die Verbindungslinien zum Mittelpunkt mit der x-Achse den Winkel 45° bzw 180° einschließen. Der so gezeichnete Bogen beginnt genau dort, wo diese Verbindungslinien die Ellipse schneiden.

Die rechte Paintbox zeigt den Ellipsenbogen, bei dem die Arc-Prozedur mit den Winkelargumenten aufgerufen wurde. Nimmt man an, dass diese Winkel dieselben wären, die in dem ersten Beispiel verwendet wurden, so sieht man dass der Ellipsenbogen an einer anderen Stelle beginnt als dem Schnittpunkt der Winkellinie mit der Ellipse. Das liegt daran, dass das Winkelargument hier nicht der geometrische Winkel zwischen dieser Geraden und der x-Achse ist, sondern das Winkelargument in den Ellipsengleichungen, mit dem man jeden Punkt auf der Ellipse beschreiben kann (x = a * cos(angle), y = b * sin(angle)). Die beiden wären identisch im Fall eines Kreises, doch im Fall der gezeigten Ellipse ist der y-Wert im Verhältnis b/a gestaucht (oder gestreckt), und damit ändert sich auch der Winkel.

Wiegesagt, das kannst du mit den Lazarus-Release-Versionen unter Windows nicht nachvollziehen, weil da die Start- und Endpunkte falsch berechnet werden.
Dateianhänge
arc_SxSyExEy_angle_sweep.zip
(2.78 KiB) 180-mal heruntergeladen
arc.png
arc.png (10.88 KiB) 1104 mal betrachtet

Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Bei mir, Lazarus 3.8 mit FPC 3.2.2 64 Bit sehe ich beide Ellipsenbögen exakt abgeschlossen.

Sollte dies der Fehler sein, frage ich mal wie Windows das in seinem API macht?
Und wie macht linux das, der XServer?

Auf jeden Fall wäre da eine fehlerbereinigte Arc Routine mit diesen Parametern hilfreich. Das klint ja hier nach fehlerfafter Berechnung in der Arc Routine. Demnach wäre ja die Math Unit korrekt. Also bracht es den fehlerbereinigten Algotithmus. Der müsste denn in einer Codesammling gespeichert werden. Gibt es den schon? Ich meine in irgendeinem Archiv! Denn wenn die Funktion nur unter Windows inkorrekt ist, müsste der korrekte Code ja irgendwo schon vorliegen. Ich könnte ja wenn die Arc Funktion mit diesen Parametern im Windows API korrekt ist, auch diese verwenden, aber jartz habe ich einmal mit dieser Ellipsenrouine experimentiert, da wäre dann wuch eine eigene vom System unabhängige Funktion schön. Wenn die Exzentritztät der Ellipse das Problem ist müsste ja die verwendete Ellipsenglichung nur ergänzt werden. Sollte ich da experimetieren oder gibt es den Algo schon? Ich habe ne Mathe Fomelsammlung hier, den Guten Alten Bartsch!

Mathias
Beiträge: 6989
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mathias »

Sollte dies der Fehler sein, frage ich mal wie Windows das in seinem API macht?
Und wie macht linux das, der XServer?
Dies wird kaum der X-Server machen, das macht GTK oder QT je nachdem was du eingestellt hast.

Über dieses Thema hatte ich mal eine längere Diskussion: viewtopic.php?p=132706&hilit=linie#p132706

Ich habe es mit Mint QT/GT2(Bild 2) und Wine(Bild 1) getestet :
Dateianhänge
Bildschirmfoto vom 2025-07-28 16-42-05.png
Bildschirmfoto vom 2025-07-28 16-42-05.png (14.47 KiB) 687 mal betrachtet
Bildschirmfoto vom 2025-07-28 16-40-32.png
Bildschirmfoto vom 2025-07-28 16-40-32.png (17.2 KiB) 687 mal betrachtet
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Nun gut, da will ich mir das Diskussionsforum zunächst angucken. Spontan ergibt sich aufgrund Studium der Bartsch-Formelsammlung die folgende Frage:

es gibt da 2 Formen der Exzentritzität einer Ellipse, die lineare und die numerische. Scheint also nicht ganz trivial zu sein, da die Endpinkte des Bogens exakt zu bestimmen.
Ich versuch jetzt mal die numerische Exzentritzität dazu zu addieren oder zu multiplizieren rsp. die Umkeroperationen zu verwenden, vielleicht kann ich das Problem schon damit lösen, wenigstens näherungsweise.

Immerhin wird in dem Ellipsenbeispielen der Winkel immer mit pi/180 berechnet wie bei einem exakten Kreis. Wenn nun aber die Exzenzitzität dazu kommt??? Immerhin gibt es ja auch die Polargleichung der Ellipse. Da ist die Berücksichtigung der Exzentritzität allerdings komplexer. Aber wenigstens kann man damit den jeweiligen Ellipsenradius bestimmen und der könnte schon mal sehr hilfreich sein, um den Schnittpunkt der Ellipse mit einer Geraden an einer gesuchten X,Y Position zu bestimmen.

@Matthias: Ich habe das Beispielprojekt "arcSxSyExEy_angle_sweep.zip" herunter geladen und mit Lazarus 3.8 FPC 3.2.2 64 Bit compiliert und ausgeführt. In meiner Version enden die Bögen in beiden Paintboxen exakt an den Geraden. Ist der Fehler etwa in der 64 Bit Version behoben und nur noch in der 32 BIt Version präsent?

@wp_xyz:
Das liegt daran, dass das Winkelargument hier nicht der geometrische Winkel zwischen dieser Geraden und der x-Achse ist, sondern das Winkelargument in den Ellipsengleichungen, mit dem man jeden Punkt auf der Ellipse beschreiben kann (x = a * cos(angle), y = b * sin(angle)). Die beiden wären identisch im Fall eines Kreises, doch im Fall der gezeigten Ellipse ist der y-Wert im Verhältnis b/a gestaucht (oder gestreckt), und damit ändert sich auch der Winkel.
-------------------------------------------------------------------------------------
Ich habe in der Bartsch- Formelsammling noch die Polargleichung der Ellipse gefunden:

sqr(r) := sqr(b)/(1-sqr(epsilon)*sqr(cos(phi))

phi -> Winkel zwischen X-Achse und Redius

epsilon -> besagte numerische Exzentritzität.

Ich werde auf jeden Fall versuchen, damit die exakten Schnittpunkte zu berechnen. Wenn GTK das schon kann, muss es ja auch die Exzentitzität der Ellipse berücsichtigen. Ich vermite sehr stark dass das GTK genauso rechnet. Ohne den GTK Quellcode bisher gesehen zu haben.

In den Quelltextauzügen im verlinkten Forum sehe ich allerdings keine Bezüge zu epsilon, der Exzenzritzität.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6885
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von af0815 »

Mario Peters hat geschrieben: Mo 28. Jul 2025, 18:54 In den Quelltextauzügen im verlinkten Forum sehe ich allerdings keine Bezüge zu epsilon, der Exzenzritzität.
Vielleicht als Erinnerung, Lazarus (und FPC) und die ganzen Bibliotheken liegen im Quellcode vor. Somit sind alle Quelltexte uneingeschränkt einsehbar. Und auch schon am Rechner vorhanden.

Man kann sich auch den letzten Stand der Entwicklung (main bzw. trunk genannt) Seite an Seite mit der stabilen Version installieren, wenn man zum Beispiel das Verhalten nach einem gefixten Bug sich ansehen will, ohne auf die nächste Release zu warten.

Nur so als Hinweis und Ergänzung zu dem was wp bereits darüber gesagt hat. Man darf auch nicht Vergessen die unbelohnte Arbeit zu würdigen. Lazarus bzw. FPC sind reine Open Source Entwicklungen, die 'nebenbei' gemacht werden. Lazarus und FreePascal sind nicht kostenpflichtig, diesen Umstand auch berücksichtigen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

wp_xyz
Beiträge: 5221
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Mario Peters hat geschrieben: Mo 28. Jul 2025, 14:10 Bei mir, Lazarus 3.8 mit FPC 3.2.2 64 Bit sehe ich beide Ellipsenbögen exakt abgeschlossen.
Ich hatte ja geschrieben, dass du das mit einer Lazarus-Release-Version auf Windows nicht so siehst wie in meinem Screenshot mit Laz/main, wo der Bug behoben ist.
Mario Peters hat geschrieben: Mo 28. Jul 2025, 14:10 Sollte dies der Fehler sein, frage ich mal wie Windows das in seinem API macht?
Gar nicht. Das Windows-API kennt nur die Arc-Version mit den SX,SY,EX,EY-Parametern (linkes Bild im Screenshot). Entsprechend kennt auch Delphi nur diese Version.
Mario Peters hat geschrieben: Mo 28. Jul 2025, 14:10 Und wie macht linux das, der XServer?
Die gtk und qt-Widgetsets kennen wiederum nur die Arc-Version mit den Winkel-Argumenten, wobei, wie immer wieder betont, diese keine geometrischen Winkel sind, sondern die Parameter phi in den Ellipsengleichungen (x = a * cos(phi), y = b * sin(phi)). In den Kommentaren zu den intern benötigten Routinen in der GraphMath-Unit sind sie als "eccentric angles" bezeichnet - dieser Begriff ist in https://byjus.com/jee/eccentric-angle-a ... n-ellipse/ erläutert.

Jetzt wird's kompliziert - das habe ich gerade selbst erst gemerkt, als ich mein Testprogramm aus dem vorigen Post unter Linux habe laufen lassen (so wie Mathias, bevor ich das posten konnte). Da es unter Linux die SX-SY-EX-EY-Variante der Arc-Prozedur nicht nativ gibt, hat ein früherer Lazarus-Entwickler die Winkelvariante für Lazarus neu programmiert (genauso wie er umgekehrt in Windows die nativ nicht vorhandene Winkel-Variante nachgebaut hat). Dabei hat er sich auf die für die jeweils vorhandene Winkel-Definition verlassen, und so sind für Windows und Linux jeweils verschiedenes Verhalten entstanden. Der Unterschied in Windows ist in Laz/Main behoben. Aber wie man in Mathias' Screenshot von Linux sieht, hat man nun ein anderes Verhalten bei den dort nachgebauten SX-SY-EX-EY-Varianten: Während bei Windows im linken Bild der Bogen an der durch den geometrischen Winkel definierten Linie beginnt, erfolgt dieser bei Linux früher, genau dort, wo er bei der Winkel-Variante (rechtes Bild) liegt.
Mario Peters hat geschrieben: Mo 28. Jul 2025, 14:10 da wäre dann wuch eine eigene vom System unabhängige Funktion schön.
Das ist nach obiger Bemerkung jetzt ganz vertrackt. Die Grundfrage ist: Welches Verhalten ist richtig?

Bei Fragen nach "richtig" ode "falsch" ist oft die Antwort: "So wie es Delphi macht".

In unserem Fall kennt Delphi nur die SX-SY-EX-EY-Variante - dieses Verhalten (unter Windows) ist also "richtig". Die entsprechende Linux-Variante, die diesem widerspricht, ist somit "falsch".

Und was ist mit der anderen Arc-Routine, der mit den Winkelargumenten? Unter Windows erstreckt sich dieser Bogen unter den Lazarus-Release-Versionen, so wie du eben auch geschrieben hast, zwischen den Schnittpunkten der Ellipse mit den Richtungslinien der SX-SY-EX-EY-Variante. Ist das richtig? In diesem Fall gibt es keinen Vergleich mit Delphi, das diese Routine gar nicht hat. Ich meine, dieses Verhalten ist wenig sinnvoll. Zum einen: Wieso sollte man dafür eine eigene Routine spendieren, wenn das Verhalten schon in der SX-SY-EX-EY-Variante enthalten ist? Zum anderen: Nehmen wir zwei Kreis-Bögen, der eine verläuft zwischen 0 und 45° und der andere zwischen 45 und 90°, beide sind gleich lang. Nun stauchen wir den vertikalen Radius. Weil die 45°-Linie weiterhin beide Bögen trennt, wird der obere Bogen zwischen 45 und 90° immer kürzer im Vergleich zum unteren Bogen zwischen 0 und 45°. Das kann man so akzeptieren, hat aber den Nachteil, dass, wenn der Bogen Bestandteil eines Pie-Charts ist, dann werden die Anteile der Tortenstücke verzerrt.

Und jetzt zu Linux: Dort beginnt und endet der Bogen nicht an den Schnittpunkten mit den Winkellinien, sondern an den Punkten, die man erhält, wenn man die genannten Winkel in die Ellipsengleichung einsetzt: xStart = a * cos(startWinkel), yStart = b*sin(startwinkel), und genauso für den Endwinkel.

Daher bin ich der Überzeugung, dass das Verhalten in den Lazarus-Release-Versionen für Linux richtig und für Windows fehlerhaft ist.

Mit dieser Erkenntnis kann man, die Arc-Routine mit den Winkelargumenten in Windows fixen (das ist in der Trunk-Version von Lazarus schon geschehen), und genauso muss man die Arc-Routine in Linux/mac mit den SX-SY-EX-EY Argumenten noch reparieren.

Unabhängig von der Lazarus-Version, kann man sich aber auch eigene "universelle" Arc-Routinen schreiben, die in allen "Welten" gleich funktionieren. Dabei kann man für die Routine mit den SX-SY-EX-EY-Argumenten davon ausgehen, dass die Windows-Version richtig ist und muss für die anderen Widgetsets nachkorrigieren:

Code: Alles auswählen

uses
  GraphMath;
  
procedure UniversalArc(Canvas: TCanvas; Left, Top, Right, Bottom: Integer; SX, SY, EX, EY: Integer);
{$IFNDEF LCLWin32}
var
   startAngle: extended = 0.0;
   endAngle: extended = 0.0;
   sweepAngle: extended = 0.0;
   sinAngle, cosAngle: Extended;
   a, b: Integer;
{$ENDIF}
begin
  {$IFDEF LCLWin32}
  Canvas.Arc(Left, Top, Right, Bottom, SX, SY, EX, EY);  // Das ist unter Windows richtig
  {$ELSE}
  a := Right - Left;
  b := Bottom - Top;
  Coords2Angles(Left, Top, a, b, SX, SY, EX, EY, startAngle, sweepAngle);
  startAngle := DegToRad(startAngle / 16);
  sweepAngle := DegToRad(sweepAngle / 16);
  endAngle := startAngle + sweepAngle;
  SinCos(startAngle, sinAngle, cosAngle);
  startAngle := RadToDeg(ArcTan2(a*sinAngle, b*cosAngle));
  SinCos(endAngle, sinAngle, cosAngle);
  endAngle := RadToDeg(ArcTan2(a*sinAngle, b*cosAngle));
  if endAngle < 0 then endAngle := endAngle + 360;
  sweepAngle := endAngle - startAngle;
  Canvas.Arc(Left, Top, Right, Bottom, round(startAngle*16), round(sweepAngle*16));
  {$ENDIF}
end;
Und bei der Routine mit den Winkel-Argumenten kann man davon ausgehen, dass die Nicht-Windows-Version richtig ist und man muss für Windows korrigieren:

Code: Alles auswählen

// In Laz/main bereits in Unit GraphMath enthalten.
procedure EllipseParams2Coords(X, Y, Width, Height: Integer;
  t1, t2: extended; out SX, SY, EX, EY: Integer);
var
  sin_t1, cos_t1, sin_t2, cos_t2: Extended;
  a, b: Double;
begin
  SinCos(t1, sin_t1, cos_t1);
  SinCos(t2, sin_t2, cos_t2);
  a := Width/2;
  b := Height/2;
  SX := X + round(a + cos_t1 * a);
  SY := Y + round(b - sin_t1 * b);
  EX := X + round(a + cos_t2 * a);
  EY := Y + round(b - sin_t2 * b);
end;

procedure UniversalArc(canvas: TCanvas; Left, Top, Right, Bottom: Integer;
  StartAngle16, SweepAngle16: Integer);
{$IFDEF LCLWin32}
var
  a, b, SX, SY, EX, EY: Integer;
  tStart, tEnd: Double;
{$ENDIF}
begin
  {$IFDEF LCLWin32}
  a := Right - Left;
  b := Bottom - Top;
  tStart := DegToRad(StartAngle16 / 16);
  tEnd := DegToRad((StartAngle16 + SweepAngle16) / 16);
  EllipseParams2Coords(Left, Top, a, b, tStart, tEnd, SX, SY, EX, EY);
  Canvas.Arc(Left, Top, Right, Bottom, SX, SY, EX, EY);
  {$ELSE}
  Canvas.Arc(Left, Top, Right, Bottom, StartAngle16, SweepAngle16);  // Das ist unter Linux/Mac richtig
  {$ENDIF}
 end;     

wp_xyz
Beiträge: 5221
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Das Thema ist ziemlich verwirrend wegen der vielen Widersprüche, und bin es noch einmal anders angegangen.

Zuerst eine Begriffsdefinition wegen der Winkel: Mit Polar-Winkel bezeichne ich den Winkel zwischen der x-Achse und der Verbindungslinie zwischen Ellipsenmittelpunkt und einem Punkt auf dem Ellipsenrand (weil er ein Teil der "Polar-Koordinaten" ist. Mit exzentrischem Winkel meine ich den Winkel phi, den man in die Ellipsengleichungen als Parameter in die cos- und sin-Funktionen einsetzt: x = a * cos(phi) und y = b * sin(phi). Dabei sind a und b die Ellipsen-Halbachsen und x,y ein Punkt des Ellipsenrandes.

Und noch eine Zusammenfassung des vorigen Beitrags: In Windows gibt es nativ nur die Arc-Methode mit den Punkt-Parametern (SX, SY, EX, EY), wobei die Punkte (SX, SY) und (EX, EY) beliebige Punkte der xy-Ebene sind; der Schnittpunkt der Verbindungslinie jedes dieser Punkte zum Ellipsenmittelpunkt mti dem nächsten Ellipsenrand definiert den Start- bzw. Endpunkt des Bogens. In den anderen Widgetsets dagegen gibt es nativ nur die Arc-Methode mit den WInkel-Argumenten (Angle16Deg und AngleLength16Deg), wobei das exzentrische Winkel zum Startpunkt bzw. für die Bogenlänge sind (in 1/16-tel Grad ausgedrückt).

Da Lazarus in jedem Widgetset beide Aufrufsmethoden unterstützt, muss die jeweils fehlende durch Pascal-Code programmiert werden. Ziel muss sein, dass sich Arc mit denselben Aufrufparametern in allen Widgetsets gleich verhält.

In Windows geschieht die Anpassung in der Klassen-Function TWin32WidgetSet.Arc (in lcl/interfaces/win32/win32winapi.inc) durch Aufruf von Angles2Coords in der GraphMath-Unit (in components/LazUtils). Die wiederum ruft in derselben Unit die Funktion RadialPoint und die braucht EllipseRadialLength auf:

Code: Alles auswählen

function EllipseRadialLength(const Rect : TRect; EccentricAngle : Extended) : Longint;
var
  a, b, R : Extended;
  sinAngle, cosAngle: Extended;
begin
  a := (Rect.Right - Rect.Left) div 2;
  b := (Rect.Bottom - Rect.Top) div 2;
  R := Sqr(a)*Sqr(b);
  if R <> 0 then
  begin
    SinCos(DegToRad(EccentricAngle/16), sinAngle, cosAngle);
    R := Sqrt(R / (sqr(b*cosAngle) + sqr(a*sinAngle)));
  end;
  Result := TruncToInt(R);
end;
EllipseRadialLength berechnet die Länge der Verbindung vom Mittelpunkt zu einem Punkt am Ellipsenrand, der durch den gegebenen EccentricAngle definiert wird. Beachte, die Namensgebung: "EccentricAngle" soll wohl suggerieren, dass das ein "exzentrischer" Winkel ist, kein "polarer" Winkel - dazu gleich mehr!

In Linux (gtk2, aber gtk3, qt, qt5, qt6 sind ähnlich) wird man beim Aufruf der fehlenden Arc-Methode mit den Punkt-Argumenten vom Debugger in die Methode TWidgetset.RadialArc in lcl/include/intfbaselcl.inc geführt, wo Coords2Angles aufgerufen wird, also das Gegenstück zur o.g. Angles2Coords, wieder in Unit GraphMath. Das Herzstück ist hier die Funktion EccentricAngle, die aus dem Rechteck-Rahmen um die Ellipse und einem Ellipsenpunkt den zugehörigen exzentrischen Winkel berechnen soll:

Code: Alles auswählen

function EccentricAngle(const PT : TPoint; const Rect : TRect) : Extended;
var
  CenterPt : TPoint;
  Quad : Integer;
  Theta : Extended;
begin
  CenterPt := CenterPoint(Rect);
  Quad := Quadrant(Pt,CenterPt);
  Theta := -1;
  Case Quad of
    1..4:
      begin
        Theta := Distance(CenterPt,Pt);
        If Theta > 0 then
          Theta := RadToDeg(ArcSin(ABS(PT.Y - CenterPt.Y) / Theta));
      end;
  end;
  // hier folgt eine Anpassung des Winkel je nach Quadrant, die hier nicht wichtig ist... 
Auch dazu gleich mehr.

Ich habe mir nun ein kleines Programm geschrieben, in dem ich einfach diese grundlegenden Hilfsfunktionen nachbaue. Das Programm zeichnet eine Ellipse, mit einer Trackbar legt man einen Winkel fest, der als "exzentrischer Winkel" zu verstehen ist, also als Argument für die Ellipsengleichungne. Damit wird der zugehörige Punkt P auf dem Ellipsenrand berechnet (P.X := a*cos(eccAngle), P.Y := b*sin(eccAngle)). Zum Vergleich berechne ich aus P mit der ArcTan2-Funktion auch den Polarwinkel. Es wird auch der die Ellipse umschreibende Kreis gezeichnet mit dem Punkt Q, der sich aus P durch Verlängerung von y bis zum Kreis ergibt; dessen Winkel mit der x-Achse ist eine direkte geometrische Veranschaulichung des exzentrischen Winkel, weil beim Kreis Polarwinkel = exzentrischer Winkel ist.

Damit kann ich die unter Windows verwendete Funktion EllipseRadialLength nachbauen: das ist einfach der Abstand zwischen dem Ellipsenmittelpunkt und dem Punkt P auf dem Ellipsenbogen. Aber damit erhalte ich keine Übereinstimmung. Setze ich dagegen den Polarwinkel in EllipseRadialLength ein, passt alles! Und wenn man sich die Implementierung der Funktion ansieht, dann wird nach etwas überlegen auch klar, dass das so sein muss.

Unter Linux ist die kritische Konvertierung in der Funktion EccentricAngle. Rufe ich diese mit dem Test-Punkt P auf, erhalte ich - den Polarwinkel! Und auch hier sieht man das im Code: Abstandsberechnung und arcsin - so berechnet man einen Polarwinkel...

Die Probleme in den Widersprüchen der Arc-Funktionen sind also dadurch begründet, dass der damalige Entwickler der jeweils fehlenden Routinen zwar den exzentrischen Winkel gemeint hat, aber den Polarwinkel verwendet hat.

Damit kann man versuchen, das Problem zu lösen:

In der Funktion EllipseRadialLength muss man eine Umwandlung von exzentrischen- zu Polar-Winkeln vorschalten: Dazu muss man einfach den Punkt Q berechnen, der auf dem die Ellipse umschreibenden Kreis liegt und denselben x-Wert hat wie P. Beim Kreis sind Polar- und exzentrischer Winkel identisch, und man hat dann mit ArcTan2 den Polarwinkel:

Code: Alles auswählen

function Eccentric2PolarAngle(EccentricAngle, a, b: Extended): Extended;
var
  Q: TFloatPoint;
  sinAngle, cosAngle: Extended;
begin
  SinCos(EccentricAngle, sinAngle, cosAngle);
  Q.X := cosAngle * a;
  Q.Y := sinAngle * b;
  Result := Arctan2(Q.Y, Q.X);
end; 

function EllipseRadialLength(const Rect : TRect; EccentricAngle : Extended) : Longint;
var
  a, b, R : Extended;
  polarAngle, sinAngle, cosAngle: Extended;
begin
  a := (Rect.Right - Rect.Left) div 2;
  b := (Rect.Bottom - Rect.Top) div 2;
  R := Sqr(a)*Sqr(b);
  if R <> 0 then
  begin
    polarAngle := Eccentric2PolarAngle(DegToRad(EccentricAngle / 16), a, b);
    SinCos(polarAngle, sinAngle, cosAngle);
    R := Sqrt(R / (sqr(b*cosAngle) + sqr(a*sinAngle)));
  end;
  Result := TruncToInt(R);
end;
Und bei der Funktion EccentricAngle streckt man die y-Koordinate des Ellipsenpunktes P im Verhältnis a/b, um zum Punkt Q zu gelangen (oder zur Vermeidung der Division durch null: man multipliziert die y-Koordinate mit a und die x-Koordinaten mit b). Und Q wiederum führt zum exzentrischen Winkel.

Code: Alles auswählen

function EccentricAngle(const P : TPoint; const Rect : TRect) : Extended;
var
  CenterPt : TPoint;
  Theta : Extended;
  a, b: Extended;
begin
  CenterPt := CenterPoint(Rect);
  a := Rect.Right - Rect.Left;
  b := Rect.Bottom - Rect.Top;
  Theta := ArcTan2((CenterPt.Y - P.Y) * a, (P.X - CenterPt.X) * b);
  Result := RadToDeg(Theta) * 16;
end; 
Damit verhalten sich die Windows- und Nicht-Windows-Versionen für jede Aufrufvariante gleich,.
Dateianhänge
ellipse_angles_laz40_gtk2.png
ellipse_angles_laz40_gtk2.png (26.61 KiB) 108 mal betrachtet
ellipse_angles.zip
(3.28 KiB) 21-mal heruntergeladen

Mario Peters
Beiträge: 12
Registriert: Sa 26. Apr 2025, 22:41

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Ist ja richitig viel Arbeit, so eine Ellipse zu berechnen. Danke wie verrückt dafür. Aber leider sehe ich in Deinem Beispielprojekt jetzt nicht die Verwendung Deiner Routinen wie im Forum hier vorgestellt. Wie sähe dann der Ellipsenbogen nun aus mit bei Codierung beiden Arc Funktionen. Mit der Winkelversion sollte sich ja dann auch die gesamte Ellipse zichnen lassen, aber eben auch ein beliebiges Stück davon. Aber wie müssen Deine Routinen von hier dabei zusammen wirken, das versteh ich noch nicht.

Mit EccetricAngle()/ Eccentric2Polarangle() habe ich ja erst mal die jeweiligen Winkel aber ich muss sie Ellipsenpunkte ja schon übergeben. Wie macht Canvas.Ellipse das? Ich nahm an duch Verwendung der bekannten Ellipsengleichungen aber dann dürfte es die hier betracteten Widersprüche nicht geben. In Deinem Download Beispiel hier verwendest Du ja leider nicht die hier aufgeführten Routinen aus diesem Beitrag.

Und warum wird der Polarwinkel außer beim Kreis überhaupt noch gebraucht? Der exzentrische Winkel ist klar, schon wegen der Bogenlänge des elliptischen Bogens. Die anderen Widersprüche kann ich mir nach all diesen Betractungen nur durch Anwendung von Näherungen erklären, die die Ellipsenfunktion schneller machen sollen. Wäre also interessant wie groß die Abweichungen da wirklich sind, im Anfangsbeispiel aber doch erheblich. Ich denke aber dass die Ellipsenfunktion so oft nun auch wieder nicht gebraucht wird, so kann man auch die hier gezeigte exakte Berechnung verwenden und die Näherungen, so ich das richtig sehe, nur wenn es wirklich so richtig auf Geschwindigkeit ankommt und weniger auf Exaktheit.

Ich habe im Eingangsbeitrag ja auch eine Ellipsenfunktion formuliert, die beruht auf der Ellipsengleichung. Daher hätte ich nie vermutet, dass es da solche gewltigen Unterschiede und Widerspüche geben kann,

Antworten