[gelöst] PaintBox-Komponente und clDefault-Farbe

Für Fragen von Einsteigern und Programmieranfängern...
Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

[gelöst] PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Hallo zusammen,

ich habe gerade Verständnisprobleme mit der PaintBox-Komponente. Hier habe ich mal einen ziemlich simplen Code, um meine Probleme zu verdeutlichen:

Code: Alles auswählen

unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    PaintBox1: TPaintBox;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    procedure draw;
  end;
 
var
  Form1: TForm1;
  positionX: Integer;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
procedure TForm1.draw;
var
  x: Integer;
  y: Integer;
  size: Integer;
  BrushColorOld: TColor;
  BrushStyleOld: TBrushStyle;
  PenColorOld: TColor;
  PenStyleOld: TPenStyle;
begin
  positionX := positionX + 1;
  x := positionX;
  y := Round(PaintBox1.Height/2);
  size := 5;
 
  // Save old settings
  BrushColorOld := PaintBox1.Canvas.Brush.Color;
  BrushStyleOld := PaintBox1.Canvas.Brush.Style;
  PenColorOld := PaintBox1.Canvas.Pen.Color;
  PenStyleOld := PaintBox1.Canvas.Pen.Style;
 
  // Remove old ellipse at (x-1, y)
  // current drawing colors/styles should draw ellipse with color = clDefault
  // which is the current background color of PaintBox1 --> remove ellipse drawn in black
  PaintBox1.Canvas.Ellipse(x-1-size, y-size, x-1+size, y+size);
 
 
  // Draw new Ellipse at (x, y) filled with black and with black line
  PaintBox1.Canvas.Brush.Color := clBlack;
  PaintBox1.Canvas.Pen.Color := clBlack;
 
  PaintBox1.Canvas.Ellipse(x-size, y-size, x+size, y+size);
 
  // load old drawing colors
  PaintBox1.Canvas.Brush.Color := BrushColorOld;
  PaintBox1.Canvas.Pen.Color := PenCOlorOld;
end;
 
 
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  draw;
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  positionX := Round(PaintBox1.Width/2);
  // set Brush and Pen properties
  PaintBox1.Canvas.Brush.Color := clDefault;
  PaintBox1.Canvas.Brush.Style := bsSolid;
  PaintBox1.Canvas.Pen.Color := clDefault;
  PaintBox1.Canvas.Pen.Style := psSolid;
end;
 
end.
 
Erst einmal eine kurze Erklärung zum Text und was ich machen wollte:

Mein Ziel für dieses Beispielprogramm ist es, einen Kreis zu zeichnen, der sich Pixel für Pixel nach rechts bewegt.

1. FormCreate-Prozedur: Hier habe ich erst einmal die Properties Color/Style von Brush und Pen gesetzt. Nach meinem Verständnis ist clDefault die Farbe, die die PaintBox schon am Anfang als Hintergrundfarbe hat. Zur Variable positionX komme ich gleich noch.

2. Timer: Der Timer ruft ganz einfach die draw-Prozedur auf, die ich im folgenden erkläre.

3. draw-Prozedur: Auch wenn beim ersten Aufruf noch kein Kreis gezeichnet wurde, gehe ich vom allgemeinen Fall aus, dass schon ein Kreis gezeichnet wurde (wenn noch keiner gezeichnet wurde, ist es auch egal). Zuerst inkrementiere ich die x-Position des Mittelpunktes (positionX) des Kreises, den ich zeichnen möchte. Den Startwert habe ich schon auf die Mitte der Paintbox festgelegt (im OnFormCreate-Event). Den y-Wert stelle ich auch auf die Mitte der Paintbox ein. Dieser Wert bleibt immer gleich. Anschließend lege ich noch die Größe des Kreises fest (size := 5). Bevor ich irgendetwas zeichne speichere ich mir zunächst die Properties von Pen und Brush (auch wenn es nicht unebdingt notwendig wäre). Nun zeichne ich einen Kreis an die Position in der Paintbox, bevor der x-Wert inkrementiert wurde (ich ziehe vom x-wert einfach wieder 1 ab). Diesen Kreis zeichne ich mit der Farbe clDefault (für Brush und Pen). Daher erwarte ich, dass der Kreis wieder die Hintergrundfarbe der PaintBox hat, also verschwindet.
Nun möchte ich an der neuen Position (ein Pixel in x-Richtung weiter) einen neuen Kreis in schwarz zeichnen. Also setze ich die Farben von Brush und Pen auf clBlack und zeichne wieder einen Kreis. Zum Schluss setze ich die Farben von Pen und Brush wieder auf die ursprünglichen Werte zurück.

Ich erwarte also, dass der Kreis in jedem Schritt um einen Pixel weiter nach rechts "wandert". Stattdessen wird aber der vorige Kreis nie gelöscht und bleibt immer schwarz. Ich denke daher, dass es mit der Farbe "clDefault" zu tun hat. Wenn ich nämlich z. B. clWhite wähle, dann werden mir an den "gelöschten" Positionen weiße Kreise gezeichnet, so wie erwartet.

Meine Frage nun: Warum funktioniert es mit clDefault nicht? Wie ist die clDefault-Farbe (die Farbe des Formulars bzw. der PaintBox im Ursprungszustand) ausgedrückt im RGB-Modell? Dann könnte ich "clDefault" durch diesen Wert ersetzen, was zum gewünschten Ergebnis führen sollte. Im Internet habe ich leider nirgendwo einen Wert gefunden.
Zuletzt geändert von Scurra am Mo 19. Jan 2015, 20:53, insgesamt 1-mal geändert.

Benutzeravatar
theo
Beiträge: 10861
Registriert: Mo 11. Sep 2006, 19:01

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von theo »

Das macht man anders.

Der Timer sollte nur "Paintbox1.Invalidate" aufrufen (Oder u.U. auch nur InvalidateRect, aber erst mal nicht.)
Der Zeichencode sollte in das OnPaint Ereignis der Paintbox.
Dort sollte immer der ganze Kram neu gezeichnet werden, also
Paintbox1.Canvas.FillRect(...
und dann den Kreis drauf.

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Ok, wenn ich deinen Vorschlag umsetze, dann funktioniert es. Meine Umsetzung hat an sich auch funktioniert, wenn ich zum Übermalen nicht die Farbe "clDefault" verwende. Gibt es hierfür eine simple Erklärung (also warum es mit "clDefault" nicht funktioniert)? Denn ich finde, mein Quelltext sieht richtig aus ;) (Auch wenn du sagst, dass man es eigentlich anders macht.)

Zwei Probleme hätte ich hinsichtlich einer PaintBox noch:

1. Den kompletten Inhalt der PaintBox zu entfernen und wieder neu bzw. verändert zu zeichnen, scheint mir sehr wenig elegant zu sein. Man stelle sich nur einmal vor, dass man sehr viel gezeichnet hat und nur an einer Stelle etwas ändern möchte (so wie ich eben meinen Kreis von der einen Position zur nächsten Position springen lassen möchte). Gibt es da keine bessere Methode? Noch schlimmer wird es, wenn man die Elemente in der Zeichnung nicht mehr gespeichert hat. Angenommen ich zeichne nacheinander zwei Kreise, merke mir aber immer nur, wo der letzte Kreis gezeichnet wurde. Wenn ich nun alles wieder übermale und neu zeichnen möchte, dann habe ich den ersten Kreis verloren...

2. Mir ist aufgefallen, dass die Pixelgröße in einer PaintBox ziemlich groß ist. Gerade, wenn ich langsame Bewegungen darstellen möchte, dann sieht die Bewegung sehr ruckartig aus. Gibt es eine Möglichkeit, die Größe der Pixel zu verkleinern (also mehr Pixel auf der gleichen Fläche zu schaffen)?

Komoluna
Beiträge: 565
Registriert: So 26. Aug 2012, 09:03
OS, Lazarus, FPC: Windows(10), Linux(Arch)
CPU-Target: 64Bit

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Komoluna »

Normalerweise entspricht ein Bildschirmpixel einem Pixel auf deinem Canvas.
Du könntest Anti-Aliasing verwenden, um die Bewegungen flüssig aussehen zu lassen. Dies auf dem CPU zu machen ist jedoch sehr inperformant, da würde ich Grafikbibliotheken wie z.B. OpenGL empfehlen (siehe http://wiki.delphigl.com/index.php/Hauptseite )

P.S.: Da dein Projekt nur 2D benötigt, guck dir das hier an: http://wiki.delphigl.com/index.php/Tutorial_2D

MFG

Komoluna
Programmer: A device to convert coffee into software.

Rekursion: siehe Rekursion.

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Das Problem mit der Pixelgröße ist, dass ich beim Zeichnen nur Integer für die Koordinaten verwenden kann. Wenn ich nun eine neue Position für mein Objekt berechne (als Double) und das Objekt an der neuen Position zeichnen möchte, dann muss ich bei der Angabe der Koordinaten die Funktion Round() verwenden, die mir eine Ganzzahl zurück gibt. So kann es sein, dass z. B. 100 Berechnungen lang immer auf den gleichen Wert gerundet wird und anschließend bewegt sich der Punkt einen Pixel weiter.

Mein "Projekt" ist nur ganz klein. Ich möchte mir Himmelskörper als Punkte darstellen lassen und diese dann entsprechend der Gravitationskraft bewegen lassen.

Benutzeravatar
theo
Beiträge: 10861
Registriert: Mo 11. Sep 2006, 19:01

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von theo »

Scurra hat geschrieben:Das Problem mit der Pixelgröße
Kannst du mal illustrieren, wie du das meinst?
Die Pixelgröße hängt vom Monitor ab, aber das meinst du wohl kaum.

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Wenn ich einen Kreis zeichnen möchte, verwende ich die Ellipse-Prozedur, die als Parameter 4 Integerwerte haben möchte. Angenommen ich habe meinen "Himmelskörper" (einen Kreis) anfangs bei (0, 0). Dann zeichne ich diesen mit

Code: Alles auswählen

PaintBox1.Canvas.Ellipse(0-size, 0-size, 0+size, 0+size);
Wobei size die Größe des Kreises bestimmt. Nun berechne ich in meinem Programm die Kräfte auf meinen Himmelskörper und berechne, wo sich der Himmelskörper im nächsten Moment (z. B. nach der Zeit t = 1sec) befindet. Dann erhalte ich z. B. den Punkt (0.1, 0). Da die Ellipse-Procedur aber Integerwerte haben möchte, muss ich die Werte runden. Mein neuer Punkt liegt dann bei (Round(0.1), Round(0)) = (0, 0). Dann wird mir im nächsten Schritt wieder ein Kreis bei (0, 0) gezeichnet, d. h. visuell bewegt sich da noch nichts, obwohl sich der Himmelskörper eigentlich schon bewegt. Falls sich der Himmelskörper weiterhin mit dieser langsamen Geschwindigkeit bewegen würde, dann würde der Kreis erst im 5. Schritt um 1 Pixel nach rechts verschoben werden. Meine Paintbox geht über etwa 3/4 des Bildschirms und hat Maße von 1480x1000.

Ich meine mich zu erinnern, dass man mit Python (wo ich dieses Problem zum ersten Mal programmiert habe) auch Double als Parameter zum Zeichnen eines Kreises verwenden kann, so dass eine Bewegung auch dann schon erkennbar ist, wenn eine Verschiebung von (0, 0) zu (0.1, 0) stattfindet. Ich frage mich, ob das mit Delphi auch irgendwie möglich ist. Aber vllt. stoße ich damit auch wirklich schon an die Grenzen der Auflösung.

Evtl. habe ich mein Problem zuvor schlecht bzw. mit den falschen Begriffen erklärt. Falls das so sein sollte, tut es mir Leid und ich hoffe, dass es jetzt klarer ist.

P.S.: Da ich jetzt in mein Programm eine Funktion eingebaut habe, mit der ich den Kreisen Farben nach dem RGB-Modell geben kann, habe ich herausgefunden, dass für die Farbe clDefault gilt: clDefault = 15790320. Das entspricht 240 rot, 240 grün und 240 blau.

Benutzeravatar
theo
Beiträge: 10861
Registriert: Mo 11. Sep 2006, 19:01

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von theo »

Scurra hat geschrieben:Aber vllt. stoße ich damit auch wirklich schon an die Grenzen der Auflösung.
Wahrscheinlich. :roll:
Ist natürlich schwierig, die kleinste Darstellungseinheit noch zu unterbieten. :wink:

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

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von wp_xyz »

Da die Ellipse-Procedur aber Integerwerte haben möchte, muss ich die Werte runden.
Nein - die Rechnung muss du immer mit der höchsten Genauigkeit ausführen, es baut ja das Ergebnis zu einer bestimmten Zeit auf dem zuvor berechneten auf. Erst wenn du die Ausgabe auf der Paintbox machst, wird gerundet.

Im 1. Beitrag schreibst du auch von einem Timer. In welchen Intervallen feuert der denn? Wenn die zu groß sind, muss die Darstellung notgedrungen ruckeln. Ich kann mir nicht vorstellen, dass eine graphishe Darstellung heutzutage mit der HD-Bildschirmauflösung wegen Pixelauflösung ruckeln kann.

Zu dem Wunsch noch, nur die geänderten Bestandteile auszugeben: Davon würde ich abraten, das stammt aus DOS-Zeiten. Heutzutage ist dein Programm nicht mehr allein, sondern hängt von anderen Programmen ab. Was ist wenn der Bildschirmschoner angeht und später vom Benutzer wieder deaktiviert wird? Was ist, wenn der Benutzer ein anderes Fenster über deine Ausgabe schiebt und es dann wieder wegzieht? Plötzlich ist alles weg und es werden nur noch ein paar Pixel neu gezeichent. Eine Grundregel der interaktiven Programmierung ist, dass sich jedes visuelle Objekt zu jedem Zeitpunkt komplett neu zeichnen können muss.

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Nein - die Rechnung muss du immer mit der höchsten Genauigkeit ausführen, es baut ja das Ergebnis zu einer bestimmten Zeit auf dem zuvor berechneten auf. Erst wenn du die Ausgabe auf der Paintbox machst, wird gerundet.
So mache ich das auch. Die Instanzen der von mir definierten Klasse für die Himmelskörper haben als Eigenschaft die x- und y-Position, die ich jeweils als Double angebe. Wenn ich Ellipse(...) aufrufe, setze ich aber gerundete Werte ein.
Im 1. Beitrag schreibst du auch von einem Timer. In welchen Intervallen feuert der denn? Wenn die zu groß sind, muss die Darstellung notgedrungen ruckeln. Ich kann mir nicht vorstellen, dass eine graphishe Darstellung heutzutage mit der HD-Bildschirmauflösung wegen Pixelauflösung ruckeln kann.
Ich habe einen Scroll-Bar für das Interval des Timers und kann den Timer zwischen 1ms und 1000ms variieren. Für eine schöne Darstellung wähle ich aber eigentlich immer Werte zwischen 1 und 10 ms. Das ruckeln fällt mir auf, wenn ich einen "schweren" Kreis in die Mitte setze und einen kleinen, der sich außen herumdreht. Dann beginnt sich natürlich auch der "schwere" kreis, sich zu bewegen (wenn auch langsam), weil sich die Körper gegenseitig anziehen. Dann kann es sein, dass sich der kleine Kreis schon ein halbes Mal um den großen gedreht hat (so 10-100 Schritte dauern kann) und erst dann bewegt sich der große ein Stück weiter.

Wahrscheinlich habe ich die Darstellung meiner Simulation mit Python nur falsch in Erinnerung und da war es genauso rucklig. ;)
Zu dem Wunsch noch, nur die geänderten Bestandteile auszugeben: Davon würde ich abraten, das stammt aus DOS-Zeiten. Heutzutage ist dein Programm nicht mehr allein, sondern hängt von anderen Programmen ab. Was ist wenn der Bildschirmschoner angeht und später vom Benutzer wieder deaktiviert wird? Was ist, wenn der Benutzer ein anderes Fenster über deine Ausgabe schiebt und es dann wieder wegzieht? Plötzlich ist alles weg und es werden nur noch ein paar Pixel neu gezeichent. Eine Grundregel der interaktiven Programmierung ist, dass sich jedes visuelle Objekt zu jedem Zeitpunkt komplett neu zeichnen können muss.
Ok, das werde ich mir merken. Was passiert denn, wenn der Bildschirmschoner an war und nun plötzlich meine Paintbox wieder angezeigt wird? Welche Funktion/Prozedur wird dann intern aufgerufen?

Danke jedenfalls für eure Hilfe.

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

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von wp_xyz »

Was passiert denn, wenn der Bildschirmschoner an war und nun plötzlich meine Paintbox wieder angezeigt wird? Welche Funktion/Prozedur wird dann intern aufgerufen?
Paintbox.Invalidate, d.h. das, was du im OnPaint-Ereignis vorgesehen hast.

Kannst du dein Programm hier posten? Wenn wir sehen und nachvollziehen können, was du genau machst, sparen wir uns einiges Hinundherschreiben. (pas, lfm, lpr und lpi-Dateien in ein zip packen und hier hochlagen, kein exe, ppu etc, die genannten Dateien reichen, das Projekt zu übersetzen).

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

Kannst du dein Programm hier posten? Wenn wir sehen und nachvollziehen können, was du genau machst, sparen wir uns einiges Hinundherschreiben. (pas, lfm, lpr und lpi-Dateien in ein zip packen und hier hochlagen, kein exe, ppu etc, die genannten Dateien reichen, das Projekt zu übersetzen).
Ich habe den Ordner im Anhang. Ich wollte nur niemandem zumuten, sich durch meinen Quelltext durchzukämpfen ;)
Dateianhänge
Sonnensystem.zip
(6.94 KiB) 108-mal heruntergeladen

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

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von wp_xyz »

Was muss man denn für Startparameter verwenden, dass der Fehler auftritt?

Scurra
Beiträge: 29
Registriert: Mi 31. Dez 2014, 12:08

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von Scurra »

wp_xyz hat geschrieben:Was muss man denn für Startparameter verwenden, dass der Fehler auftritt?
1. Körper:
- Masse: 100000000000000
- Körper hinzufügen
- Größe: 20
--> Button "Körper hinzufügen"

2. Körper:
- Name (beliebige eingabe, muss sich vom Name des ersten Körpers unterscheiden)
- x-Position: 100
- y-Position: 100
- x-Geschw.: 7
- y-Geschw.: -7
- Masse: 100000000000
(-Optional: Größe: 7)
--> Button "Körper hinzufügen"

Den Timer (Scrollbar ganz oben) kann man dann z. B. auf 15 stellen.

Anschließend auf "Start" drücken. Aber inzwischen glaube ich auch, dass das "Problem" durch die begrenzte Auflösung entsteht und sich wohl nicht ändern lässt

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

Re: PaintBox-Komponente und clDefault-Farbe

Beitrag von wp_xyz »

Na sooo schlimm ist das Ruckeln jetzt auch wieder nicht, ich finde, die Simulation läuft sehr glatt, vielleicht meint man ab und zu einen winzigen Stolperer zu sehen. Sorgen würde ich mich eher darum, dass die Simulation kein stabiles Ergebnis liefert und der Radius der Planetenbahn immer größer wird. Ich denke mal, dass deine Rechnung da zu vereinfacht ist, weil du die Formeln für Weg/Geschwindigkeit/Beschleunigung einer linearen Bewegung auf eine kreis-/ellipsenförmige Bewegung anwendest, das geht angenähert nur für ganz kurze Wegstücke, und auch da werden sich die Fehler allmählich aufsummieren, so dass die Bahn divergiert.

Einen Hinweis noch: Für die Gravitationskonstante kannst du statt G := 6.67384/power(10, 11) besser G := 6.67384E-11 schreiben.

Antworten