in this part of the Cairo tutorial, we will talk about clipping and masking.
Clipping is restricting of drawing to a certain area.
This is done for effeciency reasons and to create interesting effects.
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 の実行結果は:
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 の実行結果は:
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
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 の実行結果は: