Cairo FAQ Clipping and Masking

Материал из Wiki.crossplatform.ru

Версия от 08:58, 19 марта 2009; ViGOur (Обсуждение | вклад)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

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.

#include <cairo.h>
#include <gtk/gtk.h>
 
#include <math.h>
 
cairo_surface_t *image;
 
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 = 40;  
 
  static gint delta[] = { 3, 3 };
 
  cr = gdk_cairo_create(widget->window);
 
  gint width, height;
  gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
 
  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];
 
  cairo_set_source_surface(cr, image, 1, 1);
  cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI);
  cairo_clip(cr);
  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;
}
 
int main(int argc, char *argv[])
{
  GtkWidget *window;
  gint width, height;  
 
  image = cairo_image_surface_create_from_png("turnacastle.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_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;
}

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.

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

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.

 cairo_set_source_surface(cr, image, 1, 1);
 cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI);

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_clip(cr);

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_paint(cr);

The cairo_paint() paints the current source everywhere within the current clip region.

center

Clipping a rectangle

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

#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
 
 
static gboolean
on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data)
{
  cairo_t *cr;
  cr = gdk_cairo_create(widget->window);
 
  static gboolean xdirection = TRUE;
  static gint counter = 0;
 
  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;
 
  counter += 1;  
 
 
  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);
 
  cairo_rotate(cr, rotate);
  rotate += 0.01;
 
  cairo_rectangle(cr, -50, -25, 100, 50);
  cairo_stroke(cr);
 
  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;
 
  gdk_rectangle_intersect(&bigrect, &rect, &intersect);
  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;
}
 
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_widget_set_app_paintable(window, TRUE);
  gtk_widget_show_all(window);
  g_timeout_add(5, (GSourceFunc) time_handler, (gpointer) window);
 
  gtk_main();
 
  return 0;
}

In this example, we have two rectangles. A big one and a rotating one. The big rectangle continuosly shrinks and grows. The smaller one is rotating. We apply a intersect set operation on both rectangles. The area that is inside both rectangles 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 xdirection = TRUE;

This variable determines the direction, in which the big rectangle is moving.

 if (bigx > width) {
     xdirection = FALSE;
     delta = -delta;
     bigx = width;
 }

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.

 cairo_rotate(cr, rotate);

The cairo_rotate() rotates the smaller rectangle.

 GdkRectangle bigrect;
 GdkRectangle rect;
 GdkRectangle intersect;

Here we define three rectangular areas. The intersect rectangle will be the intersection of our two rectangles.

 gdk_rectangle_intersect(&bigrect, &rect, &intersect);

Here we apply the intersection operation.

 cairo_rectangle(cr, intersect.x, intersect.y, intersect.width, intersect.height);
 cairo_fill(cr);

We paint the area of the intersect rectangle.

center

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 sourse 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.

#include <cairo.h>
#include <gtk/gtk.h>
 
 
static gboolean
on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data)
{
  cairo_t *cr;
  cairo_surface_t *surface;
  cairo_set_source_rgb(cr, 0, 0, 0);
 
  cr = gdk_cairo_create(widget->window);
  surface = cairo_image_surface_create_from_png("omen.png");
  cairo_mask_surface(cr, surface, 0, 0);
  cairo_fill(cr);
 
  cairo_surface_destroy(surface);
  cairo_destroy(cr);
 
  return FALSE;
}
 
 
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), 305, 100); 
 
  gtk_window_set_title(GTK_WINDOW(window), "mask");
  gtk_widget_set_app_paintable(window, TRUE);
  gtk_widget_show_all(window);
 
  gtk_main();
 
  return 0;
}

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

 surface = cairo_image_surface_create_from_png("omen.png");
 cairo_mask_surface(cr, surface, 0, 0);
 cairo_fill(cr);

We use an image as a mask, thus displaying it on the window.

center