Seitenleiste

Vektorgrafiken mit GTK+ und Cairo

In vielen Anwendungen wird nicht nur die Benutzeroberfläche in das Fenster gezeichnet, oft werden Grafiken erstellt, die sich nicht über ein GUI-Toolkit wie GTK+ darstellen lassen.

Hier kommen andere Libraries ins Spiel, eine davon ist Cairo. Diese Bibliothek für Vektorgrafiken werden wir uns nun genauer anschauen.

Was ist Cairo?

Cairo ist eine freie Bibliothek zur Erstellung von vektorbasierten 2D-Zeichnungen. Sie unterstützt die Ausgabe auf dem X Window System (Linux/Unix), unter Win32 (Windows), unter Quartz (Mac OS X) und in die Dateiformate PNG, PostScipt, PDF und SVG. Andere Back-Ends befinden sich in Entwicklung.

Da Cairo frei verfügbar ist, findet es besonders im Open Source Bereich große Verwendung. Unter anderem basiert die Gecko-Browserengine, benutzt von Mozilla Firefox, Cairo zur Darstellung von SVG-Dateien. Auch die Programme Inkscape, OpenOffice.org, Evince und Okular verwenden Cairo als Grafikbibliothek.

Nicht zu vergessen ist die Verwendung in GTK+ ab der Version 2.8.0. Ab dieser Version zeichnet GTK+ die meisten seiner Widgets mit Cairo, wodurch Cairo bei der Installation von GTK+ gleich mitgeliefert wird.

Der Cairokontext

Wie schon angesprochen benutzt GTK+ intern Cairo zum Zeichnen, allerdings stellt es auch Funktionen bereit, mit denen wir mit Cairo auf GTK-Widgets zeichnen können. Das Zauberwort heißt hier Cairokontext.

Wir erstellen eine „leere“ GTK Anwendung, binden aber einen neuen Header mit ein:

#include <cairo.h> /* Der neue Header, der die Cairo-Funktionen enthält*/
#include <gtk/gtk.h>
 
int main (int argc, char *argv[])
{
 
    GtkWidget *window;
 
    gtk_init(&argc, &argv);
 
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 
    g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
 
    gtk_widget_show_all(window);
 
    gtk_main();
 
    return 0;
}

Als nächstes erstellen wir eine Callback Funktion für das Expose-Event, also wenn das Fenster gezeigt wird:

static gboolean on_expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
    cairo_t *cr;
 
    cr = gdk_cairo_create(widget->window);
 
    cairo_destroy(cr);
 
    return FALSE;
}

Hierbei ist cairo_t unser Cairokontext, den wir über die Funktion gdk_cairo_create auf unser Fenster legen (genauer gesagt: auf das GdkDrawable). Wichtig: mit cairo_destroy den Cairokontext wieder auflösen!

Nun müssen wir noch einen Signalhandler für unser Event schreiben:

g_signal_connect(window, "expose-event", G_CALLBACK (on_expose), NULL);

und GTK+ sagen, dass wir auf das Fenster zeichnen wollen:

gtk_widget_set_app_paintable(window, TRUE);

Zum Kompilieren braucht ihr nichts an den Flags ändern.

Linien

Horiontale Linie, gezeichnet mit Cairo Wie ihr seht, hätte man sich bis jetzt die ganze Cairo Geschichte auch sparen können, denn man sieht nichts. Nun wäre aber Cairo keine Grafikbibliothek, wenn man damit nicht zeichnen könnte: Also ran an die Stifte! =)

Gezeichnet wird zwischen dem Aufruf von gdk_cairo_create und cairo_destroy. Um nun eine einfache Linie zu zeichnen, bewegen wir den „Stift“ von Cairo erst zu einer beliebigen Stelle:

cairo_move_to(cr, 0, 100);

und ziehen mit ihm dann eine Gerade zu einem anderen Punkt:

cairo_line_to(cr, 200, 200);

Achtung! Wer sich jetzt wundert, dass er nichts sieht, hat vielleicht eine kleine Zeile vergessen:

cairo_stroke(cr);

cairo_stroke übermittelt alle Zeichenoperationen an das Backend, in unserem Fall an den Cairokontext. Sprich: Um etwas zu sehen, erst cairo_stroke aufrufen!

Noch etwas zu den Koordinaten: Diese gehen von der linken oberen Ecke aus. Wie gewohnt bezeichnet x die horizontale Achse und y die vertikale.

Bögen, Kreise

Kreis und Halbkreis Bekanntlich ist die Banane nicht gerade, sondern krumm: Sie bildet einen Bogen. Auch solche lassen sich mit Cairo zeichnen. Hierzu gibt es cairo_arc. Um nun die nebenstehende Figur mit Cairo zu zeichnen packen wir folgendes zwischen gdk_cairo_create und cairo_destroy:

cairo_arc(cr, 100.0, 100.0, 50.0, 0 * (M_PI / 180.0), 360.0 * (M_PI / 180.0));
 
cairo_stroke(cr);
 
cairo_arc(cr, 100.0, 100.0, 60.0, 0 * (M_PI / 180.0), 180.0 * (M_PI / 180.0));
 
cairo_stroke(cr);

