TFPCustomCanvas Arc Methode, Verständnisfrage

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
wp_xyz
Beiträge: 5241
Registriert: Fr 8. Apr 2011, 09:01

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Mario Peters hat geschrieben: Mi 30. Jul 2025, 12:06 Also wie sähe das aus wenn nicht Canvas.Arc() oder Canvas.Ellipse() verwendet würde, sondern meine eigene aus dem Anfangsbeirag dieses Threads?
Wenn du deine eigene Arc-Routine verwendest, ist das meiste von dem, was ich hier gesagt habe, irrelevant, denn das bezieht sich auf die Probleme mit den LCL-Arc-Routinen in der Graphics-Unit.

Deine eigene Routine aus dem ersten Beitrag verwendet Winkel-Argumente für Bogenanfang und -ende, und diese Winkel werden in die cos- und sin-Funktionen der parametrisierten Ellipsengleichung eingesetzt, d.h. es handelt sich um exzentrische Winkel. Alles prima - egal welches Betriebssystem, immer richtig.

Wenn du zusätzlich auch die Routinen mit den Start-/End-Punkten SX,SY,EX,EY zur Verfügung stellen willst, musst du aus diesen Punkt-Koordinaten die zugehörigen exzentrischen Winkel ermitteln und damit die schon existierende Routine aufrufen. Die Umrechnung macht eigentlich die Prozedur CoordsToAngles in der GraphMath-Unit - nur dass diese fälschlicherweise die Polarwinkel bestimmt. Stattdessen würde ich die richtige Routine neu codieren, ohne GraphMath zu verwenden, evtl nimmst du auch einen anderen Namen:

Code: Alles auswählen

procedure MyCoords2Angles(X, Y, Width, Height : Integer; SX, SY,
  EX, EY : Integer; var Angle1, Angle2 : Extended);
var
  aRect : TRect;
  SP,EP : TPoint;
begin
  aRect := Rect(X,Y,X + Width,Y + Height);
  SP := Point(SX,SY);
  EP := Point(EX,EY);
  Angle1 := MyEccentricAngle(SP, aRect);
  Angle2 := MyEccentricAngle(EP, aRect);
  If Angle2 < Angle1 then
    Angle2 := 360*16 - (Angle1 - Angle2)
  else
    Angle2 := Angle2 - Angle1;
end;
mit

Code: Alles auswählen

function MyEccentricAngle(const PT : 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 - PT.Y) * a, (PT.X - CenterPt.X) * b);
  Result := RadToDeg(Theta) * 16;
end;
Auch das ist wieder vom Betriebssystem/widgetset unabhängig.

Achtung allerdings: Diese Routinen verwenden die Winkel in den 1/16-Grad-Einheiten. Wenn du, wie in deiner eigenen Routine im ersten Post, lieber direkt Grad verwenden willst, dann musst du den Multiplikator 16 weglassen.
Mario Peters hat geschrieben: Mi 30. Jul 2025, 12:06 In welcher Unit des Gtk2 finde ich zum Quellcodestudium der GTK Funktionen die genammte Ellipsenfunktion? Die will ich mir der Volständigkeit halber dennnoch ansehen.
Die nativen gtk2-Zeichenroutinen selbst sind verborgen in Systembibliothken. Das betrifft in diesem Zusammenhang die Arc-Routine mit den Winkel-Argumenten. Die andere Routinen mit den Punkt-Argumenten dagegen kannst du mit dem Debugger durchsuchen. Trick: Füge in deinem Projekt unter "Hinzufügungen und Beeinflussungen" eine neue Option "-gw3" hinzu (ohne ") - damit wird alles mit Dwarf3-Debug-Informationen neu übersetzt, und du kannst mit dem Debugger alle Lazarus-Units erreichen (nicht allerdings die FPC-Routinen der RTL und FCL). Setze einen Breakpoint auf "Canvas.Arc(...)" und, wenn das Programm dort anhält, drücke F7, um in diese Prozedur einzutauchen - wegen des "-gw3" geht das. Unter Linux führt dich das zu TCanvas.Arc (in graphics) mit der Zeile "LCLIntf.RadialArc(...)". Wenn du dort wieder F7 drückt, kommst du zu "Widgetset.RadialArc" und nach nochmaligem F7 auf RadialArc bist du schließlich in lcl/include/intfbaselcl.inc mit

Code: Alles auswählen

function TWidgetSet.RadialArc(DC: HDC;
  left, top, right, bottom, sx, sy, ex, ey: Integer): Boolean;
var
  A1, A2: Extended;
  A2i : integer;
begin
  Coords2Angles(left, top, right-left, bottom-top, sx, sy, ex, ey, A1, A2);
  A2i := RoundToInt(A2);
  if A2i = 0 then
    A2i := 5760;
  Result := Arc(DC, left, top, right, bottom, RoundToInt(A1), A2i);
end;
Die Routine rechnet die Eingangs-Punkt-Koordinaten (sx, sy, ex, ey) in die Winkel-Argument A1 und A2 um, wie ich eingangs vorgeschlagen habe. Ein F7 auf der Arc-Prozedur (das ist jetzt die mit den Winkel-Argumenten) führt dich wieder ins gtk2-Widgetset, wo noch ein paar Anpassungen gemacht werden und schließlich die gtk2-Funktion "gdk_draw_arc" aufgerufen wird.

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

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

So schaut mein Code jetzt aus

Code: Alles auswählen

procedure TFPFclCanvas.Arc(Left,Top,Right,Bottom,SX,SY,EX,EY: integer);
var
  R: TRect;
  Angle1,Angle2: Extended;
  xm,ym,xR,YR: Integer;
  Center: TPoint;
  cosWStart,cosWEnd: Extended;    //fur sincos Prozedur, Winkelwerte
  sinWStart,sinWend: Extended;      //fur sincos Prozedur, Winkelwerte
  pStart,pEnd: TPoint;
  elw: TFloatPoint;
  //pStart,pEnd: TPoint;

  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;

begin
    _Coords2Angles(Left,Top,Right-Left,Bottom-Top,SX,SY,EX,EY,Angle1,Angle2);

    Center.X := CenterPoint(Rect(Left,Top,Right,Bottom)).X;
    Center.Y := CenterPoint(Rect(Left,Top,Right,Bottom)).Y;
    xm :=Center.X;
    ym :=Center.Y;
    xR :=abs(Center.X - Left);
    yR :=abs(Center.Y - Top);

    Angle1 := DegToRad(Angle1{ / 16}+180);
    Angle2 := DegToRad(Angle2{ / 16}+180);
    //XStart := xR * cos(Angle1);
                                                                                    //Berechnung der Koordinaten für die Bogenenden    
    sincos(Angle1,sinWStart,cosWStart);
    sincos(Angle2,sinWEnd,cosWEnd);

    pStart.X := Round(cosWStart);                 
    pStart.Y := Round(sinWStart);

    pEnd.X := Round(cosWEnd);
    pEnd.Y := Round(sinWEnd);

    EllipseRadialLength(Rect(Left,Top,Right,Bottom),_EccentricAngle(pStart, Rect(Left,Top,Right,Bottom)));   //Hier will ich die Lage der Geraden vom
    EllipseRadialLength(Rect(Left,Top,Right,Bottom),_EccentricAngle(pEnd, Rect(Left,Top,Right,Bottom)));     //Mittelpunkt zum Bogenende berechnen

    ellipses.PaintEllipse(self,xm,ym,xR,yR,Round(Angle1),Round(Angle2),4096,FPen.FPColor);

    MoveTo(xm,ym); Lineto(pStart.X,pStart.Y);
    MoveTo(xm,ym); Lineto(pEnd.X,pEnd.Y);
  end;
Die Hilfsfunktionen stammen gänzlich aus dem Therad.

Warum ist die Lage meiner Onkte noch nicht korrekt, Der Ellipssenbogen liegt entfernt von meinen Geraden die von dessen Mittelpunkt aus die Bogenenden berühren sollen.

Wo liegt mein Fehler?

Wie erhalte ich die X,Y Koordinaten der Bogenenden?

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

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von wp_xyz »

Warum addierst du 180° zu den Winkeln? Die Winkel je nach Quadranten richtig zu bekommen ist ein eigenes Thema. Ich meine, du musst den Bogen immer in derselben Richtung zeichnen, i.d.R gegen den Uhrzeigersinn. Das ist einfach, solange Angle1 < Angle2. Ist irgendwann Angle2 < Angle1 (also vertauscht), dann soll das bedeuten, dass der Bogen einmal um die 360° herumgelaufen ist und dann über die 0° weiterläuft. Das bringt alle Abfragen für "ist der aktuelle Winkel zwischen Angle1 und Angle2?" durcheinander! In diesem Fall musst du 360° zu Angle2 addieren, so dass die Größer-Kleiner-Beziehung erhalten bleibt:

Code: Alles auswählen

   if Angle2 < Angle1 then Angle2 := Angle2 + 360;
    Angle1 := DegToRad(Angle1); 
    Angle2 := DegToRad(Angle2);
Mein Problem, wenn die Punkte nicht da lagen, wo ich sie erwartet hatte, war immer, dass ich vergessen hatte, den Mittelpunkt zu berücksichtigen.

Wenn Angle1 der exzentrische Winkel ist (und bei Aufruf von _CoordsToAngles ist er das), erhältst du die Koordinaten des zugehörigen Ellipsen-Punktes aus der Ellipsengleichung: x = a * cos(Angle1) und y = b * sin(Angle1). Das ist aber nur pauschal ausgedrückt, denn die Ellipse nach diesen Gleichungen ist um den Ursprung zentriert --> du musst noch die Koordinaten des Mittelpunktes addieren:

Code: Alles auswählen

pStart.X := round(xR * coswStart + xm);
pStart.Y := round(-yR*sinWStart + ym);
Das Minus-Zeichen im pStart.Y berücksichtigt, dass auf dem Canvas die y-Koordinaten nach unten wachsen, der Winkel aber im Gegenuhrzeigersinn gezählt wird. Das heißt, wenn du, ausgehend von Angle=0, den Winkel ein bisschen vergrößerst, soll der Punkt nach oben wandern, aber bei yR*sinWStart + ym würde er nach unten wandern.

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

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Jetzt liegt bei Benutzung dieser Funktion:

Code: Alles auswählen

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; 
und diser meiner Arc() Routine

Code: Alles auswählen

procedure TFPfclCanvas.Arc(Left, Top, Right, Bottom, SX, SY, EX, EY: Integer);
var
  R: TRect;
  Angle1,Angle2: Extended;
  xm,ym,xR,YR: Integer;
  Center: TPoint;
  cosWStart,cosWEnd: Extended;
  sinWStart,sinWend: Extended;
  distanceS,distanceE: Extended;
  pStart,pEnd: TPoint;
  elw: TFloatPoint;
  //pStart,pEnd: TPoint;

  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;

begin
    _Coords2Angles(Left,Top,Right-Left,Bottom-Top,SX,SY,EX,EY,Angle1,Angle2);

    Center.X := CenterPoint(Rect(Left,Top,Right,Bottom)).X;
    Center.Y := CenterPoint(Rect(Left,Top,Right,Bottom)).Y;
    xm :=Center.X;
    ym :=Center.Y;
    xR :=abs(Center.X - Left);
    yR :=abs(Center.Y - Top);

    if Angle2 < Angle1 then Angle2 := Angle2+360;
    Angle1 := DegToRad(Angle1{ / 16});
    Angle2 := DegToRad(Angle2{ / 16});
    //XStart := xR * cos(Angle1);

    //x := a * cos(angle) + xm
    //y := -b * sin(angle) + ym

    distanceS := Distance(Center,Point(Round(xR),Round(yR)));
        pEnd := LineEndPoint(Center,distanceS,xR);


    sincos(Angle1,sinWStart,cosWStart);
    sincos(Angle2,sinWEnd,cosWEnd);

    pStart.X := Round(xR*cosWStart*1000+xm);
    pStart.Y := Round(-yR*sinWStart*1000-ym);

    pEnd.X := Round(xR*cosWEnd*1000+xm);
    pEnd.Y := Round(-yR*sinWEnd*1000-ym);

    EllipseRadialLength(Rect(Left,Top,Right,Bottom),_EccentricAngle(pStart, Rect(Left,Top,Right,Bottom)));
    EllipseRadialLength(Rect(Left,Top,Right,Bottom),_EccentricAngle(pEnd, Rect(Left,Top,Right,Bottom)));

    ellipses.PaintEllipse(self,xm,ym,xR,yR,Round(Angle1),Round(Angle2),4096,{FPen.FPColor}colRed);

    {MoveTo(xm,ym); Lineto(pStart.X,pStart.Y);} Line(Center.X,Center.Y,pEnd.X,pEnd.Y);
    {MoveTo(xm,ym); Lineto(xm,ym,pEnd.X,pEnd.Y);} Line(xm,ym,pEnd.X,pEnd.Y);
  end;
der Schnittpunkt der Winkelgeraden, die das Toertenstück abschließen soll auch nicht am Ende des Bogens.
Jetzt scolle ich noch mal diesen Thread zurück, da war das Problem irendwo erlärt, ich gucke jetzt selber noch mal da hin! wenn mein Problem weiter besteht, gehe ich wieder hier hin und frage weiter. Zumindest ist das Thema erst mal wieder oben.
--------------------------------------------------------------
nein, leider muss ich doch noch mal her kommen. Ich habe mir von Seite 1 des Threads den Beitrag angeguckt mit diesem Code:

Code: Alles auswählen

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;
Das ist der Codeteil, wo die Arc() Funktion für Windows richtig ist, aber nicht für Linux, also muss ich ich die im Code gezeigten Korrekturen aufrufen. Dazu habe ich aber noch Fragen:Was ist der Unterschied zwischen EndAngle und sweepAngle? sweep bedutet im englichen wischen, könnte also der überstrichene Winkel sein, aber warum extra noch mal Endwinkel, Wenn der Abstanddes überstrichenen Winkels zum Enwinkel doch auch NULL grad sein könnte, wenn nämlich bei einer Programmschleife der sweepAngle dem Endwinkel entspricht, was für eine Schlife eine geeignete Abbruchbefingung wäre?

Dann werden oben mit DegToRad die Start und Endwinkel erst in Rad, also Bogenmaß umgewandelt, dann aber mit Start/End Angle := RadToDeg(ArcTan2(a*sinAngle, b*cosAngle)) wieder in Geardmaß aber mit arcTan2??? Wrum diese mehrfache Konverierung, das kann ich aus meiner Formelsammlung nicht rauslesen. Warum muss das also gemacht werden

Die erste Umrechnung DegToRad ist ja in meiner Arc Routine auch drin aber die weitere fehlt und ich will jetzt nicht einfach copy&Paste machen ohne alles wirklich verstanden zu haben.

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

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Jauuuu, es ist vollbracht. Habe es hinbekommen. Hatte bloden Schusselfehler drin. Sorry, jetzt aber wird das Tortenstück korrekt gezeichnet.

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

Re: TFPCustomCanvas Arc Methode, Verständnisfrage

Beitrag von Mario Peters »

Nun, ich habe jetzt eine neue Ellipsenfunktion in Arbeit, noch nicht getestet!

Code: Alles auswählen


procedure PlotEllipse(x1,y1,x2,y2: Integer; alpha1,alpha2: extended);
var
  a,b: Integer;
  ra,rb: extended;
  x,y: Integer;
  alpha: Extended;
  e,r: Extended;
begin
   alpha := alpha1;
   a := (x2-x1) div 2;
   b := (y2-y1) div 2;
   e := sqrt(sqr(a)-sqr(b))/a;
   r := sqr(b)/(1-sqr(e)*sqr(cos(alpha)));

   while alpha >= alpha2 do
   begin
      x := CenterPoint.X + Round(a * cos(alpha1)*1000.0);
      y := Centerpoint.Y + Round(b * sin(alpha1)*1000.0);
      if alpha = alpha1 then Line(CenterPoint.X,CenterPoint.Y, x,y);
      alpha := alpha + 1.0;
      DrawPixel(Round(x),Round(y));
   end;
   Line(Centerpoint.X,Centerpoint.Y, x, y);
end;

Eine weitere folgt, in der das hier im Thrad erklärte zur Anwendung kommt. Muss da aber noch Codestudium betreiben.

Das betrifft diesen Teil des Codes hier:

Code: Alles auswählen

...
SinCos(startAngle, sinAngle, cosAngle);
  startAngle := RadToDeg(ArcTan2(a*sinAngle, b*cosAngle));
  SinCos(endAngle, sinAngle, cosAngle);
  endAngle := RadToDeg(ArcTan2(a*sinAngle, b*cosAngle));
...
Wenn das verstnden ist, folgt eine neue Funktion.

Die alte erfordert zunächst ein Aufräumen des Codes, den ich im aktuellenn Zustand keinem zumuten will.

.

Antworten