Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Delphi Verlauf mit "gewichteter" Farbe mittels Log? (https://www.delphipraxis.net/131988-verlauf-mit-gewichteter-farbe-mittels-log.html)

Pfoto 3. Apr 2009 20:31


Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo zusammen,

ich versuche seit geraumer Zeit, einen Verlauf zwischen
zwei Farben so zu ändern, dass eine der beiden Farben
mehr Gewicht hat, also über eine gewisse Fläche hinweg
dominanter ist.

Ich habe mit Log() expermimentiert und habe festgestellt,
dass ich damit der Sache näherkomme. Leider geht der Verlauf
jedoch nicht von Anfang bis Ende, sondern wird kurz vor
Ende unterbrochen und fängt neu an.

Im Anhang ist ein Beispiel davon.


Ich verwende folgenden Code (Quelle leider unbekannt):

Delphi-Quellcode:
  function GetColorBetween(StartColor, EndColor: TColor; Pointvalue,
    aFrom, aTo : Extended): TColor;
  var
    F: Extended; r1, r2, r3, g1, g2, g3, b1, b2, b3: Byte;

    function CalcColorBytes(fb1, fb2: Byte): Byte;
    begin
      result := fb1;
      if fb1 < fb2 then Result := FB1 + Trunc(F * (fb2 - fb1));
      if fb1 > fb2 then Result := FB1 - Trunc(F * (fb1 - fb2));
    end;

  begin
    if Pointvalue <= aFrom then
    begin
      result := StartColor;
      exit;
    end;
    if Pointvalue >= aTo then
    begin
      result := EndColor;
      exit;
    end;
    F := ((Pointvalue - aFrom) / (aTo - aFrom));

    {==== Hier kommt einer meiner Versuche ===}
    F:= LogN(10, F);
    {======================================}

    asm
       mov EAX, Startcolor
       cmp EAX, EndColor
       je @@exit
       mov r1, AL
       shr EAX,8
       mov g1, AL
       shr Eax,8
       mov b1, AL
       mov Eax, Endcolor
       mov r2, AL
       shr EAX,8
       mov g2, AL
       shr EAX,8
       mov b2, AL
       push ebp
       mov al, r1
       mov dl, r2
       call CalcColorBytes
       pop ecx
       push ebp
       Mov r3, al
       mov dL, g2
       mov al, g1
       call CalcColorBytes
       pop ecx
       push ebp
       mov g3, Al
       mov dL, B2
       mov Al, B1
       call CalcColorBytes
       pop ecx
       mov b3, al
       XOR EAX,EAX
       mov AL, B3
       SHL EAX,8
       mov AL, G3
       SHL EAX,8
       mov AL, R3
    @@Exit:
       mov @result, eax
    end;
  end;



  procedure FillGradient(Canvas : TCanvas; aStart, aWidth, aHeight : Integer;
    Color1, Color2 : TColor; Direction : TDirection);

  var i : Integer;
  begin
    if (aWidth > 0) and (aHeight > 0) then
    begin
      case Direction of
        diHorizontal:
        for i := aStart to aWidth do
        begin
          Canvas.Pen.Color := GetColorBetween(Color1, Color2, i, aStart, aWidth);
          Canvas.MoveTo(i, 0);
          Canvas.LineTo(i, aHeight);
        end;
        diVertical:
        for i := aStart to aHeight do
        begin
          Canvas.Pen.Color := GetColorBetween(Color1, Color2, i, aStart, aHeight);
          Canvas.MoveTo(0, i);
          Canvas.LineTo(aWidth, i);
        end;
      end;
    end;
  end;

Leider bin ich ja mathematisch minderbegabt, d.h. ich sehe zwar
mit verschiedenen Log-Arten die Ergebnisse (z.B. viele, immer
kleiner werdende Verlaufsstrecken), kann mir aber nicht erklären,
wie ich einen soliden Wert ermittle, um einen durchgängigen
Verlauf von A nach B zu generieren :-(

Wisst ihr hier eine Formel, mit der ich das lösen könnte?

Dank und Gruß
Jürgen

**** EDIT ****

Ich habe nun mit Prozentwerten gearbeitet und erhalte
eine sehr ähnliche Ausgabe, mit der ich ja schon *fast*
zufrieden wäre:
Delphi-Quellcode:
    F := ((Pointvalue - aFrom) / (aTo - aFrom));
    Percent:= (100 * Pointvalue) / (aTo - aFrom);
    F:= (Percent * F) / 100;
Der Verlauf wird nun von der Startfarbe her gewichtet
-- das würde ich jetzt noch gerne umkehren. Leider bisher
noch ohne Erfolg.

Medium 4. Apr 2009 04:25

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Ich habe jetzt nicht alles ganz genau durchgelesen, aber ich vermute mal dass du irgendwo eine "Laufvariable" i hast, die von 0 bis 1 als Faktor der Randfarben für die Mischung zuständig ist. In diesem Fall ist das simpelste "biasing" die Potenzierung, also genau die Umkehrfunktion vom Logarithmus.
Lasse i ganz normal laufen, aber rechne die Farben mit i^k*C1 + (1-i^k)*C2.

Wenn dein i nicht 0..1 ist, skaliere es darauf. Das tolle ist dabei nämlich, dass 0^k=0 und 1^k=1 bleiben, nur die Werte da zwischen werden quasi "verbogen" - ohne je [0;1] zu verlassen. Je nach dem wie du k nun wählst ist dein Verlauf mehr nach C1 oder C2 verschoben, wobei k=1 der übliche lineare Verlauf ist.

Ein wenig gewöhnungsbedürftig: Volles Schieben nach C2 wäre bei k=0,00000...1 und nach C1 k=+inf

Pfoto 4. Apr 2009 08:17

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Medium,

ich habe das eben mal (versucht) umzusetzen.

Dieses Dach (^) kenne ich nur von Pointern, aber du
meintest damit also die Funktion Power(), oder?

Delphi-Quellcode:
var
  Pointvalue, // entspricht aktueller Position
  k: extended;

[...]
  { Pointvalue geht von 0 bis Image.Widht/Height, also
    auf 0..1 skalieren }
  Pointvalue:= (Pointvalue / aTo);
  k:= 1;
  F:= power(Pointvalue, k)*StartColor + (1-power(Pointvalue, k))*EndColor;
Beim Skalieren, wie du es nennst, war ich mir auch nicht
sicher, aber zumindest bleibt Pointvalue nun zwischen
0 und 1. Poinvalue müsste also stimmen.

Als Ergebnis erhalte ich bei k=1 leider 3 Verläufe (siehe Anhang).
Wenn K Richtung 0 geht, werden es ganz viele kleine Verläufe :-(

Wo steckt denn da der Fehler?

Dank und Gruß
Jürgen

Satty67 4. Apr 2009 10:25

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Ich glaub' ich bin ganz nah dran, wenn ich die Aufgabe richtig verstanden hatte:

Delphi-Quellcode:
function GetColorBetween(FromColor, ToColor: TColor; FromDomPercent : Byte;
                         LoRange, HiRange, Position : Integer): TColor;
type
  TRGB = packed record
    R,G,B,P : Byte;
  end;
const
  Gewichtung = 200;
var
  FromClr, ToClr, ResultClr : TRGB;
  i : Integer;
begin
  i := ColorToRGB(FromColor);
  FromClr := TRGB(i);
  i := ColorToRGB(ToColor);
  ToClr := TRGB(i);

  // Hier FromColor anteilig in ToColor einrechnen
  if FromDomPercent in [1..100] then begin
    ToClr.R := ToClr.R + ((FromDomPercent * FromClr.R) div 100);
    ToClr.G := ToClr.G + ((FromDomPercent * FromClr.G) div 100);
    ToClr.B := ToClr.B + ((FromDomPercent * FromClr.B) div 100);
  end;

  // Verlauf berechnen
  i := (Position * 255) div (HiRange - LoRange);
  ResultClr.R := ( (ToClr.R * i) + (FromClr.R * (255 - i)) ) div 255;
  ResultClr.G := ( (ToClr.G * i) + (FromClr.G * (255 - i)) ) div 255;
  ResultClr.B := ( (ToClr.B * i) + (FromClr.B * (255 - i)) ) div 255;
  ResultClr.P := 0;

  Result := TColor(ResultClr);
end;

procedure FillGradient(Canvas: TCanvas; FromClr, ToClr: TColor;
                       FromDomPercent: Byte; aRect: TRect; Vertical : Boolean);
var
  i : Integer;
begin
  if Vertical then begin
    for i := aRect.Top to aRect.Bottom do begin
      Canvas.Pen.Color := GetColorBetween(FromClr, ToClr, FromDomPercent, aRect.Top, aRect.Bottom, i);
      Canvas.MoveTo(aRect.Left, i);
      Canvas.LineTo(aRect.Right, i);
    end;
  end else begin
    for i := aRect.Left to aRect.Right do begin
      Canvas.Pen.Color := GetColorBetween(FromClr, ToClr, FromDomPercent, aRect.Left, aRect.Right, i);
      Canvas.MoveTo(i, aRect.Top);
      Canvas.LineTo(i, aRect.Bottom);
    end;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FillGradient(PaintBox1.Canvas, clRed, clBlue, SpinEdit1.Value, PaintBox1.ClientRect,False);
end;
PS: Nochmal leicht verändert, da Systemfarben nicht als Palettefarbe, sondern als Paletteneintrag behandelt werden...

PPS: Dominanz-Aufgabe noch mit eingebettet... 0-100% Domininaz der From-Farbe möglich (wobei 100% nicht die Zielfarbe völlig verschwinden lässt)

Satty67 4. Apr 2009 11:56

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Es wird übrigens nur die Farbe verändert, nicht die Helligkeit. Deshalb ist zum Testen Black -> White ganz schlecht, denn die Grausstufen ändern sich bei reiner Farb-Änderung nicht.

In der Anlage das Project, mit dem ich experimentiert hab'

Pfoto 4. Apr 2009 12:49

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Satty67,

vielen Dank erstmal für Deine Mühe!

Leider meinte ich mit "Gewichtung" etwas anderes, obwohl
man sicher auch deine Routine gut gebrauchen kann.

Die Start- und Endfarbe sollte jedoch nicht verändert werden,
sondern der Verlauf sollte eben nicht linear vorangehen,
sondern überproportional (oder wie man das nennt).
D.h. im ersten Drittel ist die Dominanz der ersten Farbe noch
deutlicher und lässt dann erst allmählich nach.

Im Anhang noch mal ein Beispiel von einem normalen und
dem von mir gemeinten Verlauf.


Gruß
Jürgen

Satty67 4. Apr 2009 13:23

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ok, dann hatte ich das falsch verstanden. (aber macht nicht, das experimentieren mit Farben macht mit am meisten Spass)

Hier dann mal ein anderer Ansatz (im Anhang das Projekt zum Testen)
Delphi-Quellcode:
function GetColorBetween(FromColor, ToColor: TColor; Distributor : Byte;
                         LoRange, HiRange, Position : Integer): TColor;
type
  TRGB = packed record
    R,G,B,P : Byte;
  end;
var
  FromClr, ToClr, ResultClr : TRGB;
  i,j, FirstPart, SecondPart, FullRange : Integer;
begin
  i := ColorToRGB(FromColor);
  FromClr := TRGB(i);
  i := ColorToRGB(ToColor);
  ToClr := TRGB(i);

  // Verlauf berechnen
  FullRange := (HiRange - LoRange);
  FirstPart := (Distributor * FullRange) div 100;
  SecondPart := FullRange - FirstPart;

  if Position < FirstPart then begin
    j := (Position * 50) div FirstPart;
  end else begin
    j := ((Position- FirstPart) * 50) div SecondPart;
    j := j + 50;
  end;

  i := (j * 255) div 100;

  ResultClr.R := ( (ToClr.R * i) + (FromClr.R * (255 - i)) ) div 255;
  ResultClr.G := ( (ToClr.G * i) + (FromClr.G * (255 - i)) ) div 255;
  ResultClr.B := ( (ToClr.B * i) + (FromClr.B * (255 - i)) ) div 255;
  ResultClr.P := 0;

  Result := TColor(ResultClr);
end;

procedure FillGradient(Canvas: TCanvas; FromClr, ToClr: TColor;
                       Distributor: Byte; aRect: TRect; Vertical : Boolean);
var
  i : Integer;
begin
  if Vertical then begin
    for i := aRect.Top to aRect.Bottom do begin
      Canvas.Pen.Color := GetColorBetween(FromClr, ToClr, Distributor, aRect.Top, aRect.Bottom, i);
      Canvas.MoveTo(aRect.Left, i);
      Canvas.LineTo(aRect.Right, i);
    end;
  end else begin
    for i := aRect.Left to aRect.Right do begin
      Canvas.Pen.Color := GetColorBetween(FromClr, ToClr, Distributor, aRect.Left, aRect.Right, i);
      Canvas.MoveTo(i, aRect.Top);
      Canvas.LineTo(i, aRect.Bottom);
    end;
  end;
end;

procedure TFormFarbverlauf.Button2Click(Sender: TObject);
begin
  If ColorDialog1.Execute then PanelFrom.Color := ColorDialog1.Color;
end;

procedure TFormFarbverlauf.Button3Click(Sender: TObject);
begin
  If ColorDialog1.Execute then PanelTo.Color := ColorDialog1.Color;
end;

procedure TFormFarbverlauf.TrackBar1Change(Sender: TObject);
begin
  LabelTrackbar.Caption := IntToStr(TrackBar1.Position)+' %';
  FillGradient(PaintBox1.Canvas, PanelFrom.Color, PanelTo.Color, TrackBar1.Position, PaintBox1.ClientRect,False);
end;

jfheins 4. Apr 2009 13:31

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich hätte da auch was im Angebot :stupid:
Code:
namespace Test_1
{
    public partial class Form1 : Form
    {
        double potenz;
        Color col1;
        Color col2;

        public Form1()
        {
            InitializeComponent();

            col1 = Color.Red;
            col2 = Color.Blue;
            potenz = 1;
        }

        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        {
            double pos = 0;
            double amount = 0;
            Color tempColor;
            Pen myPen = new Pen(col1);

            for (int i = 0; i < pictureBox1.Width; i++)
            {
                pos = (double)i / (pictureBox1.Width - 1);
                amount = 1 - Math.Pow(pos, potenz);
                // 1 - Weil wir den Betrag der ersten Farbe haben wollen
                tempColor = MixColors(col1, col2, amount);

                myPen.Color = tempColor;

                e.Graphics.DrawLine(myPen, new Point(i, 0), new Point(i, pictureBox1.Height - 1));
            }
        }

        private Color MixColors(Color col1, Color col2, double amount)
        {
            if (amount < 0 || amount > 1)
                throw new ArgumentOutOfRangeException("amount");

            byte R = (byte)(col1.R * amount + col2.R * (1 - amount));
            byte G = (byte)(col1.G * amount + col2.G * (1 - amount));
            byte B = (byte)(col1.B * amount + col2.B * (1 - amount));

            return Color.FromArgb(R, G, B);
        }

        private void trackBar1_ValueChanged(object sender, EventArgs e)
        {
            potenz = Math.Pow(1.1, trackBar1.Value);
            label1.Text = String.Format("Potenz: {0:F3}", potenz);
            pictureBox1.Invalidate();
        }
    }
}

Satty67 4. Apr 2009 13:37

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Kriegt ich Delphi 5 nicht beigebracht (ist das C# oder Delphi-Prism?)

***

Zu meinem zweiten Ansatz noch kurz die Idee dahinter:

Ich gehe einfach davon aus, das irgendwo der Farbverlauf die Mitte hat (Anteil beider Farben gleich) und verschiebe danach nur die Mitte und berechne Teilfarbverläufe zu neuen Mitte

jfheins 4. Apr 2009 13:52

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Ist C# ;)

Ich habe den Ansatz von Medium gewählt.

Also mit der Laufvariable und der Potenz.

d.h. es wird eine Laufvariabele genommen die von 0 bis 1 geht (jeweils inkl.)

Der Anteil der zweiten Farbe ergibt sich dann aus laufvariable^Potenz - wenn also die Potenz gleich 1 ist isses ein linearer Farbverlauf.

Der Anteil der ersten Farbe ist dann der Rest (1 - laufvariable^Potenz)

Die Potenz muss zwischen 0 und unendlich liegen. Wenn sie null ist, ist der Anteil der zweiten Farbe immer Null, bis auf das Ende da springt er auf eins (also 100%)
Das andere Extrem ist unendlich, da ist der Anteil der zweiten Farbe erst null und springt danach sofort auf 1.

Satty67 4. Apr 2009 14:31

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Denke der Code von C# ist einfach nach Delphi portierbar. Auch der Formel-Ansatz von Medium ist dank der besseren Range-Kontrolle ein guter Weg.

Bin heute Abend grillen (danach sicher zu faul), aber werde das ganze später mal in Delphi 5 kompatiblen Code umschreiben ;)

Pfoto 5. Apr 2009 07:44

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
@Satty67:
Ddas sieht schon sehr nahe zu dem aus, wie ich das gedachte hatte.
Der einzige Nachteil ist, das man ab einer gewissen Stelle den Verlauf
"umbrechen" sieht, hin zur anderen Farbe (aber nur, wenn man größere
Prozentwerte nimmt).
Wenn Du Lust hat, den C#-Code von Julius nach Pascal zu übertragen,
wäre das klasse. Es ist auch nicht so dringend, es gibt da noch genügend
andere Baustellen in meinem Programm...

@Julius:
Ich hatte ja auch schon versucht, den Hinweis von Medium umzusetzen,
aber bei mir kam leider nichts gescheites raus. Danke für deinen Code!

Gruß
Jürgen

jfheins 5. Apr 2009 09:33

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Ich hab die die wichtigen Teile nochmal kommentiert ;)
Code:
        private void pictureBox1_Paint(object sender, PaintEventArgs e)
        { 
            double pos = 0; // Position entlang der Paintbox/Picturebox im Intervall 0..1
            double amount = 0; // Menge der ersten Farbe - soll also von links 1 (= 100%) nach rechts 0 werden
            Color tempColor; // Wird die Farbe enthalten, an der Position an der wir gerade sind
            Pen myPen = new Pen(col1); // Braucht man in Delphi nicht

            for (int i = 0; i < pictureBox1.Width; i++)
            { 
                pos = (double)i / (pictureBox1.Width - 1);
// pos errechnen. Wenn i = 0 dann pos = 0 Wenn i = (Width-1) dann pos = 1 

                amount = 1 - Math.Pow(pos, potenz);
// Menge der Farbe ausrechnen. Dazu potenzieren wir die Position (Intervall 0..1) mit der Potenz.
// Es kommt garantiert wieder etwas aus dem Intervall 0..1 raus.
// Da die Position von 0 nach 1 läuft, nehmen wir 1 - Math.pow() um die Menge der ersten Farbe rauszubekommen.
                tempColor = MixColors(col1, col2, amount);
// Mischt die beiden Farben zusammen. Code siehe unten
                myPen.Color = tempColor;
// Farbe merken
                e.Graphics.DrawLine(myPen, new Point(i, 0), new Point(i, pictureBox1.Height - 1));
// Und eine vertikale Linie an der Position zeichnen an der wir gerade sind.
// (Wir bewegen uns horizontal und zeichnen dann immer eine 1px breite Linie von oben nach unten)
            } 
        } 

        private Color MixColors(Color col1, Color col2, double amount)
        { 
            if (amount < 0 || amount > 1)
                throw new ArgumentOutOfRangeException("amount");
// Bitte tauschen Sie die FPU oder den Programmierer aus - einer von beiden macht Mist :-P

            byte R = (byte)(col1.R * amount + col2.R * (1 - amount));
            byte G = (byte)(col1.G * amount + col2.G * (1 - amount));
            byte B = (byte)(col1.B * amount + col2.B * (1 - amount));
// Farben einzeln Mischen. Farbe := Farbe1 mal Menge + Farbe2 mal (1 - Menge)
            return Color.FromArgb(R, G, B);
        }
Ich hoffe das ist verständlich ;)

Satty67 5. Apr 2009 10:58

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Liste der Anhänge anzeigen (Anzahl: 2)
Ja, ist verständlich. Basiswissen zu den anderen Dialekten besteht und bei C# darf man sich ja über die bekannte Klassenhierarchie freuen.

Das Ergenbis ist nochmal besser als meine Version (ohne Vertikal, etwas Arbeit soll Pfoto ja auch noch haben) ;):
Delphi-Quellcode:
procedure FillGradient(Canvas: TCanvas; FromClr, ToClr: TColor; aRect: TRect; Potenz : Double);

  function MixColors(FromColor, ToColor: TColor; Amount: Double): TColor;
  type
    TRGB = packed record
      R,G,B,P : Byte;
    end;
  var
    FromClr, ToClr, ResultClr : TRGB;
  begin
    if (Amount < 0) or (Amount > 1) then Exit;

    FromClr := TRGB( ColorToRGB(FromColor) );
    ToClr := TRGB( ColorToRGB(ToColor) );

    ResultClr.R := Round( FromClr.R * Amount + ToClr.R * (1 - Amount) );
    ResultClr.G := Round( FromClr.G * Amount + ToClr.G * (1 - Amount) );
    ResultClr.B := Round( FromClr.B * Amount + ToClr.B * (1 - Amount) );
    ResultClr.P := 0;

    Result := TColor(ResultClr);
  end;

