ich weiß, du gibst ungern Sourcecodes
Das liegt in 9 von 10 Fällen daran, daß derjenige, der nach dem Source fragt, mit an Sicherheit grenzender Wahrscheinlichkeit nichts damit anfangen kann, weil eine 1-Zeilen-Lösung für komplexe Probleme erwartet wird.
aber wie machst du es generell, wenn du ein Bild von dem Programm erstellen und anzeigen lassen willst?
Ich denke, an der Stelle sollte ich mal ein paar Takte darüber ablassen, wie ich die graphischen Komponenten von Delphi betrachte und kennen gelernt habe.
Der unterste Level ist der TCanvas. So ziemlich jede visuelle und/oder graphische Komponente hat einen TCanvas. Egal ob TImage, TPicture, TBitmap, TShape, TPaintBox, TForm, TButton etc.pp., überall gibt es einen TCanvas, mittels dem gezeichnet werden kann.
Einige Leute zeichnen direkt auf den TCanvas des Hauptformulars, doch ich war noch nie ein Fan davon. Denn zwischen Zeichnen und Anzeigen liegt ein deutlicher Unterschied, wie du ja auch schon festgestellt hast.
Ok, wozu sind dann die anderen graphischen Komponenten gut?
TBitmap ermöglicht die einfachsten Lade- und Speicheroptionen für die einfachste Bildart überhaupt, eine unkomprimierte Bitmap. D.h. jeder Pixel der Bitmap wird im Idealfall (24 Bit Farbtiefe) durch 3 Byte repräsentiert: Rot, Grün, Blau. Klar gibt's da noch Feinheiten, aber um die nachzulesen gibt's ja die Onlinehilfe.
TPicture kapselt die TBitmap und ermöglicht erweiterte Lade- und Speicheroptionen. Schau dir auch das mal in der Onlinehilfe an, da ist sehr schön beschrieben, welche Eigenschaften und Methoden TPicture selbst mitbringt und welche von TBitmap oder TCanvas stammen.
TImage wiederum kapselt ein TPicture und bietet v.a. folgende Möglichkeiten: Bildproportionen verankern, Bild zentriert in einem Bereich anzeigen, Bild skalieren (und das sehr schnell). Den Rest von TImage habe ich noch nicht so intensiv verwendet.
Am Rande noch: Mit TShape lassen sich einfachste geometrische Figuren darstellen und TPaintBox soll einfach nur dafür sorgen, daß nicht unmittelbar auf den Canvas der TForm gepinselt wird sondern auf einen festgelegten Bereich - nämlich die TPaintBox.
Dann werfen wir mal einen genaueren Blick auf TImage. Timage beinhaltet TPicture - und einen TCanvas. Jup, das TImage hat einen eigenen
TCanvas. Und auch das TPicture hat einen eigenen TCanvas. Und die TBitmap im TPicture im TImage hat auch einen eigenen TCanvas! Was soll denn
der Blödsinn?
TImage
|
+---> TCanvas
|
+--->TPicture
|
+---> TCanvas
|
+---> TBitmap
|
+---> TCanvas
Welches ist jetzt der richtige?
Du kannst dir das grob vereinfacht so vorstellen:
Den TCanvas von TPicture kannst du vergessen. Der hat keinen echten Wert. Der TCanvas von TBitmap ist das, was an TImage zur Darstellung weitergereicht wird. Bevor TImage aber das Bild in TBitmap anzeigt, wird es noch umgewandelt (skaliert, zentriert, proportioniert usw., was TImage halt so gemäß seiner Einstellungen mit dem Bild macht). Wenn TImage dann endlich fertig ist mit dem Verwursten der Bilddaten,
dann werden die an den TCanvas von TImage durchgereicht und auf den Bildschirm gezeichnet.
Klar soweit? (im Ton von Cpt. Jack Sparrow)
Nachdem ich mich mal durch diesen Wust durchgewühlt hatte, habe ich zwei Dinge beschlossen:
1. Soweit und wann immer möglich, bearbeite ich Bilddaten als TBitmap.
2. Ich zeichne niemals direkt auf einen TForm.Canvas, sondern verwende als Anzeigefläche immer ein TImage. Denn mit diesem TImage kann ich die Anzeige noch ein wenig steuern und sehr viel bequemer erweitern als wenn alles im TForm in einem Haufen vorliegt.
Damit ist der allererste Teile deiner Frage beantwortet.
Den Smily würde ich also (für's Erste) als Bitmap speichern.
Auf das Formular würde ich ein TImage knallen. Dieses heisst bei mir normalerweise "Image". Sehr phantasielos, ich weiß. Aber da ich in der Regel nur ein einziges Image verwende, ist es dann doch eindeutig.
Zum Laden und Anzeigen des Smilys würde ich dann so vorgehen:
Code:
prodecure ShowSmily;
var
bmSmily: TBitmap;
begin
bmSmily := TBitmap.Create;
bmSmily.LoadFromFile('smily.bmp');
// Nach dem Laden ist kein explizites Zuweisen von Width und Height erforderlich.
// Diese werden implizit durch das Laden des bmp-Files gesetzt
// Ich sitze jetzt gerade nicht am Compiler, deswegen kann ich nicht
// ausprobieren, ob die TBitmap unten in TImage defaultmässig schon
// existiert. Daher diese "Nummer Sicher"
if Image.Picture = nil then
Image.Picture := TPicture.Create;
if Image.Picture.Bitmap := nil then
Image.Picture.Bitmap := TBitmap.Create;
// Ok, furzegal, was ist, ich habe jetzt mal meine Bitmap mit dem Smily
// und es sind alle Ebenen von TImage eingerichtet. Auch wenn die Bitmap
// von TImage nichts anzeigt, weil bislang weder eine Bitmap geladen noch
// die Dimensionen der Bitmap eingerichtet worden sind.
Image.Picture.Bitmap.Assign(bmSmily);
// Tadaaa! Jetzt wäre schon mal der Smily in der Bitmap des Image.
// Ich habe aber schlechte Erfahrungen mit Assign gemacht, vor allem,
// wenn das Ursprungsobjekt, wie in diesem Falle die bmSmily am
// Prozedurende wieder aufgeräumt wird.
// Aus diesem Grund bevorzuge ich das explizite Kopieren der Bilddaten
// und deren explizite Zuweisung.
Image.Picture.Bitmap.Width := bmSmily.Width;
Image.Picture.Bitmap.Height := bmSmily.Height;
Image.Picture.Bitmap.Canvas.CopyRect(Rect(0, 0, bmSmily.Width - 1, bmSmily.Height - 1), bmSmily, Rect(0, 0, bmSmily.Width - 1, bmSmily.Height - 1));
// Da jetzt die Bilddaten von bmSmily ganz ausdrücklich kopiert und damit
// vervielfältigt wurden, sind die Ursprungsdaten nicht mehr notwendig.
FreeAndNil(bmSmily);
// Kann aber immer noch sein, daß der Smily nicht korrekt angezeigt wird.
// Das ist u.a. davon abhängig, was alles im Hintergrund auf dem Rechner
// läuft, welche Prozeßpriorität dieses Programm hier hat usw.
// Da mir das aber alles zu blöd ist, sage ich dem Programm mit Brachialgewalt
// "Hallo, da wurde ein neues Bild geladen, zeige das gefälligst an!":
Image.Invalidate;
// Oder alternativ:
Image.Refresh;
end;
Mit dem Source kannst du jetzt eine beliebige Bitmap laden und anzeigen. Bewegen ist aber wieder eine andere Baustelle.
Wenn's um Animation geht, kommen mir sehr viele Leute immer gleich mit OpenGL, DirectX und wasweissichnichtalles an. Und erklären mich dann für irre, wenn ich behaupte, daß sich am Prinzip der Animation seit 1906 nichts Grundlegendes geändert hat.
Das Prinzip ist bis heute das gleich:
- Hintergrund zeichnen
- Smily auf den Hintergrund zeichnen
- Hintergrund neu zeichnen
- Smily versetzt auf den Hintergrund zeichnen
- Hintergrund neu zeichnen
- Smily noch etwas weiter versetzt auf den Hintergrund zeichnen
usw.usf.
Und das Ganze richtig schnell. Je schneller es geht, desto flüssiger erscheint die Animation (genau das drückt der Begriff "frames per second" aus, der aber wiederum so ziemlich jedem Gamer bekannt ist...)
Mist, daß der Smily im Codestück weiter oben direkt in den Hintergrund (nämlich die TBitmap des TPicture des TImage) gezeichnet wurde.
Aus diesem Grunde arbeite ich normal mit mehreren Bitmaps: Eine Bitmap für den Hintergrund, eine Bitmap für den Smily, eine Bitmap zum Zusammenführen des Ganzen. Und dann wird das Zusammengeführte in die Bitmap des TImage geleitet und fertig ist der Käse.
Hier ein einfacher, unkommentierter Source, der dieses Geschehen veranschaulichen soll:
Code:
procedure Demo;
var
bmBackground: TBitmap;
bmSmily: TBitmap;
bmBlitter: TBitmap;
i: Integer;
begin
if Image.Picture = nil then
Image.Picture := TPicture.Create;
if Image.Picture.Bitmap = nil then
Image.Picture.Bitmap := TBitmap.Create;
Image.Picture.Bitmap.Width := 800;
Image.Picture.Bitmap.Height := 600;
Image.Width := 800;
Image.Height := 800;
bmBackground := TBitmap.Create;
bmBackground.LoadFromFile('background.bmp');
bmSmily := TBitmap.Create;
bmSmily.LoadFromFile('smily.bmp');
bmBlitter := TBitmap.Create;
bmBlitter.Width := Image.Picture.Bitmap.Width;
bmBlitter.Height := Image.Picture.Bitmap.Height;
for i := 0 to 700 do
begin
bmBlitter.Canvas.CopyRect(Rect(0, 0, bmBackground.Width - 1, bmBackground.Height - 1), bmBackground, Rect(0, 0, bmBackground.Width - 1, bmBackground.Height - 1);
bmBlitter.Canvas.CopyRect(Rect(i, 50, i + bmSmily.Width, 50 + bmSmily.Height), bmSmily, Rect(0, 0, bmSmily.Width - 1, bmSmily.Height - 1));
Image.Picture.Bitmap.Canvas.CopyRect(Rect(0, 0, bmBlitter.Width - 1, bmBlitter.Height - 1), bmBlitter, Rect(0, 0, bmBlitter.Width - 1, bmBlitter.Height - 1));
Image.Refresh;
end;
FreeAndNil(bmBackground);
FreeAndNil(bmSmily);
FreeAndNil(bmBlitter);
end;
Wie im Sourcekommentar weiter oben erwähnt, habe ich gerade keinen Compiler zur Hand zum Austesten, aber ich bin mit ziemlich sicher, daß die Sources funktionieren oder wenn dann nur geringe Fehler drin sind (z.B. bin ich mir mit den -1 bei den Dimensionierungen der CopyRects gerade nicht 100% sicher).
Ich hoffe, das hilft jetzt etwas weiter.