M_PI ist nichts anderes als die Kreiszahl π und ist definiert in <math.h>, den Header also einbinden! Benötigt wird π zur Umrechnung von Grad in Bogenmaß 1). Um nun etwas Licht ins Dunkel der Argumente zu bringen, schaut euch diese Tabelle an:

void cairo_arc(cairo_t *cr, double xc, double yc, double radius, double angle1, double angle2)
cr Der Cairokontext.
xc Die x-Koordinate des Mittelpunktes.
yc Die y-Koordinate des Mittelpunktes.
radius Selbsterklärend
angle1 Winkel am Beginn des Bogens. Angegeben in Bogenmaß.
angle2 Winkel am Ende des Bogens. Angegeben in Bogenmaß.

Etwas „ungewöhnliches“ müsste euch an diesem Beispiel dennoch aufgefallen sein: Der Aufruf von cairo_stroke zwischen den beiden cairo_arc Befehlen. Um der Sache einfach etwas nähre zu kommen, verzichtet einfach mal auf das erste cairo_stroke!

...

Verwundert?

Diese „Erscheinung“ zu verstehen ist ziemlich einfach: Wie bei den Linien bereits erwähnt arbeitet Cairo mit einem Cursor (einem Stift). Mit Funktionen wie cairo_line_to, cairo_move_to oder auch cairo_arc beschreibt man Positionsänderungen des Cursors. Diese werden von Cairo gespeichert, und mit cairo_stroke ausgegeben. Dies ist der CairoPath2). Wenn nicht anders beschrieben (zum Beispiel mit cairo_move_to), werden alle Punkte des Pfades mit einer Linie verbunden. So kommt es, dass zwischen dem angeblichen Endpunkt des Kreises und dem Anfangspunkt des Halbbogens ein Linie gezeichnet wird, der nächste Punkt im Pfad ist ja schließlich dort.

Somit besitzt cairo_stroke neben der Ausgabe des Pfades auch noch eine andere Bedeutung: Es setzt den CairoPath zurück!

Text

Auch Text lässt sich mit Cairo einfach zeichnen. Hierfür muss zunächst die Art der Darstellung festgelegt werden, bevor der Text gezeichnet wird. Setzt einfach folgenden Code wieder zwischen gdk_cairo_create und cairo_destroy:

cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
 
cairo_set_font_size(cr, 20.0);

Mit cairo_select_font_face setzen wir die Schriftart fest, mit cairo_set_font_size die Schriftgröße.

Ein Hinweis: Wenn Cairo die Schriftart, die cairo_select_font_face übergeben wurde, nicht findet, wird automatisch die Standardschriftart des Systems gewählt!

Um kursiv oder fett zu schreiben, müssen einfach die beiden letzten Argumente für cairo_select_font_face geändert werden:

CAIRO_FONT_SLANT_*Die „Schräge“ des Textes
CAIRO_FONT_SLANT_NORMALNormaler Text.
CAIRO_FONT_SLANT_ITALICKursiver Text.
CAIRO_FONT_SLANT_OBLIQUESchräger Text. 3)
CAIRO_FONT_WEIGHT_*Die „Gewichtung“ des Textes
CAIRO_FONT_WEIGHT_NORMALNormaler Text.
CAIRO_FONT_WEIGHT_BOLDFetter Text.

Wer den Code jetzt schon kompiliert, ausführt und sich wundert, dass noch nichts zu sehen ist, der war mal wieder zu voreilig, denn gezeichnet wird erst mit:

cairo_move_to(cr, 10.0, 20.0);
cairo_show_text(cr, "Proggen.org.");

Auf cairo_stroke können wir hier verzichten, da wir nichts am CairoPath geändert haben.

Achtung: Der mit cairo_move_to angegebene Punkt auf dem Fenster bezeichnet die linke untere Ecke des später gezeichneten Textes!

Farben

Natürlich können wir auch bunte Stifte aus dem Cairo-Malkasten nehmen:

cairo_set_source_rgb (cr, 1, 0, 0);
cairo_move_to (cr, 0, 0);
cairo_line_to (cr, 10, 10);
cairo_stroke (cr);
 
cairo_set_source_rgb (cr, 0, 1, 0);
cairo_move_to (cr, 10, 10);
cairo_line_to (cr, 20, 20);
cairo_stroke (cr);



void cairo_set_source_rgb (cairo_t *cr, double red, double green, double blue);
cr Der Cairokontext.
red Der Rot-Anteil der Farbe (zwischen 0.0 und 1.0).
green Der Grün-Anteil der Farbe (zwischen 0.0 und 1.0).
blue Der Blau-Anteil der Farbe (zwischen 0.0 und 1.0).

Wichtig: Möchte man mehrere Farben verwenden (wie in dem Beispiel), so muss vor dem Wechsel der Farbe erst cairo_stroke aufgerufen werden!

Neben cairo_set_source_rgb kann auch die Funktion cairo_set_source_rgba verwendet werden. Diese bietet auch noch die Möglichkeit den Alpha-Kanal zu benutzen, womit sich wiederum Transparenz erzeugen lässt.

und Flächen

FIXME

1)
Natürlich könnte man sich die Umrechnung sparen und alles gleich in Bogenmaß schreiben, ist so meiner Meinung nach aber anschaulicher.
2)
Cairo-Pfad
3)
Nicht in allen Schriftarten vorhanden.