var
  aPos, Amount : Double;
  TmpClr : TColor;
  i, width : Integer;
begin
  width := aRect.Right - aRect.Left;

  for i := 0 to width-1 do begin

    aPos := i / (width-1);
    Amount := 1 - Power(aPos, Potenz);
    TmpClr := MixColors(FromClr, ToClr, Amount);

    Canvas.Pen.Color := TmpClr;
    Canvas.MoveTo(aRect.Left + i, aRect.Top);
    Canvas.LineTo(aRect.Left + i, aRect.Bottom);

  end;
end;
Mir hat's Spass gemacht und meine Beispiel-Bibliothek hat eine tolle Funktion zum Erzeugen von Verläufen dazu bekommen.

€:
Wenn man beide Versionen nebeneinander anzeigen lässt, sieht man den Unterschied gut. In meiner Version mit der harten Verlaufsgrenze sieht man eben diese Grenze sehr gut, dafür ist der Verlauf immer über den ganzen Farbbereich.

Die Version mit der Potenzierung hat immer ein sehr weiche Grenze, aber dafür im Extrem auch einen großen fast reinen Farbbereich.

(siehe auch Bild in der Anlage)

Einen Tod muss man aber sterben, denn solange die Einzel-Farben nur Byte Werte sind, wird sich ein Verlauf auf 255 Werte beschränken. Dittering wäre dann noch eine Möglichkeit.

Pfoto 5. Apr 2009 18:44

Re: Verlauf mit "gewichteter" Farbe mittels Log?
 
Hallo ihr Beiden,

ich danke Euch sehr für Eure Mühe!
Jetzt ist es ja genau so wie es in meiner Vorstellung war. :thumb:

Gruß
Jürgen


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:26 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz