Aus Linux-Magazin 05/2006

2D-Grafikbibliothek Cairo

Identische Darstellung auf dem Linux- und dem Windows-Bildschirm oder in PDF-Dokumenten sowie performante Ausgabe von 2D-Grafiken – mit seinen Backends erledigt Cairo die Hauptarbeit.

Cairo ist schlank, schnell und beliebt. Derzeit ist die Bibliothek [1] als Unterbau von GTK 2.8 zu finden, und zwar hinter den Grafikroutinen der Dotnet-Umsetzung Mono und als Basis für die künftigen Versionen der Firefox-Engine Gecko. Ihr Konzept ist nicht neu, sondern von Mac OS X abgekupfert: In dem Betriebssystem von Apple basiert die Grafikbibliothek Quartz auf dem PDF-Standard und alle mit ihr gezeichneten Elemente fließen optional direkt in eine PDF-Datei. Cairo geht aber noch einen Schritt weiter.

Immer raus damit

Der primäre Vorteil der Bibliothek liegt in ihrer Unabhängigkeit vom Ausgabegerät. So kümmert sich Cairo selbstständig darum, ob die gezeichneten Elemente in einem X-Window oder in einer PDF-Datei landen. Zurzeit gelten die Ausgabeschnittstellen für X11, auf dem Windows-2000- und -XP-Bildschirm sowie für Grafikdateien im PNG-Format als stabil. Die Umleitungen in OpenGL über die Beschleunigungshilfe Glitz, Mac OS X über die Quartz-Bibliothek, XCB, Postscript und PDF befinden sich noch im Experimentierstadium.

Egal für welche Ausgabemöglichkeit sich der Programmierer entscheidet, Cairo garantiert eine konsistente Darstellung: Eine Zeichnung sieht in einer PDF-Datei genauso aus wie in einem X11-Fenster. Die Grafikbibliothek nutzt zudem alle vorhandenen Beschleunigungsfunktionen wie zum Beispiel die X Render Extension unter X11 oder Glitz [2] für die OpenGL-Darstellung.

Keine Bindungsängste

Das Cairo-API ähnelt den Befehlen von Postscript und PDF, aber auch OpenGL-Programmierer werden mit den Konzepten schnell warm. Die angebotenen Operationen reichen vom Zeichnen geometrischer Figuren wie Linien und Splines über die Einbindung von (transparenten) Bildern bis zur geglätteten Textdarstellung mittels Antialiasing. Zusätzlich lassen sich alle Objekte beliebig verformen, beispielsweise skalieren, rotieren oder strecken.

Cairo selbst ist in C geschrieben; Anbindungen ermöglichen seinen Einsatz außerdem unter Python, Ruby, Java, Perl und C++ sowie mit Hilfe der Bibliothek »Mono.Cairo« auch über das Mono-Framework unter C# und den anderen CLS-Sprachen. Weitere Bindings liegen als Testversionen vor, einen vollständigen Überblick gibt [3].

Von Kopf bis Fuß

Die Programmierung einer Cairo-Anwendung umfasst wenige Schritte. Zuerst bindet man den Header ein:

#include <cairo.h>
int main(int argc, char *argv[]){

Nun benötigt das Programm eine Zeichenfläche (Surface), auf die es später das Bild zeichnet. Je nach gewünschtem Backend dient dazu eine andere Funktion. In jedem Fall kommt ein »cairo_surface_t«-Objekt zurück:

cairo_surface_t *imagesurface;
imagesurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 200, 100);

Dieses Beispiel verwendet ein Bitmap im Speicher mit einer Farbtiefe von 32 Bit und einer Auflösung von 200 mal 100 Pixeln als Ziel. Später lässt sich das Bitmap in eine PNG-Datei umleiten.

Als Nächstes ist ein so genannter Drawing Context anzulegen, den alle Zeichenfunktionen benötigen. Wer bereits mit anderen Grafikbibliotheken gearbeitet hat, kennt dieses Prozedere. Im Fall von Cairo heißt die dafür zu verwendende Funktion »cairo_create()«:

cairo_t *cr;
cr = cairo_create (imagesurface);

Intern kapselt der Drawing Context den jeweils aktuellen Zustand des Ausgabegeräts, darunter beispielsweise die Zeichenfarbe und die Linienstärke. Dieser Zustand verändert sich im Folgenden durch die zur Verfügung stehenden Zeichenoperationen.

Als Erstes ist die Farbe des Zeichenstifts einzustellen. Hierbei ist zu beachten, dass Cairo immer mit Fließkommazahlen arbeitet. Dies gilt insbesondere auch für die im nächsten Schritt zu beachtenden Koordinaten. Die Farbwerte in der folgenden Zeile geben demnach die Intensitäten der Komponenten Rot, Grün und Blau aus einem Bereich zwischen 0 und 1 an:

cairo_set_source_rgb(cr, 1, 0.2, 0.2);

Jetzt ist der zu zeichnende Pfad an der Reihe: Er besteht unter Cairo aus einer zusammenhängenden Folge von Geraden und Kurven, in diesem Beispiel jedoch lediglich aus einer Linie. Dazu positioniert man den Stift zunächst an den Koordinaten (50, 10) und zeichnet von dieser Stelle aus eine Linie bis zur Position (90, 90):

cairo_move_to(cr, 50.0, 10.0);
cairo_line_to(cr, 90, 90);
cairo_stroke(cr);

Die Funktion »cairo_stroke()« gibt den erzeugten Pfad schließlich im Drawing Context aus. Diese Abspaltung des eigentlichen Malvorgangs bewirkt höhere Flexibilität. So lässt sich der gesamte Pfad nachträglich manipulieren, etwa strecken oder stauchen. Um die gezeichnete Linie optisch zu ergänzen, kommt anschließend noch ein kleiner blauer Haken ins Spiel.

Eine Applikation, viele
Ausgaben

Die Vielfalt der Ausgabemöglichkeiten erreicht Cairo durch seine Schichtenarchitektur. Ein Kern kapselt die Zeichenalgorithmen, während die Ausgabe über Backends erfolgt (siehe Abbildung 1). Für jede Ausgabemethode existiert ein eigenes Backend. Die Zeichenoperationen für geometrischen Elemente realisiert Cairo über bekannte mathematische Verfahren, allen voran die Matrizenmultiplikation.

Abbildung 1: Cairo gibt gezeichnete Elemente über Backends sowohl auf X11-, OpenGL- und Windows-Fenstern oder in Dateien aus.

Abbildung 1: Cairo gibt gezeichnete Elemente über Backends sowohl auf X11-, OpenGL- und Windows-Fenstern oder in Dateien aus.

Beim Kompilieren von Cairo ermittelt das Configure-Skript die im System vorhandenen Pakete und damit die verwendbaren Backends. Die OpenGL-Ausgabe benötigt beispielsweise Glitz ab Version 0.4.4.

In puncto Lizenz stehen die GNU LGPL in der Version 2.1 und Mozilla Public License 1.1 zur Auswahl. Somit ist Cairo auch der Weg in kommerzielle Programme geebnet.

Stapelspeicher

Um die bereits gewählte Farbe erneut zu verwenden, ließe sich ein zweiter Drawing Context anlegen und später mit dem ersten weitermalen. Cairo bietet aber eine pfiffigere Alternative: Dabei legt der Programmierer den aktuellen Zustand zur späteren Weiterbearbeitung auf einem internen Stapel (Stack) ab. Dann zeichnet er gefahrlos den neuen Pfad, der dieses Mal aus einer blauen Linie und einem Bogen (Curve) besteht. Danach holt er den vorherigen Zustand aus dem Stapel zurück und stellt damit die alten Einstellungen wieder her:

cairo_save(cr);
cairo_move_to(cr, 90, 90);
cairo_set_source_rgb(cr, 0,0,1);
cairo_rel_line_to(cr, -40, 0);
cairo_curve_to(cr, 20, 90, 20, 50, 50, 50);
cairo_stroke(cr);
cairo_restore(cr);

Bei einem einfachen Farbwechsel ließe sich in diesem Beispiel zwar eine Codezeile sparen. Bei umfangreicheren Einstellungen leistet der Stack jedoch wertvolle Hilfe; insbesondere beim Auslagern von Zeichenoperationen in eine eigene Funktionen, zum Beispiel »draw_parallelogramm(cairo_t *cr)«. Da unklar ist, was vor und nach dem Aufruf der Funktion passiert, sollte der Programmierer darin stets den aktuellen Zustand im Stack sichern und kurz vor dem Verlassen wiederherstellen.

Haken und Ösen

Nun ergänzt das Beispiel den entstandenen Haken mit einer Linie zu einer abgeschlossenen Figur:

cairo_move_to(cr, 50, 50);
cairo_line_to(cr, 50, 10);
cairo_stroke(cr);

Die gezeichnete Linie erscheint wieder in Rot. Den Abschluss dieses kleinen Kunstwerks bildet die Beschriftung “Hallo Welt”:

cairo_set_font_size(cr, 16);
cairo_move_to(cr, 70, 10);
cairo_show_text(cr, "Hallo Welt!");
cairo_stroke(cr);

Damit ist das Bild fertig und der Drawing Context überflüssig. Die Zeichnung im Speicher wandert mit den folgenden Zeilen in das PNG-Bild mit dem Dateinamen »linie.png« und muss anschließend wie der Drawing Context seinen Platz im Speicher räumen:

cairo_destroy(cairosurface);
cairo_surface_write_to_png (imagesurface,"linie.png");
cairo_surface_destroy (imagesurface);

Der folgende GCC-Aufruf kompiliert das Programm; der in Backticks eingeschlossene »pkg-config«-Befehl fügt die passenden Include- und Linker-Parameter automatisch ein. Abbildung 2 zeigt danach das Ergebnis:

gcc -o linie `pkg-config --cflags --libs cairo` linie.c
Abbildung 2: Cairo unterstützt den Alphakanal und damit transparente Hintergründe. Standardmäßig erscheinen daher alle nicht übermalten Teile des Bitmap durchsichtig.

Abbildung 2: Cairo unterstützt den Alphakanal und damit transparente Hintergründe. Standardmäßig erscheinen daher alle nicht übermalten Teile des Bitmap durchsichtig.

Das Beispielprogramm aus Listing 1 öffnet ein X11-Fenster und zeigt darin einen hüpfenden Ball (Abbildung 3). Damit die Animation flüssig und ohne Flackern abläuft, bereitet das Programm die Zeichnung in einem eigenen Surface vor und kopiert sie ins eigentliche Bildschirm-Surface. Cairo bietet von Haus aus leider keine Hilfe beim Erzeugen von Animationen, was im Beispiel die sehr niedrige Geschwindigkeit des hüpfenden Balls demonstriert. Wer derartige Funktionen benötigt, sollte lieber zu anderen Bibliotheken greifen. Eine populäre Alternative wäre SDL [4].

Listing 1: Hüpfender Ball
mit Cairo

01 #include <cairo.h>
02 #include <cairo-xlib.h>
03 #include <math.h> /* fuer die Berechnung der Ballhoehe */
04 #ifndef PI
05 #define PI 3.14159265358979323846
06 #endif
07 
08 int main(int argc, char *argv[])
09 {
10   /* Variablen fuer X11 */
11   Display *display;
12   int screen;
13   Window win;
14   GC gc;
15   int width, height;
16   float x,y,t;
17   width = 400;
18   height = 200;
19   /* Variablen fuer Cairo */
20   cairo_surface_t *surface;
21   cairo_surface_t *imagesurface; /* Puffer */
22   cairo_t *cr;
23   cairo_t *crimage; /* Drawing Context fuer Puffer */
24   /* X11-Fenster oeffnen */
25   display = XOpenDisplay(0);
26   screen = DefaultScreen(display);
27   win = XCreateSimpleWindow(display, DefaultRootWindow(display), 0, 0, width, height, 0, WhitePixel(display, screen), WhitePixel(display, screen));
28   XMapWindow(display, win);
29   /* Cairo Surface in X11 oeffnen: */
30   XClearWindow(display, win);
31   surface = cairo_xlib_surface_create(display, win, DefaultVisual(display, DefaultScreen(display)), width, height);
32   /* Den Hintergrund-Puffer einrichten: */
33   imagesurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
34   /* Drawing Context holen */
35   cr = cairo_create(surface);
36   crimage = cairo_create(imagesurface);
37   /* Zeichnen */
38   for (t=0; t<6*PI; t=t+(0.01))
39     {
40     x = 50;
41     /* aktuelle Hoehe berechnen */
42     y = 100-(100*exp((-t)*0.3)*fabs(cos(t)));
43     /* Schwarze Farbe waehlen */
44     cairo_set_source_rgb(crimage, 0, 0, 0);
45     /* Grundlinie malen */
46     cairo_move_to(crimage, 0,120);
47     cairo_line_to(crimage, 400,120);
48     cairo_stroke(crimage);
49     /* kopiere Ball an seine Position */
50     cairo_arc(crimage, x, y, 20, 0, 2*M_PI );
51     cairo_fill(crimage);
52     /* Und das vorbereitete Image in das Fenster kopieren */
53     cairo_set_source_surface(cr, imagesurface, 0, 0);
54     cairo_paint(cr);
55     /* Hintergrund-Puffer loeschen */
56     cairo_set_source_rgb(crimage, 1, 1, 1);
57     cairo_rectangle(crimage, 0, 0, width, height);
58     cairo_fill(crimage);
59     cairo_set_source_rgb(crimage, 0, 0, 0);
60   } /*for*/
61   /* Cairo aufraeumen */
62   cairo_destroy(crimage);
63   cairo_destroy(cr);
64   cairo_surface_destroy(imagesurface);
65   cairo_surface_destroy(surface);
66   /* X11 aufraeumen */
67   XDestroyWindow(display, win);
68   XCloseDisplay(display);
69   return 0;
70 }
Abbildung 3a: Animationen unterstützt Cairo von Haus aus leider nicht, der Programmierer muss sie manuell umsetzen.

Abbildung 3a: Animationen unterstützt Cairo von Haus aus leider nicht, der Programmierer muss sie manuell umsetzen.

Abbildung 3b: Der Ball bewegt sich im Beispiel aus Listing 1 sehr langsam auf und ab. Wer Animationen, entwickelt, sollte etwa auf SDL zurückgreifen.

Abbildung 3b: Der Ball bewegt sich im Beispiel aus Listing 1 sehr langsam auf und ab. Wer Animationen, entwickelt, sollte etwa auf SDL zurückgreifen.

Die im Paketumfang enthaltene API-Referenz dokumentiert alle Cairo-Zeichenfunktionen. Doch geizt sie mit verwertbaren Beispielen und ist auch sonst recht knapp gehalten. Wer weitere Anregungen und Informationen sucht, hat als Anlaufstellen die Code-Snippets unter [5] und ein Archiv mit teilweise umfangreichen Beispielen, das allerdings nur über das CVS-Repository erreichbar ist:

cvs -d :pserver:anoncvs@cvs.cairographics.org:/cvs/cairo co cairo-demo

Unter GTK hat der Programmierer die Wahl zwischen den GTK-eigenen Funktionen oder der direkten Kommunikation mit Cairo (Listing 2 und Abbildung 4). Da GTK ab Version 2.8 Cairo bereits integriert [6], entfällt in Listing 2 die Einbindung der Cairo-Bibliotheken. Der GCC-Aufruf zum Kompilieren heißt für dieses Beispiel:

gcc test.c -o test `pkg-config --cflags --libs gtk+-2.0`

Listing 2: Cairo unter
GTK

01 #include <gtk/gtk.h>
02 gboolean draw(GtkWidget *widget, GdkEventExpose *event, gpointer data)
03 {
04   cairo_t *cr;
05   cr = gdk_cairo_create (widget->window);
06   cairo_move_to(cr, 50, 50);
07   cairo_rel_line_to(cr,  100,  100);
08   cairo_rel_line_to(cr, -100,   0);
09   cairo_rel_line_to(cr,  100, -100);
10   cairo_close_path(cr);
11   cairo_stroke (cr);
12   cairo_destroy (cr);
13   return TRUE;
14 }
15 int main(int argc, char *argv[])
16 {
17   GtkWidget *window;
18   GtkWidget *drawing_area;
19   cairo_t *cr;
20   gtk_init (&argc, &argv);
21   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
22   /* eine drawing area erstellen*/
23   drawing_area = gtk_drawing_area_new ();
24   gtk_widget_set_size_request (GTK_WIDGET (drawing_area), 200, 200);
25   gtk_container_add (GTK_CONTAINER(window), drawing_area);
26   gtk_widget_show (drawing_area);
27   g_signal_connect (G_OBJECT(drawing_area), "expose_event", G_CALLBACK(draw), NULL);
28   gtk_widget_show(window);
29   gtk_main ();
30   return 0;
31 }
Abbildung 4: Ab Version 2.8 integriert GTK die Cairo-Bibliothek direkt davon profitieren beispielsweise auch Gnome-Entwickler.

Abbildung 4: Ab Version 2.8 integriert GTK die Cairo-Bibliothek direkt davon profitieren beispielsweise auch Gnome-Entwickler.

Cairo für die Linux-Welt

Durch die Integration in GTK kommen alle auf Cairo basierenden Programme in den Genuss der Vektorgrafik-Bibliothek – einschließlich des Gnome-Desktops. Sie profitieren von einer verbesserten Bildschirmdarstellung und erhalten darüber hinaus eine qualitativ hochwertige PDF-Exportfunktion.

Nachteile bestehen in dem gegenwärtig noch etwas geringen Funktionsumfang und der gänzlich fehlenden Unterstützung für Animationen. Die Roadmap der Gecko-Engine sieht übrigens vor, dass Version 1.8 zunächst nur die SVG-Ausgaben mit Cairo erledigt, ab 1.9 sollen dann sämtliche Zeichenoperationen darüber ablaufen. (csc)

Infos

[1] Cairo: [http://cairographics.org]

[2] Glitz: [http://www.freedesktop.org/wiki/Software/glitz]

[3] Sprachbindungen von Cairo: [http://cairographics.org/bindings]

[4] Simple Direct Media Layer: [http://www.libsdl.org]

[5] Codebeispiele für Cairo: [http://cairographics.org/samples]

[6] “Writing a widget using Cairo and GTK+ 2.8”: [http://www.gnomejournal.org/article/34/writing-a-widget-using-cairo-and-gtk28]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben