8. Clipping and Masking

in this part of the Cairo tutorial, we will talk about clipping and masking.

Clipping

Clipping is restricting of drawing to a certain area.

This is done for effeciency reasons and to create interesting effects.

Clipping image

In the following example we will be clipping an image.

clippingimg.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <cairo.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <math.h>

/*
  In this example, we will clip an image.
  A circle is moving on the screen and showing a part of the underlying image.
  This is as if we looked through a hole.
 */

cairo_surface_t *image;

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data);
static gboolean time_handler(GtkWidget *widget);

int main(int argc, char *argv[]){
  GtkWidget *window;

  gint width, height;

  image = cairo_image_surface_create_from_png("aki.png");
  width = cairo_image_surface_get_width(image);
  height = cairo_image_surface_get_height(image);
  
  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), width+2, height+2);
  
  gtk_window_set_title(GTK_WINDOW(window), "clipping image");

  gtk_widget_set_app_paintable(window, TRUE);
  gtk_widget_show_all(window);
  g_timeout_add(100, (GSourceFunc)time_handler, (gpointer)window);

  gtk_main();

  cairo_surface_destroy(image);

  return 0;
}

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data){
  cairo_t *cr;

  static gint pos_x = 128;
  static gint pos_y = 128;
  gint radius = 50; /* 円の半径 */

  static gint delta[] = {3, 3};

  cr = gdk_cairo_create(widget->window);

  gint width, height;
  gtk_window_get_size(GTK_WINDOW(widget), &width, &height);

  /*
    if the circle hits the left or the right side of the window, the
    direction of the circle movement changes randomly.
    same for the top and bottom sides.
   */
  if(pos_x < 0 + radius){
    delta[0] = rand() % 4 + 5;
  }
  else if(pos_x > width - radius){
    delta[0] = -(rand() % 4 + 5);
  }

  if(pos_y < 0 + radius){
    delta[1] = rand() % 4 + 5;
  }
  else if(pos_y > height - radius){
    delta[1] = -(rand() % 4 + 5);
  }

  pos_x += delta[0];
  pos_y += delta[1];

  /*
    here we draw the image and a circle.
    notice that we do not draw onto the window at the moment,
    but only in memory.
  */
  cairo_set_source_surface(cr, image, 1, 1);
  cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI);
  /*
    the cairo_clip() sets a clipping region.
    the clipping region is the current path used.
    the current path was created by the cairo_arc() function call.
   */
  cairo_clip(cr);
  /*
    the cairo_paint() paints the current source everywhere within the current clip region.
   */
  cairo_paint(cr);

  cairo_destroy(cr);

  return FALSE;
}

static gboolean time_handler(GtkWidget *widget){
  if(widget->window == NULL){
    return FALSE;
  }

  gtk_widget_queue_draw(widget);
  return TRUE;
}

clippingimg.c の実行結果は:

_images/clippingimg1.png

Clipping a rectangle

the following example is inspired by a similar example, that I found in the Java2D demo.

clippingrect.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>

/*
  In this example, we have two rectangles.
  A big one and rotating one.
  The big rectangle continously shrinks and grows.
  The smaller one is rotating.
  We apply a intersect set operation on both rectangles.
  The area that is inside both rectangls is painted in black color.
  Note, that the intersection is not a perfect rectangle.
  To make it simpler, we approximate the area to a rectangle.
*/

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data);
static gboolean time_handler(GtkWidget *widget);

int main(int argc, char *argv[]){
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  
  gtk_window_set_title(GTK_WINDOW(window), "clipping rectangle");

  gtk_widget_set_app_paintable(window, TRUE);
  gtk_widget_show_all(window);
  g_timeout_add(5, (GSourceFunc)time_handler, (gpointer)window);

  gtk_main();

  return 0;
}

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data){
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  /*
    the variable determines the direction, in which the big rectangle is moving.
  */
  static gboolean xdirection = TRUE;

  int width, height;

  gtk_window_get_size(GTK_WINDOW(widget), &width, &height);

  static gdouble rotate = 0;

  static gint bigx = 20;
  static gint bigy = 200;
  static gint delta = 1;

  /*
    If the big rectangle is as big as the width of the window, we change the direction.
    The rectangle begins shrinking in the y direction.
  */
  if(bigx > width){
    xdirection = FALSE;
    delta = -delta;
    bigx = width;
  }

  if(bigx < 1){
    bigx = 1;
    delta = -delta;
  }

  if(bigy > height){
    xdirection = TRUE;
    delta = -delta;
    bigy = height;
  }

  if(bigy < 1){
    delta = -delta;
    bigy = 1;
  }

  if(xdirection){
    bigx += delta;
  }else{
    bigy += delta;
  }

  cairo_translate(cr, width/2, height/2);

  cairo_rectangle(cr, -bigx/2, -bigy/2, bigx-2, bigy-2);
  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_set_line_width(cr, 1);
  cairo_stroke(cr);

  /*
    the cairo_rotate() rotates the smaller rectangle.
  */
  cairo_rotate(cr, rotate);
  rotate += 0.01;

  cairo_rectangle(cr, -50, -25, 100, 50);
  cairo_stroke(cr);

  /*
    Here we define three rectangular areas.
    The intersect rectangle will be the intersection of our two rectangles.
  */
  GdkRectangle bigrect;
  GdkRectangle rect;
  GdkRectangle intersect;

  bigrect.x = -bigx/2;
  bigrect.y = -bigy/2;
  bigrect.width = bigx - 2;
  bigrect.height = bigy - 2;

  rect.x = -50;
  rect.y = -25;
  rect.width = 100;
  rect.height = 50;

  /*
    Here we apply the intersection operation.
  */
  gdk_rectangle_intersect(&bigrect, &rect, &intersect);
  /*
    We paint the area of the intersect rectangle.
  */
  cairo_rectangle(cr, intersect.x, intersect.y, intersect.width, intersect.height);
  cairo_fill(cr);

  cairo_destroy(cr);
  
  return FALSE;
}

static gboolean time_handler(GtkWidget *widget){
  if(widget->window == NULL){
    return FALSE;
  }

  gtk_widget_queue_draw(widget);
  return TRUE;
}

clippingrect.c の実行結果は:

_images/clippingrect.png

Mask

Before the source is applied to the surface, it is filtered first.

The mask is used as a filter. The mask determines, where the source is applied and where not.

Opaque parts of the mask allow to copy the source. Transparent parts do not let to copy the source to the surface.

sincos.png

_images/sincos.png

mask.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <cairo.h>
#include <gtk/gtk.h>

/*
  This small example clearly shows the basic idea behind the mask.
  The mask determines where to paint and where not to paint.
*/

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data);

int main(int argc, char *argv[]){
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  g_signal_connect(G_OBJECT(window), "expose-event", G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 600, 480);
  
  gtk_window_set_title(GTK_WINDOW(window), "mask");

  gtk_widget_set_app_paintable(window, TRUE);
  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data){
  cairo_t *cr;
  cairo_surface_t *surface;
  
  cr = gdk_cairo_create(widget->window);
  
  cairo_set_source_rgb(cr, 0, 0, 0);

  cr = gdk_cairo_create(widget->window);

  /*
    We use an image as a mask, thus displaying it on the window.
  */
  surface = cairo_image_surface_create_from_png("sincos.png");
  cairo_mask_surface(cr, surface, 0, 0);
  cairo_fill(cr);

  cairo_surface_destroy(surface);

  cairo_destroy(cr);
  
  return FALSE;
}

mask.c の実行結果は:

_images/mask.png

Table Of Contents

Previous topic

7. Compositing

Next topic

9. Transformations