paintbox freihandlinie zeichnen
-
Hallo,
ich nutze eine paintbox, um eine Freihandlinie zu zeichnen:void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (Drawing) PaintBox1->Canvas->LineTo(X, Y); // only draw if mouse is down } } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { Drawing = true; // set the Drawing flag PaintBox1->Canvas->MoveTo(X, Y); // set pen position } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { PaintBox1->Canvas->LineTo(X, Y); // draw line from PenPos to (X, Y) Drawing = false; // clear the Drawing flag }
Problem ist natürlich, dass dieses Zeichnen nicht permanent ist. D.h. nach z.B. dem Minimieren und anschließendem Maximieren des Fensters ist die Zeichnung weg.
Wie kann ich es machen, dass die Zeichnung z.B. nach dem MouseUp-Event permanent gespeichert wird?
-
Zeichne auf ein TImage.
-
Dies ist aber nicht gut, da ich unter der Paintbox ein Timage liegen habe, damit ich "auf" dieses zeichnen kann und auch meine Freihandlinie wieder löschen kann.
-
Pack alle Koordinaten in einen std::vector und zeichne dann die Linien anhand der gespeicherten Koordinaten.
-
Kannst du mir mit std::vector etwas Beispielcode geben? Danke!
-
-
Verstehe ich das richtig, dass dann aber OnPaint immer wieder aufgerufen werden muss und dort jedesmal die kompletten Linienstücke neu gezeichnet werden müssen?
-
Weder sollst du
OnPaint
selbst aufrufen (das macht das Betriebssystem für dich), noch sollst du in irgendwelchen anderen Eventhandlern wieOnMouseDown
/OnMouseMove
OnMouseUp
irgendwelche Zeichenroutinen aufrufen. Alle Zeichenvorgänge haben inOnPaint
stattzufinden, sonst hast du undefiniertes und wahrscheinlich inkonsistentes Verhalten. Und ja, du mußt immer alle Linienstücke zeichnen. (Das ist nicht ganz korrekt; eigentlich solltest du das Canvas nach einem Clipping-Rechteck fragen, um zu erfahren, welchen Teilbereich des Bildes du neuzeichnen mußt. Aber das macht die Sache jetzt nur unnötig kompliziert, und das einfachste ist, du zeichnest immer alles.)
-
Dann ist also mein obiger Code Müll?
Und ich müsste in OnPaint in z.B. einem dyn. Vector jede Linie, die ich mit der Mouse zeichne (inkl. Stiftfarbe, Strichstärke,..) speichern, solange ich einen weiteren Strich hinzufüge? Und wenn ich die Zeichnung löschen will, dann wird auch der Inhalt des Vectors wieder gelöscht?
Hört sich kompliziert an. Gibt es hierfür evtl. irgendwo eine "Musterlösung"?
Danke!
-
williman schrieb:
Und ich müsste in OnPaint in z.B. einem dyn. Vector jede Linie, die ich mit der Mouse zeichne (inkl. Stiftfarbe, Strichstärke,..) speichern, solange ich einen weiteren Strich hinzufüge? Und wenn ich die Zeichnung löschen will, dann wird auch der Inhalt des Vectors wieder gelöscht?
Entweder das, oder du zeichnest deine Linien einfach auf ein transparentes Bitmap, das du dann wiederum in
OnPaint
aufs Canvas zeichnest. Hat beides Vor- und Nachteile, aber letzteres ist einfacher umzusetzen und auch effizienter.williman schrieb:
Hört sich kompliziert an. Gibt es hierfür evtl. irgendwo eine "Musterlösung"?
In den C++Builder-Demoprojekten gibt es ein Projekt namens "Graphex", das demonstriert, wie man in das Bitmap einer
TImage
-Komponente zeichnet. Das geht ganz analog mit deinem eigenen Bitmap. Um inOnPaint
dein transparentes Bitmap aufs Canvas zu zeichnen, verwendeTCanvas::Draw()
.
-
Danke audacia,
genau das meinte ich.
-
Deinen Vorschlag mit dem malen aufs transparente Bitmap und am Ende per OnPaint in Paintbox per Draw habe ich nun getestet u. funktioniert auch, allerdings bekomme ich das Bitmap nicht transparent gemacht. Das Bitmap hat immer die Hintergrundfarbe weiß. Ein dahinter liegendes Timage sollte aber hindurch scheinen:
TForm1... DrawingBoard = new Graphics::TBitmap; DrawingBoard->Width = PaintBox1->Width; DrawingBoard->Height = PaintBox1->Height; DrawingBoard->Transparent = true; // <-- damit sollte das Bitmap //transparent sein, wird es aber nicht !!! DrawingBoard->TransparentMode = tmAuto; DrawingBoard->Canvas->Pen->Color = PaintBox1->Canvas->Pen->Color; DrawingBoard->Canvas->Pen->Width = PaintBox1->Canvas->Pen->Width; // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { PaintBox1->Canvas->Draw(0, 0, DrawingBoard); } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (IsDrawing) { PaintBox1->Canvas->LineTo(X, Y); // only draw if mouse is down DrawingBoard->Canvas->LineTo(X, Y); } } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { IsDrawing = True; PaintBox1->Canvas->MoveTo(X, Y); DrawingBoard->Canvas->MoveTo(X, Y); } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if( IsDrawing ) { PaintBox1->Canvas->Draw(0, 0, DrawingBoard); IsDrawing = False; } }
Wie bekomme ich die Transparenz bei Drawingboard hin?
-
Einfacher Test:
// MainUnit.h #include <memory> class TForm1 : public TForm { ... void __fastcall PaintBox1Paint(TObject *Sender); private: // Anwender-Deklarationen std::unique_ptr<Graphics::TBitmap> bmp; // <-- bitte einen Smartpointer nehmen! ... };
// MainUnit.cpp __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { bmp.reset(new Graphics::TBitmap()); // SetSize() ist effizienter als das separate Setzen von Width/Height bmp->SetSize(Image1->Picture->Width, Image1->Picture->Height); bmp->Transparent = true; bmp->TransparentColor = clFuchsia; // Bitmap löschen und transparent machen bmp->Canvas->Brush->Color = clFuchsia; bmp->Canvas->FillRect(Rect(0, 0, bmp->Width, bmp->Height)); // irgendwas zeichnen bmp->Canvas->Ellipse(50, 50, 300, 200); } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { PaintBox1->Canvas->Draw(0, 0, bmp.get()); }
-
Eine gezeichnete Linie ist leider sehr zackig. Leider ist wohl eine Kantenglättung (Antialiasing) hierbei nicht möglich. Hierfür müsste man GDI+ verwenden. Auch nach langer Recherche komme ich noch nicht sehr weit. Ich müsste GDI+ mit der PaintBox kombinieren.
Initialisierung von GDI+ funzt. Aber wie gehts weiter? Wo packe ich ein Drawline hin bzw. wie müssten die PaintBox-Events geändert werden?
Danke!