Die Windows GDI+ (Teil 1)



  • Die Windows GDI+ (Teil 1)

    In diesem C++ Artikel geht es um die relativ neue Grafikbibliothek GDI+ für Windows.

    1 Einleitung
    2 Technische Voraussetzungen
    3 Sauberes C++ von Microsoft
    4 Was kann die GDI+?
    5 Hello World!
    6 Mächtige Graphics
    7 Ausblick
    A Links

    1 Einleitung

    Wer bisher in seinen Windows-Anwendungen Grafikfunktionen benötigt hat, hat meistens die alte GDI-API benutzt. Immerhin erfüllt sie ihren Zweck und ist seit eh und je in Windows vorhanden. Für C++ Programmierer ist sie jedoch leider eine API, die nicht auf einer OO-Sprache basiert, obwohl sie einen objektorientieren Ansatz verfolgt. Doch könnte es für einen C++ Programmierer viel komfortabler sein, komplexere Grafiken zu erstellen, als dies mit der GDI der Fall ist. Um zu ansprechenderen Ergebnissen in kürzerer Zeit zu gelangen, muss man sich mit der GDI aktuelle Effekte selbst entwickeln oder kombinieren.

    Mit Erscheinen von Windows XP hat Microsoft jedoch die Zeichen der Zeit erkannt und eine leistungsfähigere API eingeführt. Und schon sind wir bei der neuen GDI+-Bibliothek angelangt.

    2 Technische Voraussetzungen

    Die GDI+ ist in jedem Windows XP serienmäßig dabei, ist jedoch nicht auf Windows XP beschränkt. Es kann auch auf Windows 2000, Windows NT 4.0 SP6 und Windows 98/ME mit einer zusätzlichen DLL ohne Installation eingesetzt werden. Die kostenlose GDI+ für Endbenutzer ist auf der GDI+ Homepage als Download verfügbar.

    3 Sauberes C++ von Microsoft

    Die Entwickler von Microsoft haben mit der GDI+ eine Bibliothek entworfen, die einem C++-Programmierer nicht peinlich sein muss. Keine Makros, keine Präfixe, sondern Namespaces und klare Parameternamen. Die Freigabe von Ressourcen, wie sie in der alten GDI nötig war, entfällt durch die Fähigkeiten der C++-Stackobjekte ebenfalls. Alles das hätten wir uns von Microsoft viel früher gewünscht. Aber lieber spät als nie.

    Vom Programmierer werden Kenntnisse in C++ und der Objektorientierung verlangt, wobei diese auf einer einfachen Ebene stattfinden ("weil einfach einfach einfach ist!"). Templates o.Ä. muss man nicht kennen, was die GDI+ auf jeden Fall auch für C++-Einsteiger sehr interessant macht. Besonders diese können schnell und einfach in die Grafikprogrammierung einsteigen, ohne in die Gefahr der Speicherlecks o.Ä. zu laufen.

    4 Was kann die GDI+?

    Wen die oben genannten Argumente immer noch nicht überzeugt haben, wird dies ganz bestimmt durch die Features der GDI+ werden. Prinzipiell deckt die GDI+ alle Bereiche der 2D-Grafik ab, solange man kein Actionspiel programmieren will. Aber 2D-Vectorgrafik mit Transformationen und Translationen in Kombination mit Spezialeffekten sind für jeden intuitiv umzusetzen. Weiterhin unterstützt werden das Codieren und Decodieren von wichtigen Grafikformaten, dazu gehören z.B. JPEG, PNG, TIFF u.a. Diese Bitmaps kann man ebenfalls, wie die Vectorfunktionen, mit Effekten versehen und transformieren (z.B. skallieren). Wie das alles sehr einfach, schnell und intuitiv umzusetzen ist, werde ich jetzt endlich zeigen.

    5 Hello World!

    Alle nachfolgenden Beispiele sind von mir als VisualC++ 2003 Projekte mit Sourcecode im Download verfügbar:
    GDI-Plus-Tutorial.zip (174 KB)

    Ich gehe in diesem Tutorial nicht auf die Windows-Programmierung ein. Deshalb werde ich nur auf die relevanten Teile für die GDI+ eingehen. Um die GDI+ nutzen zu können, muss die gdiplus.lib dem Linker mitgeteilt werden und bei Nicht-WinXP-Systemen die gdiplus.dll in den Arbeitspfad gelegt werden. Wer mit Visual C++ ein Win32-Projekt anlegt, muss unbedingt das Makro WIN32_LEAN_AND_MEAN entfernen (wird im stdafx.h definiert), da man sonst Kompilierungsfehler bekommt. Schon steht dem Programmieren nichts mehr im Weg.

    Für ein erstes einfaches Beispiel werden wir die GDI+ initialisieren und eine Linie zeichnen.

    #include <windows.h>  // für die GDI+ wird auch windows.h benötigt
    #include <gdiplus.h>  // dieser Header ist für alle GDI+ Klassen, Funktionen usw.
    
    using namespace Gdiplus; // 01
    
    INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow) {
    
       GdiplusStartupInput gdiplusStartupInput;
       ULONG_PTR           gdiplusToken;   
       GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); // 02
       // ...
       paint(hdc);
       // ...
       GdiplusShutdown(gdiplusToken);  // 03
    }
    
    void paint(HDC hdc) {
       Graphics graphics(hdc);    // 04
       Pen      pen(Color(255, 0, 0, 255));   // 05
       graphics.DrawLine(&pen, 0, 0, 200, 100);   // 06
    }
    

    Erklärungen zu den einzelnen Zeilen:

    01: Um nicht vor jeder Klasse oder Funktion ein "Gdiplus::" zu schreiben, benutzen wir implizit den Namespace.

    02: Im Startpunkt unserer Anwendung (hier die WinMain-Funktion) sollten wir die GDI+ initialisieren. Da die GDI+ eine komplexere Initialisierung durchführt, ist dieser Aufruf nur einmalig zu empfehlen und nicht vor jedem Zeichenvorgang. Das gdiplusTocken (ein Pointer) darf nicht verloren gehen, damit man die GDI+ wieder deinitialisieren kann.

    03: Die GdiplusShutdown()-Funktion will den gültigen gdiplusToken-Pointer haben, um die GDI+ zu beenden. Dieser Aufruf kann und sollte erst kurz vor Beenden der Anwendung aufgerufen werden.

    04: Hier geht es endlich zur Sache. Gdiplus::Graphics ist die wichtigste Klasse, die uns begegnet. Sie stellt sozusagen die Zeichenfläche dar, auf der sich das Geschehen abspielt. In dem Beispiel beziehen wir die Zeichenfläche auf ein HDC-Device (z.B. ein Fenster). Wir hätten auch eine Bitmap übergeben können, welche sich nicht auf dem Bildschirm befindet. So könnte man Bitmaps im Hintergrund manipulieren.

    05: Zum Zeichnen benötigt man auch ein Werkzeug. Wir instanzieren hier einen Stift und übergeben dem Konstruktor gleich eine Wunschfarbe. Gdiplus::Color ist eine Farbenklasse die auch einen Alphachannel unterstützt. Somit sind auch transluzente Farben kein Problem.

    06: Auf unserer Zeichenfläche (graphics) können wir Aktionen ausführen, z.B. mit DrawLine eine Linie ziehen. Als Parameter erwartet diese Methode einmal das Werkzeug (unseren blauen Stift) und die X/Y-Koordinaten jeweils für den Start- und Endpunkt unserer Linie. Das Beispiel zeichnet eine blaue Linie von oben links diagonal nach unten rechts.

    Wie man an unserer paint()-Funktion sieht, können wir alle Objekte auf dem Stack anlegen und brauchen somit diese deshalb nicht wieder mit einem delete oder einer ominösen release()-Funktion freizugeben.

    6 Mächtige Graphics

    Das Graphics-Objekt legt man wahlweise vor jeder Zeichensequenz neu an oder einmalig und löscht sie jedes Mal mit der Clear()-Methode. Hat man das gemacht, kann man allerhand damit anstellen, denn sie verfügt über 188 Methoden.

    Sehr leicht kann man z.B. eine Figur (Rechteck, Ellipse, Pfade usw.) rotieren, verschieben und skallieren lassen. Diese Aktionen benötigen dabei jeweils nur eine einzige Zeile Programmcode. Das folgende Beispiel zeichnet erst ein Rechteck und verschiebt dann ein zweites Rechteck nach unten links und dreht es dann um 28°.

    Graphics graphics(hdc);
    
    	Rect rect(0, 0, 50, 50);  // 01
    	Pen pen(Color::Blue, 1);  // 02
    	graphics.DrawRectangle(&pen, rect);
    
    	pen.SetColor(Color::Red);
    
    	graphics.TranslateTransform(100,100); // 03
    	graphics.RotateTransform(28.0f);      // 04
    	graphics.DrawRectangle(&pen, rect);   // 05
    

    01: Wir wollen ein Rechteck. Hier wird es mit der Größe 50x50 Pixel instanziert.

    02: Unser Stift bekommt die Farbe blau, da die Klasse Color uns schon Enumerationen für viele Farben bereitstellt. Selbst Farben, die uns Männern nichts sagen, aber wohl den Damen, sind vordefiniert.

    03: Nachdem wir das erste Rechteck gezeichnet haben, bewegen wir unseren Grafikkontext um 100/100 (X/Y) Pixel.

    04: Jetzt rotieren wir den Grafikkontext um 28°.

    05: Auf Grund der vorherigen Aktionen wird unser Rechteck an einer ganz anderen Stelle gezeichnet. Das Rechteck selbst braucht man nicht verändern, es soll sich einfach nur zeichnen. Das hat den Vorteil, dass sich die Transformationen auf alle möglichen Figuren auswirken und somit die Figuren selbst keine Transformationsmethoden bieten müssen (kann man ganz gut mit dem STL-Konzept vergleichen, wo die Container auch keine Algorithmen à la Sortieren oder Suchen haben).

    Wer will, kann auch mit der Methode ScaleTransform() seine Figuren skallieren, d.h. in der Größe ändern.

    Hier noch ein Beispiel, das eindrucksvoll zeigt, wie man mit nur einer zusätzlichen Zeile Code (in dem Fall eine for-Schleife) etwas Spektakuläres zaubern kann:

    Graphics graphics(hdc);
    
    	Rect rect(0, 0, 50, 50);
    	Pen pen(Color::Red, 1);
    	graphics.TranslateTransform(100,100);
    
    	for(int i =0; i < 360; i=i+10) {     // 360° in 10° Schritten
    		graphics.RotateTransform(10);     // dynamische Rotation mehrmals durchlaufen
    		graphics.DrawRectangle(&pen, rect);
    	}
    

    Mit diesem Wissen können wir jetzt auch etwas praktisches mit der GDI+ umsetzen. Eine analoge Uhr ist dazu gerade ideal.

    Ist euch in dem obigen Screenshot das Anti-Aliasing (Kantenglättung) aufgefallen? Dies kann man mit der Graphics-Methode SetSmoothingMode() ein- und ausschalten. Wie man eine solche Uhr zeichnet, zeigt die Klasse kharchi::clock, welche ich hier im Quellcode bereitgestellt habe. Die Klasse unterliegt der BSD-Lizenz - ihr könnt sie also ändern, erweitern und in eigenen Projekten benutzen. Anwenden kann man sie wie folgt:

    using namespace Gdiplus;
    	Graphics graphics(hdc);
    
    	kharchi::clock c;
    	c.set_time(16, 55, 00);	// 16:55 Uhr
    	c.paint(graphics);
    

    7 Ausblick

    Das war jetzt der erste Teil, in dem ich euch die GDI+ hoffentlich schmackhaft machen konnte. Sicherlich waren die Ergebnisse auf dem Bildschirm nicht wahnsinnig spektakulär, doch an den Beispielcodes kann man sehen, dass man mit sehr wenig Programmieraufwand spektakulärere Ergebnisse zaubern kann. Im nächsten Teil werde ich etwas tiefer in die Möglichkeiten der GDI+ Figuren eingehen.

    A Links

    GDI+ Homepage
    Neueste SDK-Distribution
    GDI+ Reference



  • wurden alle bisherigen antworten gelöscht, oder hat echt keiner welche gegeben.

    @topic: sieht toll aus, freue mich auf mehr



  • Es gab bisher keine Antworten.

    Danke für das Feedback! 🙂



  • bietet gdi+ auch irgendwelche GUI funktionen oder dergleichen?



  • Wie meinst du das mit den GUI-Funktionen? GDI+ ist eine Grafikbibliothek, nicht mehr und nicht weniger. Wenn du GUIs programmieren willst, kannst du z.B. die MFC nehmen. Es gibt ja auch noch andere GUI-Libs, z.B. wxWidgets.

    Oder was hast du genau vor?



  • Artchi schrieb:

    Wie meinst du das mit den GUI-Funktionen? GDI+ ist eine Grafikbibliothek, nicht mehr und nicht weniger. Wenn du GUIs programmieren willst, kannst du z.B. die MFC nehmen. Es gibt ja auch noch andere GUI-Libs, z.B. wxWidgets.

    Oder was hast du genau vor?

    das meinte ich schon.



  • Hallo.

    Ich bin sicher es hat damit zu tun das ich grundsätzlich noch nicht so fit bit mit dem MVC++ 2005 Express und c++.
    Ich bekomme nur Fehlermeldungen.

    Folgendes habe ich gemacht.
    1. File/New Projekt
    2. Auswahl WIN32/Win32 Console Applikation
    3. Next => Windows Applikation
    4. OK drücken
    5. Wo muß ich den nun den Code einbinden?

    Sorry. Sicher dumme Frage.

    Danke jano



  • Eigentlich mußt du (wenn du ein Fenster erstellt hast) nur die Paint-Message nutzen und den HDC an GDI+ übergeben (wie im ersten Codebeispiel zu sehen).

    Bei diesem Artikel handelt es um eine GDI+-Einführung. Es wird nicht behandelt wie man ein Fenster öffnet oder dergleichen. Wenn du die GDI+ nutzen willst, mußt du dich erstmal informieren wie man ein Fenster öffnet - unabhängig von GDI+. Danach kannst und weißt du auch wo du den hier gezeigten Code implementieren mußt.

    Ich will hier auch nichts schreiben, wie man ein Fenster öffnet. Dazu würdes eines neuen Tutorial bedürfen, was ich nicht leisten kann/will.



  • Vielleicht noch als Tip, falls du garnicht weißt wo du mit Fensterprogrammierung anfangen sollst:

    http://smartwin.sf.net



  • Ich habe das Beispiel in einkleines Testprogramm übertragen. Beim Schreiben zeigt VC alle Auswahlmöglichkeiten (sprich Memberfunctionen) an. Auch das Kompilieren klappt reibungslos.
    Beim Linken hagelt es dann Fehlermeldungen.

    Um zu vermeiden, das VC irgendwelche Voreinstellungen vornimmte, habe ich das ganze nochmals mit einem leeren Projekt versucht. Fehlanzeige!

    Eine von 8 Fehlern !
    Error 1 error LNK2019: unresolved external symbol _GdipCreatePen1@16 referenced in function "public: __thiscall Gdiplus::Pen::Pen(class Gdiplus::Color const &,float)" (??0Pen@Gdiplus@@QAE@ABVColor@1@M@Z) Grafiktest.obj

    Ich habe die neueste SDK installiert, ebenso.

    Wer kann helfen?



  • Sind die anderen sieben Fehler auch unresolved external symbol bei GDI+ Funktionen? Hast du das hier beachtet:

    Um die GDI+ nutzen zu können, muss die gdiplus.lib dem Linker mitgeteilt werden



  • Die anderen Fehler sind ebenso "unresolved external symbol" !

    Ich vermute ja auch so einen Fehler, aber nach dem ich alle directories unter options nochmals überprüft habe ... ?!
    Unter den Show directories for: Libary files steht:
    C:\Programme\Microsoft Platform SDK for Windows XP SP2\lib

    in diesem Verzeichnis ist die lib auch vorhanden.

    Oder habe ich etwas übersehen.

    Danke für Eure Antworten.



  • Naja, wie ich geschrieben habe: du mußt die lib deinem Linker bekannt machen.

    Füge sie direkt in dein Projekt hinzu: Projekteigenschaften->Linker->Eingabe->Zus. Abhängigkeit. Dort einfach gdiplus.lib eintragen (ohne Pfad oder sowas). Dann weiß dein Linker, das er die lib dazulinken muß. Er sucht sie in den Pfadangaben, die du in der IDE eingetragen hast.

    Einige Bibliotheken (z.B. Boost) machen es heute schlauer, die tragen in ihre eigenen Header ein #pragma ein und sagen dem Linker dort, welche Lib gelinkt werden soll. Ist aber nicht immer der Fall, bei der GDI+ auch nicht. Also von Hand eintragen. 😉



  • Besten Dank für den Tip. 👍 Darauf muss man erst mal kommen. 💡

    Schade nur das in der Hilfe dazu kaum etwas steht.



  • Hmm.. ich habe leider ein paar Probleme mit GDI+

    Ich hab ein neues Win32 Projekt erstellt, die gdiplus.lib in den Linker eingetragen, und das Makro WIN32_LEAN_AND_MEAN aus der stadfx entfernt.

    Wenn ich nun den code einfüge, oder nur bei einem "Hallo Welt"-Projekt
    #Include "gdiplus.h" schreibe, spuckt er mir Fehler aus.

    Am Anfang hieß es immer er könnte die Datei nicht finden. Dabei stand im Text doch, dass sie ab Windows XP standartmäßig dabei wäre, und ich benutze XP.
    Ich hab mir dann die ganzen Header-Dateien, eine DLL und die lib-Datei runtergeladen und in den Projektordner eingefügt.
    Nun heißt es zwar nicht mehr, dass die Datei nicht existieren würde, aber Fehler bekomme ich dennoch. Ganz viele Zeilen in der Datei "gdiplusinit.h" (39, 40, und 97) verursachen Fehler.

    Ich weiß nicht was das jetzt bedeutet, und wie ich das beheben kann... kann mir jemand bitte helfen?



  • Wenn ich nun den code einfüge, oder nur bei einem "Hallo Welt"-Projekt
    #Include "gdiplus.h" schreibe, spuckt er mir Fehler aus.

    Mit spitzen Klammern inkludieren! Das ist wichtig, denn in Anführungsstrichen heißt, das gdiplus.h angeblich in deinem eigenen Projektverzeichnis liegt (was aber eigentlich nicht der Fall ist). Die gdiplus.h liegt aber in deinem Compiler-Path, deshalb spitze Klammern verwenden.

    Am Anfang hieß es immer er könnte die Datei nicht finden. Dabei stand im Text doch, dass sie ab Windows XP standartmäßig dabei wäre, und ich benutze XP.

    Damit war die gdiplus.dll (also die Enduser-Variante) gemeint. Was soll denn ein WinXP-Anwender mit einer Header-Datei anfangen?

    Ich hab mir dann die ganzen Header-Dateien, eine DLL und die lib-Datei runtergeladen und in den Projektordner eingefügt.

    Naja, das macht man aber eigentlich nicht, auch wenn es irgendwie doch funktioniert. Du hättest einfach den gdiplus.h mit den spitzen Klammern inkludieren sollen. Wenn das nicht gegangen wäre, hättest du wahrscheinlich kein aktuelles PlatformSDK oder kein aktuelles VC++ (mind. 2003er).

    Ganz viele Zeilen in der Datei "gdiplusinit.h" (39, 40, und 97) verursachen Fehler.

    Ja, ich wette es hat was mit den fehlenden spitzen klammern zu tun. An der Basis falsch angefangen zieht das unendlich viele Fehler nach sich. Welches VC++ hast du denn?



  • Ich benutze Visual C++ 6.0, lag bei "Jetzt lerne ich DirectX9 und Visual C++" als CD dabei.

    Ich werd das mit den Spitzen klammern mal ausprobieren, danke für den Tipp!



  • Oh oh, VC++6 ist schon asbach uralt!!! Von 1998, da ist sicherlich kein gdiplus dabei und somit nützen auch die spitzen Klammern nichts. Welcher idiotische Buchverlag legt denn VC++6 einem DX9-Buch bei?

    Wenn das mit den spitzen Klammern nicht klappt (was ich vermute) bitte hier weiter lesen:

    du brauchst bei VC++6 erstmal ein aktuelles Platform SDK.
    http://www.microsoft.com/downloads/details.aspx?FamilyID=e15438ac-60be-41bd-aa14-7f1e0f19ca0d&DisplayLang=en

    Oder am besten gleich ein aktuelles und kostenloses VisualC++:
    http://www.c-plusplus.net/forum/viewtopic-var-t-is-143003.html
    Das Platform SDK brauchst du da trotzdem, wenn du es noch nicht hast!!!



  • Hab vor 10 Minuten auch gemerkt, dass meine Version von 1998 ist.. naja, man sollte eben doch nicht so viel Wert auf die Software legen, die man in der Schule bekommt 😃

    Vielen Dank für die Links und für deine Hilfe, ich hoffe ich krieg das noch hin.
    Ich wusste garnicht, dass es eine kostenlose Version von Visual C++ gibt o_o werd ich gleich mal ausprobieren, danke!



  • Sorry fürn Doppelpost, ich glaub ich sollte mich langsam hier registrieren..

    ich hab jetzt das SDK installiert, aber kann die aktuelle VC++ Version nicht installieren, da ich kein Service Pack 2 habe (und auch keins haben will!).

    Hab es grad nochmal probiert, ich bekomme nur noch einen Fehler, wenn ich den Quelltext aus dem Tutorial probiere: unerwartetes Dateiende.

    Keine Ahnung obs daran liegt, dass mein VC++ so alt ist.. kann man da noch irgendwas machen, oder sollte ich nach einer anderen Möglichkeit suchen, um mit Grafiken zu arbeiten?
    Ich such eigentlich nur eine einfache Lösung wie ich transparente PNGs anzeigen kann..



  • Wo passiert denn das unerwartete Dateiende? Bitte immer die Fehlermeldung posten.

    Hast du auch <windows.h> vorher inkludiert?

    Sind in den Compiler-Pfadeinstellungen auch die Pfade für das neue SDK eingetragen? (kenne leider die Install-Routine vom SDK nicht)

    Wenn GDI+ erstmal läuft (bei dir scheint es ein Sonderfall zu sein) ist es ganz einfach mit der GDI+. Andere Libs müsstest du ja auch erstmal einrichten. Und das aktuelle PSDK kann man immer gut gebrauchen, nicht nur für die GDI+ alleine.


Anmelden zum Antworten