Interfaces Graphiques #8
Introduction à la bibliothèque Cairo

Cairo est la bibliothèque utilisée par GTK pour le dessin vectoriel. Avec Cairo, on peut dessiner des courbes de Bezier, des droites anti-aliasées, créer des dégradés de couleurs, faire de l'alpha-blending, déformer des images.

Pour l'utiliser, il est nécessaire d'inclure la directive d'inclusion d'entête suivante, en revanche, Cairo et déjà linké avec GTK, donc il n'y a rien de spécial à rajouter à la commande de compilation ou d'édition de lien:

#include <cairo.h>
Les documentation essentielles sont :

Le contexte

Pour dessiner avec Cairo, il faut un objet de type cairo_t. Cet objet combine l'équivalent d'un GdkGC et d'un GdkDrawable sous GDK. C'est-à-dire que c'est à la fois le "crayon" et le "papier" sur lequel celui-ci dessine. Dans la terminologie Cairo, on appelle aussi cela un contexte.

Le "papier", ou dans la terminologie Cairo, la surface destination, peut être un GdkDrawable, un buffer image en mémoire, ou encore un fichier au formats PNG, PostScript, ou PDF. Nous ne nous intéressons ici qu'aux surfaces issues d'un GdkDrawable. Pour créer un contexte de type cairo_t associé à la surface d'un GdkDrawable, GDK propose une fonction d'interaction avec Cairo:

cairo_t * gdk_cairo_create (GdkDrawable * drawable);
Contrairement à un GdkGC, on ne peut le créer une bonne fois pour toutes, et le partager pour plusieurs surfaces, ou même l'utiliser sur un même GdkWindow d'un widget pour plusieurs expositions si l'on travaille avec GTK, à cause du mécanisme de double-buffer de GTK qui peut invalider ces buffers entre chaque expositions. Il faut donc créer le contexte au début de la callback d'exposition et le détruire en fin de callback avec void cairo_destroy(cairo_t *) :
#include <gtk/gtk.h>
#include <cairo.h>

typedef struct Info {
  GtkWidget * win;
  cairo_t * cairo;
} Info;

void info_init (Info * info, GtkWidget * win)  {
  info->win  = win;
  info->cairo= NULL;
}

gboolean on_expose (GtkWidget * widget, GdkEvent * e, gpointer data) {
  Info * info= data;
  info->cairo= gdk_cairo_create (widget->window);
  ...
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}
int main (int argc, char * argv [])
{
  Info info;
  GtkWidget * win;
  gtk_init (& argc, & argv);
  win= gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_size_request (win, 200, 200);
  info_init (& info, win);
  g_signal_connect (win, "expose_event", G_CALLBACK (on_expose), & info);
  gtk_widget_show_all (win);  
  gtk_main ();
  return 0;
}

Les trois éléments du contexte

Le modèle de dessin de Cairo est celui des graphers qui travaillent sur un mur avec de la peinture en spray et des pochoirs. Un contexte cairo_t travaille avec trois éléménts qui modélisent le spray, le pochoir et le mur. Ces éléments sont:

Pour dessiner, la principale méthode consiste à:

Préparation de la source

La source est initialisée avec un motif de type cairo_pattern_t via la fonction cairo_set_source().

avec une couleur uniforme

Le motif le plus simple est le motif monochrome, que l'on crée avec les fonctions cairo_pattern_create_rgb() ou cairo_pattern_create_rgba() si l'on veut un canal alpha (degré de transparence). Si le motif ne sert qu'à initialiser la source, on peut combiner les deux étape (création du motif et initialisation de la source) avec les fonctions cairo_set_source_rgb() et cairo_set_source_rgba(). Les intensités des canaux RGBA sont des flottants normalisés (variant entre 0.0 et 1.0). Pour le canal alpha 1.0 correspond à l'opacité totale, et 0.0 à la transparence totale.

double red= 1.0, green= 1.0, blue= 0.0, alpha= 1.0.
/* methode 1... */
cairo_pattern_t * yellow= cairo_pattern_create_rgba (red, green, blue, alpha);
cairo_set_source (info->cairo, yellow);
cairo_pattern_destroy (yellow);
/* methode 2... */
cairo_set_source_rgba (info->cairo, red, green, blue, alpha);

On peut parfois souhaiter utiliser une couleur GdkColor préexistante. Or les canaux dans cette structure ne sont pas des flottants normalisés mais des entiers sur 16 bits, ce qui nécessite une conversion. GDK propose une fonction d'interaction avec Cairo pour simplifier la tâche: gdk_cairo_set_source_color():

/* methode 3... */
GdkColor yellow;
yellow.red= 0xFF00; yellow.green= 0xFF00; yellow.blue= 0x0000;
gdk_cairo_set_source_color (info->cairo, & yellow);

alpha= 1.00

alpha= 0.66

alpha= 0.33

alpha= 0.00
Le code suivant montre comment peindre une fenêtre en jaune comme ci-dessus. Les 4 captures d'écran varient selon le paramètre d'alpha-blending entre la source et la destination. La fenêtre ayant un fond gris par défaut sous GTK., le mélange se fait donc entre le jaune et le gris:
gboolean on_expose_color (GtkWidget * widget, GdkEvent * e, gpointer data) {
  Info * info= data;
  cairo_pattern_t * yellow;
  double red, green, blue, alpha;

  info->cairo= gdk_cairo_create (widget->window);
  red= 1.0; green= 1.0; blue= 0.0; alpha= 1.00;
  yellow= cairo_pattern_create_rgba (red, green, blue, alpha);
  cairo_set_source (info->cairo, yellow);
  cairo_paint (info->cairo);

  cairo_pattern_destroy (yellow);
  cairo_destroy (info->cairo);  info->cairo= NULL;
  return TRUE;
}

avec une image

Si on veut pour motif une image, le plus simple est d'utiliser les fonctions d'interaction de GDK avec Cairo: gdk_cairo_set_source_pixbuf() et gdk_cairo_set_source_pixmap(), selon que l'image se trouve dans un GdkPixbuf ou un GdkPixmap.

GdkPixbuf * pixbuf= gdk_pixbuf_new_from_file (filename, NULL);
...
gdk_cairo_set_source_pixbuf(info->cairo, pixbuf);

Si l'image se trouve déjà dans une surface, par exemple après avoir été chargée via cairo_image_surface_create_from_png (), alors on peut utiliser la fonction cairo_set_source_surface() pour initialiser la source avec l'image. L'avantage d'avoir une image dans une surface est que l'on peut lui appliquer des transformations (rotation, étirement, ...). Si la taille de l'image est 200x300 pixels, le code suivant positionne son centre en (0,0) dans la source:

cairo_surface_t * image= cairo_image_surface_create_from_png (filename);
gint offset_x= -200/2, offset_y= -300/2;
...
cairo_set_source_surface (info->cairo, image, offset_x, offset_y);

L'image peut également être issue de la surface de destination. Dans l'exemple suivant, le pixel (100,100) de la destination est utilisé comme pixel (0,0) de la source:

surface_t * destination= cairo_get_target (info->cairo);
cairo_set_source_surface (info->cairo, destination, 100, 100);

info->seat
 

x= 0.0;
y= 0.0;

x= -sw/2.0;
y= -sh/2.0;

x= dw/2.0 - sw/2.0;
y= dh/2.0 - sh/2.0;

Le code suivant charge dans une surface l'image du fauteuil ci-dessus, de dimensions (sw, sh) . Il initialise ensuite la source avec cette surface et la peint sur la destination de dimensions (dw,dh). en différents offsets (x,y). On voit qu'avec des offset nuls, l'image est dessinée dans le coin supérieur gauche de la fenêtre. En soustrayant à ces offsets la moitié des dimensions de la source, le centre du siège est dessiné en l'origine de la destination. On verra que cela est utile lorsque l'on veut faire pivoter l'image autour de son centre. Enfin, en rajoutant à ces offsets la moitié des dimensions de la destination, le centre du siège est dessiné au centre de la destination.

typedef struct Info {
  GtkWidget * win;
  cairo_t * cairo;
  cairo_surface_t * seat;
} Info;

void info_init (Info * info, GtkWidget * win) 
{
  info->win  = win;
  info->cairo= NULL;
  info->seat= cairo_image_surface_create_from_png ("seat.png");
}
gboolean on_expose_image_1 (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  int dw, dh; 
  double x, y;
  double sw= cairo_image_surface_get_width  (info->seat);
  double sh= cairo_image_surface_get_height (info->seat);
  gdk_drawable_get_size (widget->window, & dw, & dh);
  x= dw/2.0 - sw/2.0;
  y= dh/2.0 - sh/2.0;

  info->cairo= gdk_cairo_create (widget->window);
  cairo_set_source_surface (info->cairo, info->seat, x, y);
  cairo_paint (info->cairo);

  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

CAIRO_EXTEND_NONE

CAIRO_EXTEND_PAD

CAIRO_EXTEND_REPEAT

CAIRO_EXTEND_REFLECT

Par défaut, rien n'est peint en dehors des limites de l'image (mode CAIRO_EXTEND_NONE). Trois autres modes sont disponibles: padding avec les pixels de bordure (mode CAIRO_EXTEND_PAD), répétition de motif en tapisserie (mode CAIRO_EXTEND_REPEAT), et réflexion répétée de motif (mode CAIRO_EXTEND_REFLECT). Les captures d'écran ci-dessus semblent montrer que ce dernier mode ignore le canal alpha de la source (le fond gris est écrasé par du noir). Le mode d'affichage d'un motif se configure avec cairo_pattern_set_extend() et on peut obtenir le motif de la source avec cairo_get_source().

gboolean on_expose_image_2 (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  cairo_pattern_t * source;
 
  info->cairo= gdk_cairo_create (widget->window);
  cairo_set_source_surface (info->cairo, info->seat, 0.0, 0.0);

  source= cairo_get_source (info->cairo);
  cairo_pattern_set_extend (source, CAIRO_EXTEND_NONE);

  cairo_paint (info->cairo);

  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

avec un dégradé de couleurs linéaire

On peut créer deux types de dégradés: linéaire et radial. Le dégradé linéaire se spécifie avec deux points A et B via la fonction cairo_pattern_create_linear(). À ces deux points ont associées les valeurs 0.0 et 1.0 d'un paramètre t. On fixe ensuite des couleurs pour des valeurs données de t (entre 0.0 et 1.0) via cairo_pattern_add_color_stop_rgb() ou cairo_pattern_add_color_stop_rgba(). Un point C associé au paramètre d'interpolation t se situe en A*(1-t)+B*t et aura la couleur spécifiée. Les couleurs des autres points sur la droite AB sont obtenus par interpolation linéaire. Tous les points se situant sur une même droite perpendiculaire à AB et passant par C ont la même couleur que C.

CAIRO_EXTEND_NONE

CAIRO_EXTEND_PAD

CAIRO_EXTEND_REPEAT

CAIRO_EXTEND_REFLECT

Les dégradés ci-dessus sont obtenus par le programme suivant. Il créé un gradient linéaire dont les deux points de contrôle se trouvent au tiers et au deux tiers des dimensions de la fenêtre. On associe à ces deux points les couleurs rouge et magenta, et on associe au point intermédiaire (de paramètre t=0.5) la couleur verte. Les différences entre les 4 dégradés viennent seulement de la façon dont on décide de traiter les points qui sont associés à un paramètre t inférieur à 0.0 ou supérieur à 1.0:

#include <gtk/gtk.h>
#include <cairo.h>

gboolean on_expose_linear (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  gint dw, dh;
  cairo_pattern_t * lin;

  gdk_drawable_get_size (widget->window, & dw, & dh);
  info->cairo= gdk_cairo_create (widget->window);

  lin= cairo_pattern_create_linear (dw*0.33, dh*0.33, 
                                    dw*0.66, dh*0.66);

  cairo_pattern_set_extend (lin, CAIRO_EXTEND_NONE);
  cairo_pattern_add_color_stop_rgb (lin, /*t=*/ 0.0, /*rgb=*/ 1.0, 0.0, 0.0); 
  cairo_pattern_add_color_stop_rgb (lin, /*t=*/ 0.5, /*rgb=*/ 0.0, 1.0, 0.0); 
  cairo_pattern_add_color_stop_rgb (lin, /*t=*/ 1.0, /*rgb=*/ 1.0, 0.0, 1.0); 

  cairo_set_source (info->cairo, lin);
  cairo_paint (info->cairo);

  cairo_pattern_destroy (lin);
  cairo_destroy (info->cairo);  info->cairo= NULL;
  return TRUE;
}

avec un dégradé de couleurs radial

Le dégradé radial se spécifie via la fonction cairo_pattern_create_radial() avec deux cercles de centres et rayons (Oa,Ra) et (Ob,Rb) auxquels sont associés les valeurs 0.0 et 1.0 du paramètre d'interpolation t. A chaque valeur de t est associé un cercle unicolore dont le centre et le rayon sont obtenus par interpolation entre les 2 cercles de contrôle. Le premier cercle doit être inscrit dans le second sinon les résultats seront difficiles à contrôler.


CAIRO_EXTEND_NONE

CAIRO_EXTEND_PAD

CAIRO_EXTEND_REPEAT

CAIRO_EXTEND_REFLECT

Les gradients ci-dessus sont obtenus en modifiant légèrement le code précédent. Les points intermédiaires sont spécifiés par la même fonction cairo_pattern_add_color_stop_rgb():

gboolean on_expose_radial (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  gint dw, dh;
  cairo_pattern_t * rad;
  gdk_drawable_get_size (widget->window, & dw, & dh);
  info->cairo= gdk_cairo_create (widget->window);
  rad= cairo_pattern_create_radial (dw*0.40, dh*0.40, 20.0, 
                                    dw*0.50, dh*0.50, 80.0);
  cairo_pattern_set_extend (rad, CAIRO_EXTEND_NONE);
  cairo_pattern_add_color_stop_rgb (rad, /*t=*/ 0.0, /*rgb=*/ 1.0, 0.0, 0.0); 
  cairo_pattern_add_color_stop_rgb (rad, /*t=*/ 0.5, /*rgb=*/ 0.0, 1.0, 0.0); 
  cairo_pattern_add_color_stop_rgb (rad, /*t=*/ 1.0, /*rgb=*/ 1.0, 0.0, 1.0); 
  cairo_set_source (info->cairo, rad);
  cairo_paint (info->cairo);
  cairo_pattern_destroy (rad);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

Manipulation du masque

Nous avons utilisé jusqu'ici cairo_paint() qui peint la totalité de la surface sans s'occuper du masque. Les fonctions cairo_stroke() et cairo_fill() permettent respectivement de dessiner les contours d'un chemin découpé dans le masque, et de remplir l'aire décrite par ce chemin.

On trace un chemin (de type cairo_path_t) dans le masque avec les fonctions suivantes:

Le masque est une surface ne possédant qu'un canal alpha (pas de canal RGB). L'action de cairo_fill() (resp. cairo_stroke()) est:



make_heart()

Voici par exemple comment dessiner un coeur avec 4 cubiques de Bézier. Dans make_heart(), on se contente de tracer le chemin dans le masque. Différentes procédures draw_heart_X() exploitent ensuite ce chemin pour obtenir des résultats différents. le coeur est paramétré par la position de son centre (x,y), un pseudo-rayon r, et un écart horizontal d contrôlant son creux et sa pointe:

void make_heart (Info * info, double x, double y, double r, double d)
{
  double y1= y-r, y2= y-r/3, y3= y+r/3, y4= y+r;
  double x1= x-r, x2= x-d  , x3= x+d  , x4= x+r; 
  
  cairo_move_to ( info->cairo, x ,y4);                  
  cairo_curve_to (info->cairo, x2,y3,  x1,y3,  x1,y2); /* courbe rouge   */
  cairo_curve_to (info->cairo, x1,y1,  x2,y1,  x ,y2); /* courbe cyan    */
  cairo_curve_to (info->cairo, x3,y1,  x4,y1,  x4,y2); /* courbe verte   */
  cairo_curve_to (info->cairo, x4,y3,  x3,y3,  x ,y4); /* courbe magenta */
}
gboolean on_expose_heart (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  int dw, dh;
  gdk_drawable_get_size (widget->window, & dw, & dh);
  info->cairo= gdk_cairo_create (widget->window);
  draw_heart_1 (info, dw/2, dh/2, MIN(dw, dh)/3);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

draw_heart_1()

draw_heart_2()

draw_heart_3()

La première procédure draw_heart_1() exploite le chemin avec un simple fill sur un source de couleur rouge mi-transparente:

void draw_heart_1  (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_source_rgba (info->cairo, /*rgb=*/1.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_fill (info->cairo);
}

La deuxième procédure draw_heart_2() exploite le chemin avec un simple stroke sur la même source de couleur rouge mi-transparente:

void draw_heart_2  (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_source_rgba (info->cairo, /*rgb=*/1.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_stroke (info->cairo);
}

La troisième procédure draw_heart_3() exploite le chemin avec un fill sur une source de couleur rouge mi-transparente, suivi d'un stroke sur une source de couleur bleue mi-transparente. Comme on souhaite garder le chemin actif après le fill pour le réutiliser avec un stroke, on utilise cairo_fill_preserve():

void draw_heart_3 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_source_rgba (info->cairo, /*rgb=*/1.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_fill_preserve (info->cairo);
  cairo_set_source_rgba (info->cairo, /*rgb=*/0.0, 0.0, 1.0, /*alpha=*/ 0.5);
  cairo_stroke (info->cairo);
}

draw_heart_4()

draw_heart_5()

draw_heart_6()

La quatrième procédure draw_heart_4() exploite le chemin avec deux strokes d'épaisseurs 15 et 5 sur des source de couleurs mi-transparentes rouge et bleue. Comme on souhaite garder le chemin actif après le premier stroke pour le réutiliser avec le second stroke, on utilise cairo_stroke_preserve() la première fois:

void draw_heart_4 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_source_rgba (info->cairo, /*rgb=*/1.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_set_line_width (info->cairo, 15);      
  cairo_stroke_preserve (info->cairo);
  cairo_set_line_width (info->cairo, 5);      
  cairo_set_source_rgba (info->cairo, /*rgb=*/0.0, 0.0, 1.0, /*alpha=*/ 0.5);
  cairo_stroke (info->cairo);
}

La cinquième procédure draw_heart_5() fait la même chose mais utilise de jointures (joins) arrondies et des extrémités (caps) arrondies. Un exemple de jointure se trouve en haut dans le creux du coeur, et les deux extrémités du chemin se trouve en bas dans la pointe du coeur:

void draw_heart_5 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_line_cap  (info->cairo, CAIRO_LINE_CAP_ROUND);
  cairo_set_line_join (info->cairo, CAIRO_LINE_JOIN_ROUND);
  ...
}

La sixième procédure draw_heart_6() fait la même chose mais utilise de jointures (joins) pointues et des extrémités (caps) carrées. On voit que la jointure pointue n'est pas effectuée dans le creux du coeur car l'angle (0 degré pour un écart d=0) est trop petit pour que ce soit réalisable:

void draw_heart_6 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, 0);
  cairo_set_line_cap  (info->cairo, CAIRO_LINE_CAP_SQUARE);
  cairo_set_line_join (info->cairo, CAIRO_LINE_JOIN_MITER);
  ...
}

draw_heart_7()

draw_heart_8()

draw_heart_9()

La septième procédure draw_heart_7() fait la même chose mais change l'écart d=0 en d=r/3 pour que la jointure pointue soit possible; On voit ici que le fait que le chemin ne soit pas fermé génère des extrémités carrées qui se croisent dans la pointe du coeur, créant un effet graphique disgracieux:

void draw_heart_7 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, r/3);
  cairo_set_line_cap  (info->cairo, CAIRO_LINE_CAP_SQUARE);
  cairo_set_line_join (info->cairo, CAIRO_LINE_JOIN_MITER);
  ...
}

La huitième procédure draw_heart_8() fait la même chose mais ferme le chemin pour éliminer le problème précédent. Il n'y a plus d'extrémités, il n'y a plus que des jointures:

void draw_heart_8 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, r/3);
  cairo_close_path (info->cairo);
  cairo_set_line_join (info->cairo, CAIRO_LINE_JOIN_MITER);
  ...
}

La neuvième procédure draw_heart_9() fait la même chose avec une jointure biseautée:

void draw_heart_9 (Info * info, double x, double y, double r)
{
  make_heart (info, x, y, r, r/3);
  cairo_close_path (info->cairo);
  cairo_set_line_join (info->cairo, CAIRO_LINE_JOIN_BEVEL);
  ...
}

Clipping

Cairo permet de faire du clipping dans une forme quelconque. Pour restreindre les actions de stroke et de fill à l'intérieur d'une forme, il suffit de décrire cette forme par un chemin, et d'invoquer cairo_clip() ou cairo_clip_preserve() si l'on souhaite conserver le chemin pour une utilisation ultérieure. L'exemple suivant montre comment dessiner une image restreinte à l'aire du coeur. On va utiliser l'image du Joker (dans Batman, The Dark Knight). Commençons par charger l'image:

typedef struct Info {
  GtkWidget * win;
  cairo_t * cairo;
  cairo_surface_t * seat, * joker;
} Info;

void info_init (Info * info, GtkWidget * win) {
  info->win  = win;  info->cairo= NULL;
  info->seat = cairo_image_surface_create_from_png ("seat.png");
  info->joker= cairo_image_surface_create_from_png ("joker.png");
}

info->joker
 

draw_heart_10()
width=1;

draw_heart_10()
width=15; reset=FALSE;

draw_heart_10()
width=15; reset=TRUE;

La dixième procédure draw_heart() utilise le coeur tracé dans le masque 3 fois (pour un fill, pour un clipping, et pour un stroke). L'image est peinte, après le clipping et avant le stroke, en mi-transparence avec cairo_paint_with_alpha(). Pour un stroke épais, noter la différence de résultat si l'on conitnue d'être clippé dans le coeur ou si l'on annule le clipping avec cairo_reset_clip():

void draw_heart_10 (Info * info, double x, double y, double r) {
  double jw= cairo_image_surface_get_width  (info->joker);
  double jh= cairo_image_surface_get_height (info->joker);
  gboolean reset= FALSE; double width= 15;
  double jx= x - jw/2.0;
  double jy= y - jh/2.0;

  make_heart (info, x, y, r, r/3);
  cairo_close_path (info->cairo);

  cairo_set_source_rgba (info->cairo, /*rgb=*/1.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_fill_preserve (info->cairo);

  cairo_clip_preserve (info->cairo);
  cairo_set_source_surface (info->cairo, info->joker, jx, jy);
  cairo_paint_with_alpha (info->cairo, 0.5);
  if (reset) cairo_reset_clip (info->cairo);

  cairo_set_source_rgba (info->cairo, /*rgb=*/0.0, 0.0, 0.0, /*alpha=*/ 0.5);
  cairo_set_line_width (info->cairo, width);
  cairo_stroke (info->cairo);
}

Alpha-blending via un gradient dans le masque

Au lieu de peindre la source sur la destination avec un canal alpha uniforme via cairo_paint_with_alpha(), on peut transférer le canal alpha d'un motif dans le masque avec cairo_mask(). Cela est particulièrement utile si le motif est un gradient, ce qui permet d'obtenir une transparence non-uniforme lors de l'application de l'image.


apply_mask= FALSE;

apply_mask= TRUE;

Dans l'exemple suivant, on créé un gradient radial. Sa couleur bleue n'a aucune importance, ce qui compte est sa composante alpha, car c'est elle qui sera transférée dans le masque. Si on peint ensuite le joker avec cairo_mask().

gboolean on_expose_mask (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  double jx, jy;
  double jw= cairo_image_surface_get_width  (info->joker);
  double jh= cairo_image_surface_get_height (info->joker);
  gint dw, dh;
  cairo_pattern_t * rad;
  gboolean apply_mask= FALSE;

  gdk_drawable_get_size (widget->window, & dw, & dh);
  jx= dw/2.0 - jw/2.0;
  jy= dh/2.0 - jh/2.0;

  info->cairo= gdk_cairo_create (widget->window);

  rad= cairo_pattern_create_radial (dw*0.40, dh*0.40, 20.0, 
                                    dw*0.50, dh*0.50, 80.0);
  cairo_pattern_add_color_stop_rgba (rad, /*t=*/ 0.0, /*rgba*/ 0,0,1,1.0); 
  cairo_pattern_add_color_stop_rgba (rad, /*t=*/ 1.0, /*rgba*/ 0,0,1,0.0);
 
  if (! apply_mask) {
    cairo_set_source (info->cairo, rad);
    cairo_paint (info->cairo);
  } else {
    cairo_set_source_surface (info->cairo, info->joker, jx, jy);
    cairo_mask (info->cairo, rad);                   
  }

  cairo_pattern_destroy (rad);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

Utilisation la matrice de transformation

Les exemples suivants montrent comment on peut utiliser la matrice de transformation avant l'initialisation d'une source pour déformer celle-ci.

rotation d'image


angle= 1.0 * G_PI / 4.0;

angle= 2.0 * G_PI / 4.0;

angle= 3.0 * G_PI / 4.0;
gboolean on_expose_rotate (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  double sx, sy;
  double sw= cairo_image_surface_get_width  (info->seat);
  double sh= cairo_image_surface_get_height (info->seat);
  gint dw, dh;
  cairo_matrix_t mat;  
  double angle= 3.0 * G_PI / 4.0;

  gdk_drawable_get_size (widget->window, & dw, & dh);
  sx=  - sw/2.0;
  sy=  - sh/2.0;

  info->cairo= gdk_cairo_create (widget->window);
  cairo_save (info->cairo);

  cairo_matrix_init_identity (& mat);
  cairo_matrix_translate (& mat, dw/2.0, dh/2.0);
  cairo_matrix_rotate (& mat, angle);
  cairo_set_matrix (info->cairo, & mat);

  cairo_set_source_surface (info->cairo, info->seat, sx, sy);
  cairo_paint (info->cairo);                   

  cairo_restore (info->cairo);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

retaille d'image (scale)


scale_x= 0.5;
scale_y= 0.5;

scale_x= 1.5;
scale_y= 0.5;

scale_x= 0.5;
scale_y= 1.5;

scale_x= 1.5;
scale_y= 1.5;
gboolean on_expose_scale (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  double sx, sy;
  double sw= cairo_image_surface_get_width  (info->seat);
  double sh= cairo_image_surface_get_height (info->seat);
  gint dw, dh;
  cairo_matrix_t mat;  
  double scale_x= 0.5, scale_y= 0.5;

  gdk_drawable_get_size (widget->window, & dw, & dh);
  sx=  - sw/2.0;
  sy=  - sh/2.0;

  info->cairo= gdk_cairo_create (widget->window);
  cairo_save (info->cairo);

  cairo_matrix_init_identity (& mat);
  cairo_matrix_translate (& mat, dw/2.0, dh/2.0);
  cairo_matrix_scale (& mat, scale_x, scale_y);
  cairo_set_matrix (info->cairo, & mat);

  cairo_set_source_surface (info->cairo, info->seat, sx, sy);
  cairo_paint (info->cairo);                   

  cairo_restore (info->cairo);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}

symétrie d'image (flip)


scale_x= 1.0;
scale_y= 1.0;

scale_x= 1.0;
scale_y=-1.0;

scale_x=-1.0;
scale_y= 1.0;

scale_x=-1.0;
scale_y=-1.0;

penchement d'image (skew)


skew_x=-0.8;
skew_y=-0.8;

skew_x= 0.0;
skew_y=-0.8;

skew_x= 0.8;
skew_y=-0.8;

skew_x=-0.8;
skew_y= 0.0;

skew_x= 0.0;
skew_y= 0.0;

skew_x= 0.8;
skew_y= 0.0;

skew_x=-0.8;
skew_y= 0.8;

skew_x= 0.0;
skew_y= 0.8;

skew_x= 0.8;
skew_y= 0.8;
gboolean on_expose (GtkWidget * widget, GdkEvent * e, gpointer data)
{
  Info * info= data;
  double sx, sy;
  double sw= cairo_image_surface_get_width  (info->seat);
  double sh= cairo_image_surface_get_height (info->seat);
  gint dw, dh;
  cairo_matrix_t mat, mat_x, mat_y, mat_t;  
  double skew_x= 0., skew_y= -0.8;

  gdk_drawable_get_size (widget->window, & dw, & dh);
  sx=  - sw/2.0;
  sy=  - sh/2.0;

  info->cairo= gdk_cairo_create (widget->window);
  cairo_save (info->cairo);

  cairo_matrix_init (& mat_x,  1,0,       skew_x,1,      0,0);
  cairo_matrix_init (& mat_y,  1,skew_y,       0,1,      0,0);
  cairo_matrix_init (& mat_t,  1,0,            0,1,   dw/2,dh/2);
  cairo_matrix_multiply (& mat, & mat_x, & mat_y);
  cairo_matrix_multiply (& mat, & mat, & mat_t);
  cairo_set_matrix (info->cairo, & mat);

  cairo_set_source_surface (info->cairo, info->seat, sx, sy);
  cairo_paint (info->cairo);                   

  cairo_restore (info->cairo);
  cairo_destroy (info->cairo); info->cairo= NULL;
  return TRUE;
}