Cairo - Graphics Framework
Cairo - Graphics Framework
Cairo - Graphics Framework
Home Contents
2D Vector Graphics
There are two different computer graphics. Vec tor and ra ster graphics. Raster graphics represents images as a collection of pixels. Vector graphics is the use of geometrical primitives such as points, lines, curves or polygons to represent images. These primitives are created using mathematical equations. Both types of computer graphics have advantages and disadvantages. The advantages of vector graphics over raster are: smaller size ability to zoom indefinitely moving, scaling, filling or rotating does not degrade the quality of an image
Cairo
Cairo is a library for creating 2D vector graphics. It is written in the C programming language. Bindings for other computer languages exist. Python, Perl, C++, C#, Java. Cairo is a multiplatform library, works on Linux, BSDs, OSX. Cairo supports various backends. X Window System Win32 GDI Mac OS X Quartz PNG PDF PostScript SVG This means, we can use the library to draw on windows on Linux/BSDs, Windows, OSX and we can use the library to create PNG images, PDF files, PostScript files and SVG files. We can compare the cairo library to the GDI + library on Windows OS and the Qu a rtz 2D on Mac OS. Cairo is an open source software library. From version 2.8, the cairo library is part of the GT K+ system.
Compiling examples
The examples are created in the C programming language. We use GNU C compiler to compile them.
gcc example.c -o example `pkg-config --cflags --libs gtk+-3.0`
Note that the order of compiling options is important. Home Contents Top of Page
Cairo Definitions
Home Contents
Cairo definitions
In this part of the Cairo graphics tutorial, we will provide some useful definitions for the Cairo graphics library. This will help us better understand the Cairo drawing model.
Context
To do some drawing in Cairo, we must first create a Cairo context. The Cairo context holds all of the graphics state parameters that describe how drawing is to be done. This includes information such as line width, colour, the surface to draw to, and many other things. This allows the actual drawing functions to take fewer arguments to simplify the interface. The gdk_cairo_create() function call creates a cairo context for drawing on a GDK window. A GDK window is one of the supported Cairo backends.
cairo_t *cr; cr = gdk_cairo_create(gtk_widget_get_window(widget));
These two lines create a cairo context. In this example, the context is tied to a Gd kW ind ow. A cairo_t structure contains the current state of the rendering device, including coordinates of yet to be drawn shapes. Technically speaking, the cairo_t objects are called the Cairo contexts. All drawing with Cairo is always done to a cairo_t object. A Cairo context is tied to a specific surface. A PDF, SVG, PNG, GdkWindow etc. The GDK does not wrap the Cairo API. It allows to create a Cairo context, which can be used to draw on GDK windows.
P ath
A path is made up of one or more lines. These lines are connected by two or more anchor points. Paths can be made up of straight lines and curves. There are two kinds of paths. Open and closed paths. In a closed path, starting and ending points meet. In an open path, starting and ending point do not meet. In Cairo, we start with an empty path. First we define a path and then we make them visible by stroking and filling them. One important note. After each cairo_stroke() or cairo_fill() function calls, the path is emptied. We have to define a new path. A path is made of subpaths.
Source
The source is the paint we use in drawing. We can compare the source to a pen or ink, that we will use to draw the outlines and fill the shapes. There are four kinds of basic sources. Colors, gradients, patterns and images.
Surface
A surface is a destination that we are drawing to. We can render documents using the PDF or PostScript surfaces, directly draw to a platform via the Xlib and Win32 surfaces. The documentation mentions the following surfaces:
typedef enum _cairo_surface_type { CAIRO_SURFACE_TYPE_IMAGE, CAIRO_SURFACE_TYPE_PDF, CAIRO_SURFACE_TYPE_PS,
Cairo Definitions
CAIRO_SURFACE_TYPE_XLIB, CAIRO_SURFACE_TYPE_XCB, CAIRO_SURFACE_TYPE_GLITZ, CAIRO_SURFACE_TYPE_QUARTZ, CAIRO_SURFACE_TYPE_WIN32, CAIRO_SURFACE_TYPE_BEOS, CAIRO_SURFACE_TYPE_DIRECTFB, CAIRO_SURFACE_TYPE_SVG, CAIRO_SURFACE_TYPE_OS2 } cairo_surface_type_t;
M ask
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.
P attern
A cairo pattern represents a source when drawing onto a surface. In cairo, a pattern is something that you can read from, that is used as the source or mask of a drawing operation. Patterns in cairo can be solid, surface-based or gradients patterns. In this chapter of the Cairo tutorial, we have given some basic definitions.
Cairo backends
Home Contents
Cairo backends
The Cairo library supports various backends. In this section of the Cairo graphics tutorial, we will use Cairo to create a PNG image, PDF file, SVG file and we will draw on a GTK window.
P NG image
In the first example, we will create a PNG image.
#include <cairo.h> int main(void) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 390, 60); cr = cairo_create(surface); cairo_set_source_rgb(cr, 0, 0, 0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 40.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Disziplin ist Macht."); cairo_surface_write_to_png(surface, "image.png"); cairo_destroy(cr); cairo_surface_destroy(surface); } return 0;
This example is a small console application, that will create a PNG image.
#include <cairo.h>
In this header file, we will find declarations of our functions and constants.
cairo_surface_t *surface; cairo_t *cr;
We move to a (10.0, 50.0) position within the image and draw the text.
Cairo backends
cairo_surface_write_to_png(surface, "image.png");
P DF file
In the second example, we will use the Cairo library to create a simple PDF file.
#include <cairo.h> #include <cairo-pdf.h> int main(void) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_pdf_surface_create("pdffile.pdf", 504, 648); cr = cairo_create(surface); cairo_set_source_rgb(cr, 0, 0, 0); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size (cr, 40.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Disziplin ist Macht."); cairo_show_page(cr); cairo_surface_destroy(surface); cairo_destroy(cr); } return 0;
We must open the pdf file in a pdf viewer. Linux users can use KPDF or Evince viewers.
surface = cairo_pdf_surface_create("pdffile.pdf", 504, 648);
To render a pdf file, we must create a pdf surface using the cairo_pdf_surface_create() function call. The size of the pdf file is specified in points, which is a standard in typesetting.
cairo_show_page(cr);
SVG file
The next example creates a simple SVG (Scalable Vector Graphics) file. The SVG is one of the hottest technologies these days.
#include <cairo.h> #include <cairo-svg.h>
Cairo backends
int main(void) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_svg_surface_create("svgfile.svg", 390, 60); cr = cairo_create(surface); cairo_set_source_rgb(cr, 0, 0, 0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 40.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Disziplin ist Macht."); cairo_surface_destroy(surface); cairo_destroy(cr); } return 0;
We can use Firefox, Opera or Inkscape programs to open the svgfile.svg file.
surface = cairo_svg_surface_create("svgfile.svg", 390, 60);
To create a SVG file in Cairo, we must create a svg surface using the cairo_svg_surface_create() function call.
cr = cairo_create(surface);
A Cairo context is created from a SVG surface. The Rest of the code is identical to the previous examples.
GTK W indow
In the last example, we will draw on the GTK window. This backend will be used throughout the rest of the tutorial.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 40.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Disziplin ist Macht.");
Cairo backends
} int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 400, 90); gtk_window_set_title(GTK_WINDOW(window), "GTK window"); gtk_widget_show_all(window); gtk_main(); } return 0;
The example pops up a centered GTK window, on which we draw the "Disziplin ist Macht" text.
#include <cairo.h> #include <gtk/gtk.h>
In the on_draw_event() function, we create a Cairo context. It is a graphics object on which we perform the drawing operations. Actual drawing is delegated to the do_drawing() function. After the drawing, the cairo context is destroyed.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, 40.0); cairo_move_to(cr, 10.0, 50.0); cairo_show_text(cr, "Disziplin ist Macht.");
We create a GtkDrawingArea widget and add it to the container window. It is used for custom drawing.
g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL);
Cairo backends
When the GtkDrawingArea widget needs to be redrawn, it emits the draw signal. We connect that signal to the on_draw_event() callback.
Figure: GTK window In this chapter we have covered supported Cairo backends.
Home Contents
Lines
Lines are very basic vector objects. To draw a line, we use two function calls. The starting point is specified with the cairo_move_to() call. The ending point of a line is specified with the
cairo_line_to()
call.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); struct { int count; double coordx[100]; double coordy[100]; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width(cr, 0.5); int i, j; for (i = 0; i <= glob.count - 1; i++ ) { for (j = 0; j <= glob.count - 1; j++ ) { cairo_move_to(cr, glob.coordx[i], glob.coordy[i]); cairo_line_to(cr, glob.coordx[j], glob.coordy[j]); } } glob.count = 0; cairo_stroke(cr);
static gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { if (event->button == 1) { glob.coordx[glob.count] = event->x; glob.coordy[glob.count++] = event->y; } if (event->button == 3) { gtk_widget_queue_draw(widget); } } return TRUE;
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; glob.count = 0;
In our example, we click randomly on a window with a left mouse button. Each click is stored in an array. When we right click on the window, all points are connected with every point in the array. This way, we can create some interesting objects. Right clicking on the drawn object clears the window, and we can click another object.
cairo_set_source_rgb(cr, 0, 0, 0); cairo_set_line_width (cr, 0.5);
The lines will be drawn in black ink and will be 0.5 points wide.
int i, j; for (i = 0; i <= glob.count - 1; i++ ) { for (j = 0; j <= glob.count - 1; j++ ) { cairo_move_to(cr, glob.coordx[i], glob.coordy[i]); cairo_line_to(cr, glob.coordx[j], glob.coordy[j]); } }
Inside the clicked callback, we determine, if we there was a left or right click. If we click with a left mouse button, we store the x, y coordinates into the arrays.
if (event->button == 3) { gtk_widget_queue_draw(widget); }
Figure: Lines
static void do_drawing(cairo_t *cr, GtkWidget *widget) { GtkWidget *win = gtk_widget_get_toplevel(widget); int width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); cairo_set_line_width(cr, 9); cairo_set_source_rgb(cr, 0.69, 0.19, 0); cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 50, 0, 2 * M_PI); cairo_stroke_preserve(cr); cairo_set_source_rgb(cr, 0.3, 0.4, 0.6); cairo_fill(cr);
int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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);
In our example, we will draw a circle and fill it with a solid color.
#include <math.h>
Here we get the width and height of the window. We will need these values, when we draw the circle. The circle will be resized, when we resize the window.
cairo_set_line_width(cr, 9); cairo_set_source_rgb(cr, 0.69, 0.19, 0);
We set a line width with the set_line_width() method. We set the source to some dark red colour using the set_source_rgb() method.
cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 50, 0, 2 * M_PI); cairo_stroke_preserve(cr);
With the cairo_translate() method, we move the drawing origin to the center of the window. We want our circle to be centered. The arc() method adds a new circular path to the cairo drawing context. Finally, the stroke_preserve() method draws the outline of the circle. Unlike the
stroke()
P en dashes
Each line can be drawn with a different pen dash. It defines the style of the line. The dash is used by the cairo_stroke() function call. The dash pattern is specified by the cairo_set_dash() function. The pattern is set by the dash array, which is an array of positive floating point values. They set the on and off parts of the dash pattern. We also specify the length of the array and the offset value. If the length is 0, the dashing is disabled. If it is 1, a symmetric pattern is asumed with alternating on and off portions of the size specified by the single value in dashes.
In this example, we will draw three lines with different dash patterns.
static const double dashed1[] = {4.0, 21.0, 2.0};
We have a pattern of three numbers. We have 4 points drawn, 21 not drawn and 2 drawn. Then 4 points not drawn, 21 points drawn and 2 not drawn. This pattern takes turns until the end of the line.
static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);
These lines create a line with a pen dash of a symmetric pattern of alternating single on and off points.
Figure: Dashes
Line caps
The line caps are endpoints of lines.
CAIRO_LINE_CAP_SQUARE CAIRO_LINE_CAP_ROUND CAIRO_LINE_CAP_BUTT There are three different line cap styles in Cairo.
Figure: Square, round and butt caps A line with a CAIRO_LINE_CAP_SQUARE cap will have a different size, than a line with a
CAIRO_LINE_CAP_BUTT
will be exactly width px greater in size. width/2 px at the beginning and width/2 px at the end.
static void do_drawing(cairo_t *cr) { cairo_set_line_width(cr, 10); cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); cairo_move_to(cr, 30, 50); cairo_line_to(cr, 150, 50); cairo_stroke(cr); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_move_to(cr, 30, 90); cairo_line_to(cr, 150, 90); cairo_stroke(cr); cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE); cairo_move_to(cr, 30, 130); cairo_line_to(cr, 150, 130); cairo_stroke(cr); cairo_set_line_width(cr, 1.5); cairo_move_to(cr, 30, 40); cairo_line_to(cr, 30, 140); cairo_stroke(cr); cairo_move_to(cr, 150, 40); cairo_line_to(cr, 150, 140); cairo_stroke(cr); cairo_move_to(cr, 155, 40); cairo_line_to(cr, 155, 140); cairo_stroke(cr);
The example draws three lines with three different caps. It will also graphically demonstrate the differences is size of the lines.
cairo_set_line_width(cr, 10);
This is one of the three vertical lines used to demostrate the differences in size.
Line joins
The lines can be joined using three different join styles. CAIRO_LINE_JOIN_MITER CAIRO_LINE_JOIN_BEVEL CAIRO_LINE_JOIN_ROUND
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.1, 0, 0); cairo_rectangle(cr, 30, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); cairo_stroke(cr); cairo_rectangle(cr, 160, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); cairo_stroke(cr); cairo_rectangle(cr, 100, 160, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); cairo_stroke(cr);
In this example, we draw three thick rectangles with various line joins.
cairo_rectangle(cr, 30, 30, 100, 100); cairo_set_line_width(cr, 14); cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER); cairo_stroke(cr);
In this code example, we draw a rectangle with CAIRO_LINE_JOIN_MITER join style. The lines are 14 px wide.
Home Contents
Basic shapes
The Cairo API has some basic functions to create simple shapes.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); cairo_set_line_width(cr, 1); cairo_rectangle(cr, 20, 20, 120, 80); cairo_rectangle(cr, 180, 20, 80, 80); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_arc(cr, 330, 60, 40, 0, 2*M_PI); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_arc(cr, 90, 160, 40, M_PI/4, M_PI); cairo_close_path(cr); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_translate(cr, 220, 180); cairo_scale(cr, 1, 0.7); cairo_arc(cr, 0, 0, 50, 0, 2*M_PI); cairo_stroke_preserve(cr); cairo_fill(cr);
In this example, we will create a rectangle, a square, a circle, an arc and an ellipse.
cairo_rectangle(cr, 20, 20, 120, 80); cairo_rectangle(cr, 180, 20, 80, 80);
The cairo_rectangle() is used to create both squares and rectangles. A square is just a specific type of a rectangle.
cairo_arc(cr, 330, 60, 40, 0, 2*M_PI);
Figure: Basic shapes Other shapes can be created using a combination of basic primitives.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); int points[11][2] = { { 0, 85 }, { 75, 75 }, { 100, 10 }, { 125, 75 }, { 200, 85 }, { 150, 125 }, { 160, 190 }, { 100, 150 }, { 40, 190 }, { 50, 125 }, { 0, 85 } }; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); cairo_set_line_width(cr, 1); gint i; for (i = 0; i < 10; i++) { cairo_line_to(cr, points[i][0], points[i][1]); } cairo_close_path(cr); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_move_to(cr, 240, 40); cairo_line_to(cr, 240, 160); cairo_line_to(cr, 350, 160); cairo_close_path(cr); cairo_stroke_preserve(cr); cairo_fill(cr); cairo_move_to(cr, 380, 40); cairo_line_to(cr, 380, 160); cairo_line_to(cr, 450, 160); cairo_curve_to(cr, 440, 155, 380, 145, 380, 40); cairo_stroke_preserve(cr); cairo_fill(cr);
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
In this example, we create a star object a triangle and a modified triangle. These objects are created using lines and one curve.
gint i; for (i = 0; i < 10; i++ ) { cairo_line_to(cr, points[i][0], points[i][1]); } cairo_close_path(cr);
The star is drawn by joining all the points that are in the points array. The star is finished by calling the cairo_close_path() function, which joins the last two points of a star.
cairo_move_to(cr, 380, 40); cairo_line_to(cr, 380, 160); cairo_line_to(cr, 450, 160); cairo_curve_to(cr, 440, 155, 380, 145, 380, 40);
The modified triangle is a simple combination of two lines and one curve.
Fills
Fills fill the interiors of shapes. Fills can be solid colours, patters or gradients.
Solid colou r s
A colour is an object representing a combination of Red, Green, and Blue (RGB) intensity values. Cairo valid RGB values are in the range 0 to 1.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.5, 0.5, 1); cairo_rectangle(cr, 20, 20, 100, 100); cairo_fill(cr); cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); cairo_rectangle(cr, 150, 20, 100, 100); cairo_fill(cr); cairo_set_source_rgb(cr, 0, 0.3, 0); cairo_rectangle(cr, 20, 140, 100, 100); cairo_fill(cr);
The cairo_set_source_rgb() function call sets the source to an opaque colour. The parameters are the Red, Green, Blue intensity values. The source is used to fill the interior of a rectangle by calling the cairo_fill() function.
Patter ns
Patterns are complex graphical objects that can fill the shapes.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); cairo_surface_t cairo_surface_t cairo_surface_t cairo_surface_t *surface1; *surface2; *surface3; *surface4;
static void create_surfaces() { surface1 = cairo_image_surface_create_from_png("blueweb.png"); surface2 = cairo_image_surface_create_from_png("maple.png"); surface3 = cairo_image_surface_create_from_png("crack.png"); surface4 = cairo_image_surface_create_from_png("chocolate.png"); } static void destroy_surfaces() { cairo_surface_destroy(surface1); cairo_surface_destroy(surface2); cairo_surface_destroy(surface3); cairo_surface_destroy(surface4); } static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
cairo_set_source(cr, pattern1); cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); cairo_rectangle(cr, 20, 20, 100, 100); cairo_fill(cr); cairo_set_source(cr, pattern2); cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); cairo_rectangle(cr, 150, 20, 100, 100); cairo_fill(cr); cairo_set_source(cr, pattern3); cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); cairo_rectangle(cr, 20, 140, 100, 100); cairo_fill(cr); cairo_set_source(cr, pattern4); cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); cairo_rectangle(cr, 150, 140, 100, 100); cairo_fill(cr); cairo_pattern_destroy(pattern1); cairo_pattern_destroy(pattern2); cairo_pattern_destroy(pattern3); cairo_pattern_destroy(pattern4);
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); create_surfaces(); gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 270, 260); gtk_window_set_title(GTK_WINDOW(window), "Patterns"); gtk_widget_show_all(window); gtk_main(); destroy_surfaces(); } return 0;
In this example we draw four rectangles again. This time, we fill them with some patterns. We use four pattern images from the Gim p image manipulation program. We must retain the original size of those patterns, because we are going to tile them. We create image surfaces outside the on_draw_event() function. It would not be efficient to read from harddisk each time, the window needs to be redrawn.
pattern1 = cairo_pattern_create_for_surface(surface1);
Here we draw our first rectangle. The cairo_set_source() tells the Cairo context to use a pattern as a source for drawing. The image patterns may not fit exactly the shape. We set the mode to
CAIRO_EXTEND_REPEAT, which causes
creates a rectangular path. Finally, cairo_fill() fills the path with the source. This chapter covered Cairo shapes and fills.
Gradients
Home Contents
Gradients
In this part of the Cairo graphics tutorial, we will cover gradients. We will mention linear and radial gradients. In computer graphics, gradient is a smooth blending of shades from light to dark or from one colour to another. In 2D drawing programs and paint programs, gradients are used to create colourful backgrounds and special effects as well as to simulate lights and shadows. (answers.com)
Linear gradients
Linear gradients are blendings of colours or shades of colours along a line. They are created with the cairo_pattern_create_linear() function.
#include <cairo.h> #include <gtk/gtk.h> void draw_gradient1(cairo_t *); void draw_gradient2(cairo_t *); void draw_gradient3(cairo_t *); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); draw_gradient1(cr); draw_gradient2(cr); draw_gradient3(cr); cairo_destroy(cr); } return FALSE;
350.0, 350.0);
gdouble j; gint count = 1; for ( j = 0.1; j < 1; j += 0.1 ) { if (( count % 2 )) { cairo_pattern_add_color_stop_rgb(pat1, j, 0, 0, 0); } else { cairo_pattern_add_color_stop_rgb(pat1, j, 1, 0, 0); } count++; } cairo_rectangle(cr, 20, 20, 300, 100); cairo_set_source(cr, pat1); cairo_fill(cr); } cairo_pattern_destroy(pat1);
350.0, 0.0);
gdouble i; gint count = 1; for ( i = 0.05; i < 0.95; i += 0.025 ) { if (( count % 2 )) { cairo_pattern_add_color_stop_rgb(pat2, i, 0, 0, 0); } else { cairo_pattern_add_color_stop_rgb(pat2, i, 0, 0, 1);
Gradients
} count++;
void draw_gradient3(cairo_t *cr) { cairo_pattern_t *pat3; pat3 = cairo_pattern_create_linear(20.0, 260.0, 20.0, 360.0); cairo_pattern_add_color_stop_rgb(pat3, 0.1, 0, 0, 0); cairo_pattern_add_color_stop_rgb(pat3, 0.5, 1, 1, 0); cairo_pattern_add_color_stop_rgb(pat3, 0.9, 0, 0, 0); cairo_rectangle(cr, 20, 260, 300, 100); cairo_set_source(cr, pat3); cairo_fill(cr); } cairo_pattern_destroy(pat3);
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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), 340, 390); gtk_window_set_title(GTK_WINDOW(window), "Linear gradients"); gtk_widget_set_app_paintable(window, TRUE); gtk_widget_show_all(window); gtk_main(); } return 0;
Here we create a linear gradient pattern. The parameters specify the line, along which we draw the gradient. In our case it is a vertical line.
cairo_pattern_add_color_stop_rgb(pat3, 0.1, 0, 0, 0); cairo_pattern_add_color_stop_rgb(pat3, 0.5, 1, 1, 0); cairo_pattern_add_color_stop_rgb(pat3, 0.9, 0, 0, 0);
We define colour stops to produce our gradient pattern. In this case, the gradient is a blending of black and yellow colours. By adding two black and one yellow stops, we create a horizontal gradient pattern. What these stops actually mean? In our case, we begin with black colour, which will stop at 1/10 of the size. Then we begin to gradually paint in yellow, which will culminate at the centre of the shape. The yellow colour stops at 9/10 of the size, where we begin painting in black again, until the end.
Gradients
Radial gradients
Radial gradients are blendings of colours or shades of colours between two circles. The
cairo_pattern_create_radial() #include <cairo.h> #include <math.h> #include <gtk/gtk.h> void draw_gradient1(cairo_t *); void draw_gradient2(cairo_t *); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); draw_gradient1(cr); draw_gradient2(cr); cairo_destroy(cr); } return FALSE;
void draw_gradient1(cairo_t *cr) { cairo_pattern_t *r1; cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_set_line_width(cr, 12); cairo_translate(cr, 60, 60); r1 = cairo_pattern_create_radial(30, 30, 10, 30, 30, 90); cairo_pattern_add_color_stop_rgba(r1, 0, 1, 1, 1, 1); cairo_pattern_add_color_stop_rgba(r1, 1, 0.6, 0.6, 0.6, 1); cairo_set_source(cr, r1); cairo_arc(cr, 0, 0, 40, 0, M_PI * 2); cairo_fill(cr); } cairo_pattern_destroy(r1);
void draw_gradient2(cairo_t *cr) { cairo_pattern_t *r2; cairo_translate(cr, 120, 0); r2 = cairo_pattern_create_radial(0, 0, 10, 0, 0, 40); cairo_pattern_add_color_stop_rgb(r2, 0, 1, 1, 0);
Gradients
cairo_pattern_add_color_stop_rgb(r2, 0.8, 0, 0, 0); cairo_set_source(cr, r2); cairo_arc(cr, 0, 0, 40, 0, M_PI * 2); cairo_fill(cr);
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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), 300, 200); gtk_window_set_title(GTK_WINDOW(window), "Radial gradients"); gtk_widget_set_app_paintable(window, TRUE); gtk_widget_show_all(window); gtk_main(); } return 0;
We draw a circle and fill its inside with a radial gradient. The radial gradient is defined by two circles. The cairo_pattern_add_color_stop_rgba() function defines the colours. We can experiment with the position of the circles or the length of their radius. In the first gradient example, we have created an object which resembles a 3D shape.
r2 = cairo_pattern_create_radial(0, 0, 10, 0, 0, 40); cairo_pattern_add_color_stop_rgb(r2, 0, 1, 1, 0); cairo_pattern_add_color_stop_rgb(r2, 0.8, 0, 0, 0); cairo_set_source(cr, r2); cairo_arc(cr, 0, 0, 40, 0, M_PI * 2); cairo_fill(cr);
In this example, the circles that define the radial gradient and the custom drawn circle have a common center point.
Gradients
Transparency
Home Contents
Transparency
In this part of the Cairo C API tutorial, we will talk about transparency. We will provide some basic definitions and two interesting transparency effects. Transparency is the quality of being able to see through a material. The easiest way to understand transparency is to imagine a piece of glass or water. Technically, the rays of light can go through the glass and this way we can see objects behind the glass. In computer graphics, we can achieve transparency effects using a lp h a c om p ositing. Alpha compositing is the process of combining an image with a background to create the appearance of partial transparency. The composition process uses an a lp h a c h a nnel. Alpha channel is an 8-bit layer in a graphics file format that is used for expressing translucency (transparency). The extra eight bits per pixel serves as a mask and represents 256 levels of translucency. (answers.com, wikipedia.org)
Transparent rectangles
The first example will draw ten rectangles with different levels of transparency.
static void do_drawing(cairo_t *cr) { gint i; for ( i = 1; i <= 10; i++) { cairo_set_source_rgba(cr, 0, 0, 1, i*0.1); cairo_rectangle(cr, 50*i, 20, 40, 40); cairo_fill(cr); } }
The cairo_set_source_rgba() has an optional alpha parameter to provide transparency. This code creates ten rectangles with alpha values from 0.1 ... 1.
Figure: Transparency
P uff effect
In the following example, we create a puff effect. The example will display a growing centered text that will gradually fade out from some point. This is a very common effect which we can often see in flash animations. The cairo_paint_with_alpha() method is crucial to create the effect.
#include <cairo.h> #include <gtk/gtk.h> void do_drawing(cairo_t *, GtkWidget *); struct { gboolean timer; gdouble alpha; gdouble size; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget));
Transparency
do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_text_extents_t extents; GtkWidget *win = gtk_widget_get_toplevel(widget); gint width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); gint x = width/2; gint y = height/2; cairo_set_source_rgb(cr, 0.5, 0, 0); cairo_paint(cr); cairo_select_font_face(cr, "Courier", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); glob.size += 0.8; if (glob.size > 20) { glob.alpha -= 0.01; } cairo_set_font_size(cr, glob.size); cairo_set_source_rgb(cr, 1, 1, 1); cairo_text_extents(cr, "ZetCode", &extents); cairo_move_to(cr, x - extents.width/2, y); cairo_text_path(cr, "ZetCode"); cairo_clip(cr); cairo_paint_with_alpha(cr, glob.alpha); if (glob.alpha <= 0) { glob.timer = FALSE; }
static gboolean time_handler(GtkWidget *widget) { if (!glob.timer) return FALSE; gtk_widget_queue_draw(widget); } return TRUE;
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; glob.timer = TRUE; glob.alpha = 1.0; glob.size = 1.0; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 350, 200); gtk_window_set_title(GTK_WINDOW(window), "Puff"); g_timeout_add(14, (GSourceFunc) time_handler, (gpointer) window);
Transparency
gtk_widget_show_all(window); gtk_main(); } return 0;
Here we define some variables inside a structure. This is used to avoid using globals.
draw_text(cr, widget);
The text is going to be centered on the window. Therefore we need to find out the size of the parent widget.
cairo_set_source_rgb(cr, 0.5, 0, 0); cairo_paint(cr);
The background of the window is filled with some dark red colour.
cairo_select_font_face(cr, "Courier", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
The size of the text is increased by 0.8 units. After it has reached 20 units, the alpha value starts decreasing. And the text fades away slowly.
cairo_text_extents(cr, "ZetCode", &extents); cairo_move_to(cr, x - extents.width/2, y);
We get the text metrics. We will use only the text width. We move to a position where the text will be centered on the window.
cairo_text_path(cr, "ZetCode"); cairo_clip(cr); cairo_paint_with_alpha(cr, glob.alpha);
We get the path of the text with the cairo_text_path() method. We restrict the painting to the current path using the cairo_clip() method. The cairo_paint_with_alpha() method paints the current source everywhere within the current clip region using a mask of the alpha value.
glob.timer = TRUE; glob.alpha = 1.0;
Transparency
glob.size = 1.0;
The main function of the time_handler call is to redraw the window regularly. When the function returns FALSE, the timeout function will cease to work.
g_timeout_add(14, (GSourceFunc) time_handler, (gpointer) window);
We create a timer function. This function will call time_handler every 14 ms.
W aiting demo
In this examle, we use transparency effect to create a waiting demo. We will draw 8 lines that will gradually fade out creating an illusion, that a line is moving. Such effects are often used to inform users, that a lengthy task is going on behind the scenes. An example is streaming video over the internet.
#include <cairo.h> #include <gtk/gtk.h> #include <math.h> static void do_drawing(cairo_t *, GtkWidget *); struct { gushort count; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr, GtkWidget *widget) { static gdouble const trs[8][8] = { { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 }, { 1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9 }, { 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8 }, { 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65}, { 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5 }, { 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3 }, { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15 }, { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, } };
Transparency
GtkWidget *win = gtk_widget_get_toplevel(widget); gint width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); cairo_translate(cr, width/2, height/2); gint i = 0; for (i = 0; i < 8; i++) { cairo_set_line_width(cr, 3); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_set_source_rgba(cr, 0, 0, 0, trs[glob.count%8][i]); cairo_move_to(cr, 0.0, -10.0); cairo_line_to(cr, 0.0, -40.0); cairo_rotate(cr, M_PI/4); } cairo_stroke(cr);
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; glob.count = 0; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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, 150); gtk_window_set_title(GTK_WINDOW(window), "Waiting demo"); g_timeout_add(100, (GSourceFunc) time_handler, (gpointer) window); gtk_widget_show_all(window); gtk_main(); } return 0;
This is a two dimensional array of transparency values used in this demo. There are 8 rows, each for one state. Each of the 8 lines will continuosly use these values.
cairo_set_line_width(cr, 3);
Transparency
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
We make the lines a bit thicker, so that they are better visible. We draw the lines with rouded caps.
cairo_set_source_rgba(cr, 0, 0, 0, trs[glob.count%8][i]);
Figure: Waiting demo In this part of the Cairo tutorial, we have covered transparency.
Compositing
Home Contents
Compositing
In this part of the Cairo graphics programming tutorial, we will define compositing operations. Com p ositing is the combining of visual elements from separate sources into single images. They are used to create the illusion that all those elements are parts of the same scene. Compositing is videly used in film industry to create crowds, entire new worlds which would be expensive or impossible to create otherwise. (wikipedia.org)
Operations
There are several compositing operations. The Cairo graphics library has 14 different compositing operations.
#include <cairo.h> #include <gtk/gtk.h> void do_drawing(cairo_t *cr, gint x, gint w, gint h, cairo_operator_t op) { cairo_t *first_cr, *second_cr; cairo_surface_t *first, *second; first = cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, w, h); second = cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, w, h); first_cr = cairo_create(first); cairo_set_source_rgb(first_cr, 0, 0, 0.4); cairo_rectangle(first_cr, x, 20, 50, 50); cairo_fill(first_cr); second_cr = cairo_create(second); cairo_set_source_rgb(second_cr, 0.5, 0.5, 0); cairo_rectangle(second_cr, x+10, 40, 50, 50); cairo_fill(second_cr); cairo_set_operator(first_cr, op); cairo_set_source_surface(first_cr, second, 0, 0); cairo_paint(first_cr); cairo_set_source_surface(cr, first, 0, 0); cairo_paint(cr); cairo_surface_destroy(first); cairo_surface_destroy(second); cairo_destroy(first_cr); cairo_destroy(second_cr); } static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); cairo_operator_t oper[] = { CAIRO_OPERATOR_DEST_OVER, CAIRO_OPERATOR_DEST_IN, CAIRO_OPERATOR_OUT, CAIRO_OPERATOR_ADD, CAIRO_OPERATOR_ATOP, CAIRO_OPERATOR_DEST_ATOP, }; GtkWidget *win = gtk_widget_get_toplevel(widget);
Compositing
gint width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); gint i; gint x, y; for(x=20, y=20, i=0; i < 6; x+=80, i++) { do_drawing(cr, x, width, height, oper[i] ); } cairo_destroy(cr); } return FALSE;
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 510, 120); gtk_window_set_title(GTK_WINDOW(window), "Compositing operations"); gtk_widget_show_all(window); gtk_main(); } return 0;
Compositing
CAIRO_OPERATOR_ATOP, CAIRO_OPERATOR_DEST_ATOP,
};
Home Contents
Clipping
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.
#include <cairo.h> #include <gtk/gtk.h> #include <math.h> static void do_drawing(cairo_t *, GtkWidget *); struct { cairo_surface_t *image; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr, GtkWidget *widget) { static gint pos_x = 128; static gint pos_y = 128; static gint radius = 40; static gint delta[] = { 3, 3 }; GtkWidget *win = gtk_widget_get_toplevel(widget); gint width, height; gtk_window_get_size(GTK_WINDOW(win), &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, glob.image, 1, 1); cairo_arc(cr, pos_x, pos_y, radius, 0, 2*M_PI); cairo_clip(cr); cairo_paint(cr);
static gboolean time_handler(GtkWidget *widget) { gtk_widget_queue_draw(widget); return TRUE; } int main(int argc, char *argv[]) {
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, glob.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.
glob.image = cairo_image_surface_create_from_png("turnacastle.png");
function.
M ask
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 void do_drawing(cairo_t *); struct { cairo_surface_t *surface; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_mask_surface(cr, glob.surface, 0, 0); cairo_fill(cr); } static void create_surface() { glob.surface = cairo_image_surface_create_from_png("omen.png"); } static void destroy_surface() { cairo_surface_destroy(glob.surface); } int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); create_surface(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(G_OBJECT(window), "destroy",
This small example clearly shows the basic idea behind the mask. The mask determines where to paint and where not to paint.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0, 0, 0); cairo_mask_surface(cr, glob.surface, 0, 0); cairo_fill(cr); }
In the do_drawing() function, we use an image as a mask. And it is therefore displayed on the window.
static gboolean time_handler(GtkWidget *widget) { if (!glob.timer) return FALSE; gtk_widget_queue_draw(widget); return TRUE;
static void init_vars() { glob.timer = TRUE; glob.image = cairo_image_surface_create_from_png("beckov.png"); glob.img_width = cairo_image_surface_get_width(glob.image); glob.img_height = cairo_image_surface_get_height(glob.image); glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, glob.img_width, glob.img_height); } static void cleanup() { cairo_surface_destroy(glob.image); cairo_surface_destroy(glob.surface); } int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); init_vars(); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER(window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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), 325, 250); gtk_window_set_title(GTK_WINDOW(window), "Blind down"); g_timeout_add(15, (GSourceFunc) time_handler, (gpointer) window); gtk_widget_show_all(window); gtk_main(); cleanup(); } return 0;
The idea behind the blind down effect is simple. The image is h pixels high. We draw 0, 1, 2 ... lines of 1px height. Each cycle the portion of the image is 1px higher, until the whole image is visible.
struct { cairo_surface_t *image; cairo_surface_t *surface; gboolean timer; gint img_width; gint img_height;
In the glob structure, we will store two surfaces, a timer and the image width and height variables.
static void init_vars() { glob.timer = TRUE; glob.image = cairo_image_surface_create_from_png("beckov.png"); glob.img_width = cairo_image_surface_get_width(glob.image); glob.img_height = cairo_image_surface_get_height(glob.image); glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, glob.img_width, glob.img_height); }
In the init_vars() function, we initiate the previously declared variables. The last line creates an empty image surface. It is going to be filled with lines of pixels from the image surface, that we have created earlier.
ic = cairo_create(glob.surface);
We draw a rectangle into the initially empty image. The rectangle will be 1px higher each cycle. The image created this way will serve as a mask later.
h += 1;
We stop the timer function when we draw the whole image on the GTK window.
cairo_set_source_surface(cr, glob.image, 10, 10); cairo_mask_surface(cr, glob.surface, 10, 10);
The image of a castle is set as a source for painting. The cairo_mask_surface() paints the current source using the alpha channel of surface as a mask.
static void cleanup() { cairo_surface_destroy(glob.image); cairo_surface_destroy(glob.surface); }
In the cleanup() function we destroy the created surfaces. This chapter was about clipping and masking in Cairo.
Transformations
Home Contents
Transformations
In this part of the Cairo graphics programming tutorial, we will talk about transformations. An a ffine tra nsform is composed of zero or more linear transformations (rotation, scaling or shear) and translation (shift). Several linear transformations can be combined into a single matrix. A rota tion is a transformation that moves a rigid body around a fixed point. A sc a ling is a transformation that enlarges or diminishes objects. The scale factor is the same in all directions. A tra nsla tion is a transformation that moves every point a constant distance in a specified direction. A sh ea r is a transformation that moves an object perpendicular to a given axis, with greater value on one side of the axis than the other. sources: (wikipedia.org, freedictionary.com)
Translation
The following example describes a simple translation.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.2, 0.3, 0.8); cairo_rectangle(cr, 10, 10, 30, 30); cairo_fill(cr); cairo_translate(cr, 20, 20); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 0, 0, 30, 30); cairo_fill(cr); cairo_translate(cr, 30, 30); cairo_set_source_rgb(cr, 0.8, 0.8, 0.2); cairo_rectangle(cr, 0, 0, 30, 30); cairo_fill(cr); cairo_translate(cr, 40, 40); cairo_set_source_rgb(cr, 0.3, 0.8, 0.8); cairo_rectangle(cr, 0, 0, 30, 30); cairo_fill(cr);
The examle draws a rectangle. Then we do a translation and draw the same rectangle again.
cairo_translate(cr, 20, 20);
The cairo_translate() function modifies the current transormation matrix by tranlating the user space origin. In our case we shift the origin by 20 units in both directions.
Figure: Translation
Shear
In the following example, we perform a shearing operation. A shearing is an object distortion along
Transformations
a particular axis. There is no shear function for this operation. We need to create our own transformation matrix. Note that each affine transformation can be performed by creating a transformation matrix.
static void do_drawing(cairo_t *cr) { cairo_matrix_t matrix; cairo_set_source_rgb(cr, 0.6, 0.6, 0.6); cairo_rectangle(cr, 20, 30, 80, 50); cairo_fill(cr); cairo_matrix_init(&matrix, 1.0, 0.5, 0.0, 1.0, 0.0, 0.0); cairo_transform(cr, &matrix); cairo_rectangle(cr, 130, 30, 80, 50); cairo_fill(cr);
Figure: Shearing
Scaling
The next example demonstrates a scaling operation. Scaling is a transformation operation where the object is enlarged or shrinken.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.2, 0.3, 0.8); cairo_rectangle(cr, 10, 10, 90, 90); cairo_fill(cr); cairo_scale(cr, 0.6, 0.6); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr); cairo_scale(cr, 0.8, 0.8);
Transformations
cairo_set_source_rgb(cr, 0.8, 0.8, 0.2); cairo_rectangle(cr, 50, 50, 90, 90); cairo_fill(cr);
We draw three rectangles of 90x90px size. On two of them, we perform a scaling operation.
cairo_scale(cr, 0.6, 0.6); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr);
Here we perform another scaling operation by a factor of 0.8. If we look at the picture, we see, that the third yellow rectangle is the smallest one. Even if we have used a smaller scaling factor. This is because transformation operations are additive. In fact, the third rectangle was scaled by a factor of 0.528 (0.6x0.8).
Figure: Scaling
I solating transformations
Transformation operations are additive. To isolate one operation from the other one, we can use the cairo_save() and cairo_restore() functions. The cairo_save() function makes a copy of the current state of the drawing context and saves it on an internal stack of saved states. The
cairo_restore()
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.2, 0.3, 0.8); cairo_rectangle(cr, 10, 10, 90, 90); cairo_fill(cr); cairo_save(cr); cairo_scale(cr, 0.6, 0.6); cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr); cairo_restore(cr); cairo_save(cr); cairo_scale(cr, 0.8, 0.8); cairo_set_source_rgb(cr, 0.8, 0.8, 0.2); cairo_rectangle(cr, 50, 50, 90, 90); cairo_fill(cr); cairo_restore(cr);
In the example we scale two rectangles. This time we isolate the scaling operations from each other.
cairo_save(cr); cairo_scale(cr, 0.6, 0.6);
Transformations
cairo_set_source_rgb(cr, 0.8, 0.3, 0.2); cairo_rectangle(cr, 30, 30, 90, 90); cairo_fill(cr); cairo_restore(cr);
We isolate the scaling operation by putting the cairo_scale() function between the cairo_save() and cairo_restore() functions.
Figure: Isolating transformations Now the third yellow rectangle is bigger than the second red one.
Donut
In the following example we create an complex shape by rotating a bunch of ellipses.
#include <cairo.h> #include <gtk/gtk.h> #include <math.h> static void do_drawing(cairo_t *, GtkWidget *widget); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr, GtkWidget *widget) { GtkWidget *win = gtk_widget_get_toplevel(widget); gint width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); cairo_set_line_width(cr, 0.5); cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI); cairo_stroke(cr); gint i; for (i = 0; i < 36; i++) { cairo_save(cr); cairo_rotate(cr, i*M_PI/36); cairo_scale(cr, 0.3, 1); cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI); cairo_restore(cr); cairo_stroke(cr); }
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv);
Transformations
window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_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), 350, 250); gtk_window_set_title(GTK_WINDOW(window), "Donut"); gtk_widget_show_all(window); gtk_main(); } return 0;
We will do rotation and scaling operations. We will also save and restore Cairo contexts.
cairo_translate(cr, width/2, height/2); cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI); cairo_stroke(cr);
In the middle of the GTK+ window, we create a circle. This will be a bounding circle for our ellipses.
gint i; for (i = 0; i < 36; i++) { cairo_save(cr); cairo_rotate(cr, i*M_PI/36); cairo_scale(cr, 0.3, 1); cairo_arc(cr, 0, 0, 120, 0, 2 * M_PI); cairo_restore(cr); cairo_stroke(cr); }
We create 36 ellipses along the path of our bounding circle. We insulate each rotate and scale operation from one another with the cairo_save() and cairo_restore() methods.
Star
The next example shows a rotating and scaling star.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *, GtkWidget *widget); int points[11][2] = { { 0, 85 }, { 75, 75 }, { 100, 10 }, { 125, 75 }, { 200, 85 }, { 150, 125 }, { 160, 190 }, { 100, 150 }, { 40, 190 }, { 50, 125 }, { 0, 85 } }; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
Transformations
static void do_drawing(cairo_t *cr, GtkWidget *widget) { static gdouble angle = 0; static gdouble scale = 1; static gdouble delta = 0.01; GtkWidget *win = gtk_widget_get_toplevel(widget); gint width, height; gtk_window_get_size(GTK_WINDOW(win), &width, &height); cairo_set_source_rgb(cr, 0, 0.44, 0.7); cairo_set_line_width(cr, 1); cairo_translate(cr, width/2, height/2 ); cairo_rotate(cr, angle); cairo_scale(cr, scale, scale); gint i; for ( i = 0; i < 10; i++ ) { cairo_line_to(cr, points[i][0], points[i][1]); } cairo_close_path(cr); cairo_fill(cr); cairo_stroke(cr); if ( scale < 0.01 ) { delta = -delta; } else if (scale > 0.99) { delta = -delta; } scale += delta; angle += 0.01;
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 400, 300); gtk_window_set_title(GTK_WINDOW(window), "Star"); g_timeout_add(10, (GSourceFunc) time_handler, (gpointer) window); gtk_widget_show_all(window); gtk_main(); } return 0;
In this example, we create a star object. We will translate it, rotate it and scale it.
int points[11][2] = {
Transformations
{ 0, 85 }, { 75, 75 }, { 100, 10 },
...
We initialize three important variables. The angle is used in the rotation, the scale in scaling the star object. The delta variable controls when the star is growing and when it is shrinking.
cairo_translate(cr, width/2, height/2); cairo_rotate(cr, angle); cairo_scale(cr, scale, scale);
We shift the star into the middle of the window. Rotate it and scale it.
gint i; for ( i = 0; i < 10; i++ ) { cairo_line_to(cr, points[i][0], points[i][1]); } cairo_close_path(cr); cairo_fill(cr); cairo_stroke(cr);
These lines control the growing or shrinking of the star object. In this part of the Cairo graphics tutorial, we talked about transformations.
Text in Cairo
Home Contents
Text in Cairo
In this part of the Cairo graphics tutorial, we will work with text.
Soulmate
In the first example, we will display some lyrics on the GTK+ window.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgb(cr, 0.1, 0.1, 0.1); cairo_select_font_face(cr, "Purisa", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 13); cairo_move_to(cr, 20, 30); cairo_show_text(cr, "Most relationships seem so transitory"); cairo_move_to(cr, 20, 60); cairo_show_text(cr, "They're all good but not the permanent one"); cairo_move_to(cr, 20, 120); cairo_show_text(cr, "Who doesn't long for someone to hold"); cairo_move_to(cr, 20, 150); cairo_show_text(cr, "Who knows how to love you without being told"); cairo_move_to(cr, 20, 180); cairo_show_text(cr, "Somebody tell me why I'm on my own"); cairo_move_to(cr, 20, 210); cairo_show_text(cr, "If there's a soulmate for everyone");
In this example, we display part of the lyrics from the Natasha Bedingfield's Soulmate song.
cairo_select_font_face(cr, "Purisa", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
Here we select the font face. The function takes three parameters, the font family, font slant and the font weight.
cairo_set_font_size(cr, 13);
We display the text on the window by specifying the position of the text and calling the
cairo_show_text()
function.
Text in Cairo
Figure: Soulmate
Centered text
Next we will show, how to center text on the window.
static void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_text_extents_t extents; GtkWidget *win = gtk_widget_get_toplevel(widget); gint w, h; gtk_window_get_size(GTK_WINDOW(win), &w, &h); cairo_select_font_face(cr, "Courier", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 60); cairo_text_extents(cr, "ZetCode", &extents); cairo_move_to(cr, w/2 - extents.width/2, h/2); cairo_show_text(cr, "ZetCode");
The code will center a text on the window. It remains centered, even if we resize the window.
GtkWidget *win = gtk_widget_get_toplevel(widget); gint w, h; gtk_window_get_size(GTK_WINDOW(win), &w, &h);
To center a text on the window, it is necessary to get the size of of the parent window.
cairo_select_font_face(cr, "Courier", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 60);
We get the text extents. These are some numbers that describe the text. We need the width of the text for our example.
cairo_move_to(cr, w/2 - extents.width/2, h/2); cairo_show_text(cr, "ZetCode");
We position the text into the middle of the window and show it using the cairo_show_text() method.
Text in Cairo
Shaded text
Now we will show a shaded text on the window.
static void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_select_font_face(cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 50); cairo_set_source_rgb(cr, 0, 0, 0); cairo_move_to(cr, 40, 60); cairo_show_text(cr, "ZetCode"); cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); cairo_move_to(cr, 43, 63); cairo_show_text(cr, "ZetCode");
To create a shade, we draw the text twice. In different colours. The second text is moved a bit to the right and bottom.
cairo_set_source_rgb(cr, 0, 0, 0); cairo_move_to(cr, 40, 60); cairo_show_text(cr, "ZetCode");
The second text is drawn in some gray ink. It is moved by 3px to the right and to the bottom.
Text in Cairo
static void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_pattern_t *pat; cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); cairo_paint(cr); gint h = 90; cairo_select_font_face(cr, "Serif", CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, h); pat = cairo_pattern_create_linear(0, 15, 0, h*0.8); cairo_pattern_set_extend(pat, CAIRO_EXTEND_REPEAT); cairo_pattern_add_color_stop_rgb(pat, 0.0, 1, 0.6, 0); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1, 0.3, 0); cairo_move_to(cr, 15, 80); cairo_text_path(cr, "ZetCode"); cairo_set_source(cr, pat); cairo_fill(cr);
We draw a text on the window filled with a linear gradient. The colours are some orange colours.
cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); cairo_paint(cr);
To make it more visually appealing, we paint the background in dark gray colour.
pat = cairo_pattern_create_linear(0, 15, 0, h*0.8); cairo_pattern_set_extend(pat, CAIRO_EXTEND_REPEAT); cairo_pattern_add_color_stop_rgb(pat, 0.0, 1, 0.6, 0); cairo_pattern_add_color_stop_rgb(pat, 0.5, 1, 0.3, 0);
The text is displayed on the window. We use the gradient as a source for painting.
Glyphs
The cairo_show_text() method is only suitable for simple text rendering. Cairo developers call it a toy method. More professional text rendering is done with glyphs. A glyp h is a graphic symbol which provides a form for a character. A character provides a meaning. It can have multiple glyphs. A character has no intrinsic appearance. A glyph has no intrinsic meaning. Note that many common programming requirements conserning text are addressed by the Pango library.
static void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_select_font_face(cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
Text in Cairo
cairo_set_font_size(cr, 13); const int n_glyphs = 20 * 35; cairo_glyph_t glyphs[n_glyphs]; gint i = 0; gint x, y; for (y=0; y<20; y++) { for (x=0; x<35; x++) { glyphs[i] = (cairo_glyph_t) {i, x*15 + 20, y*18 + 20}; i++; } } } cairo_show_glyphs(cr, glyphs, n_glyphs);
The glyphs array will store three integer values. The first value is the index of the glyph to the chosen font type. The second and the third values are x, y positions of a glyph.
cairo_show_glyphs(cr, glyphs, n_glyphs);
The cairo_show_glyphs() method shows the glyphs on the window. This chapter covered text in Cairo.
Images in Cairo
Home Contents
I mages in Cairo
In this part of the Cairo graphics tutorial, we will talk about the images. We will show how to display an image on the GTK window. We will also create some effects with images.
Displaying an image
In the first example, we will display an image.
#include <cairo.h> #include <gtk/gtk.h> struct { cairo_surface_t *image; } glob; static void do_drawing(cairo_t *); static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_surface(cr, glob.image, 10, 10); cairo_paint(cr); } int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; glob.image = cairo_image_surface_create_from_png("stmichaelschurch.png"); gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 300, 220); gtk_window_set_title(GTK_WINDOW(window), "Image"); gtk_widget_show_all(window); gtk_main(); cairo_surface_destroy(glob.image); } return 0;
Images in Cairo
glob.image = cairo_image_surface_create_from_png("stmichaelschurch.png");
We create an image surface from a png image. For efficiency reasons, the function is called in the main function.
cairo_set_source_surface(cr, glob.image, 10, 10);
W atermark
It is common to draw information on images. The text written on an image is called a watermark. Watermarks are used to identify images. They could be copyright notices or image creation times.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *, GtkWidget *widget); struct { cairo_surface_t *image; } glob; static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr, widget); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr, GtkWidget *widget) { cairo_set_source_surface(cr, glob.image, 10, 10); cairo_paint(cr); } static void load_image() { glob.image = cairo_image_surface_create_from_png("beckov.png"); } static void draw_mark() { cairo_t *ic; ic = cairo_create(glob.image); cairo_set_font_size(ic, 11); cairo_set_source_rgb(ic, 0.9 , 0.9 , 0.9); cairo_move_to(ic, 20, 30); cairo_show_text(ic, " Beckov 2012 , (c) Jan Bodnar "); cairo_stroke(ic);
int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; load_image(); draw_mark(); gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
Images in Cairo
darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 350, 250); gtk_window_set_title(GTK_WINDOW(window), "Watermark"); gtk_widget_show_all(window); gtk_main(); cairo_surface_destroy(glob.image); } return 0;
In the draw_mark() funciton, we draw the copyright message on the image. First we create a drawing context from the image surface.
cairo_set_font_size(ic, 11); cairo_set_source_rgb(ic, 0.9 , 0.9 , 0.9); cairo_move_to(ic, 20, 30); cairo_show_text(ic, " Beckov 2012 , (c) Jan Bodnar "); cairo_stroke(ic);
Images in Cairo
gint img_width; gint img_height; } glob; static void init_vars() { glob.image = cairo_image_surface_create_from_png("beckov.png"); glob.img_width = cairo_image_surface_get_width(glob.image); glob.img_height = cairo_image_surface_get_height(glob.image); glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, glob.img_width, glob.img_height); glob.timer = TRUE;
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_t *ic; static gint count = 0; ic = cairo_create(glob.surface); gint i, j; for (i = 0; i <= glob.img_height; i+=7) { for (j = 0 ; j < count; j++) { cairo_move_to(ic, 0, i+j); cairo_line_to(ic, glob.img_width, i+j); } } count++; if (count == 8) glob.timer = FALSE; cairo_set_source_surface(cr, glob.image, 10, 10); cairo_mask_surface(cr, glob.surface, 10, 10); cairo_stroke(ic); } cairo_destroy(ic);
static gboolean time_handler(GtkWidget *widget) { if (!glob.timer) return FALSE; gtk_widget_queue_draw(widget); return TRUE;
int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; init_vars(); gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
Images in Cairo
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); gtk_window_set_default_size(GTK_WINDOW(window), 325, 250); gtk_window_set_title(GTK_WINDOW(window), "Spectrum"); g_timeout_add(400, (GSourceFunc) time_handler, (gpointer) window); gtk_widget_show_all(window); gtk_main(); cairo_surface_destroy(glob.image); cairo_surface_destroy(glob.surface); } return 0;
We divide the image into n parts consisting of 8 lines. Each cycle each part of the image will get bigger by one pixel. The created image will serve as a mask for displaying the image of the castle.
struct { gboolean timer; cairo_surface_t *image; cairo_surface_t *surface; gint img_width; gint img_height; } glob;
The glob structure stores variables that are used within more functions.
static void init_vars() { glob.image = cairo_image_surface_create_from_png("beckov.png"); glob.img_width = cairo_image_surface_get_width(glob.image); glob.img_height = cairo_image_surface_get_height(glob.image); glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, glob.img_width, glob.img_height); glob.timer = TRUE;
Using the mask operation, we draw the portions of the image on the window. This chapter covered images in Cairo.
Home Contents
Root w indow
In this part of the Cairo graphics tutorial, we will work with the root window. The root window is the desktop window where we usually have icon shortcuts. It is possible to manipulate with the root window. From the programmer's perspective, it is just a special kind of a window.
Transparent w indow
Our first example will create a transparent window. We will see, what it beneath of the window object.
#include <cairo.h> #include <gtk/gtk.h> static void do_drawing(cairo_t *); static void tran_setup(GtkWidget *win) { GdkScreen *screen; GdkVisual *visual; gtk_widget_set_app_paintable(win, TRUE); screen = gdk_screen_get_default(); visual = gdk_screen_get_rgba_visual(screen); if (visual != NULL && gdk_screen_is_composited(screen)) { gtk_widget_set_visual(win, visual); }
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) { cr = gdk_cairo_create(gtk_widget_get_window(widget)); do_drawing(cr); cairo_destroy(cr); } return FALSE;
static void do_drawing(cairo_t *cr) { cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.4); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *darea; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); tran_setup(window); darea = gtk_drawing_area_new(); gtk_container_add(GTK_CONTAINER (window), darea); g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 300, 250);
To create a transparent window, we get the visual of the screen object and set it for our window. In the on_draw() method, we draw over the screen's visual object. This createas an illusion of partial transparency.
gtk_widget_set_app_paintable(win, TRUE);
From the screen window, we get its visual. The visual contains the low level display information.
if (visual != NULL && gdk_screen_is_composited(screen)) { gtk_widget_set_visual(win, visual); }
Not all displays support this operation. Therefore, we check if our screen supports composition and the returned visual is not None. We set the screen's visual to be the visual of our window.
static void do_drawing(cairo_t *cr) { cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 0.4); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_paint(cr); }
We use a partially transparent source to draw over the screen window. The
CAIRO_OPERATOR_SOURCE CAIRO_OPERATOR_CLEAR
the screen window. To get full transparency, we set the alpha value to 0 or use the operator.
Taking a screenshot
The root window is also essential in taking a screenshot.
#include <cairo.h> #include <gdk/gdk.h> int main (int argc, char *argv[])
The example captures a snapshot of the entire screen. In this example, we do not use the full GTK windowing system. We use Cairo and GDK libraries to do the job.
gdk_init(&argc, &argv);
The gdk_init() initializes the GDK library and connects to the windowing system.
GdkWindow *root_win = gdk_get_default_root_window();
An empty image surface is created. It has the size of the root window.
GdkPixbuf *pb = gdk_pixbuf_get_from_window(root_win, 0, 0, width, height);
We get a pixbuf from the root window using the gdk_pixbuf_get_from_window() function call. A pixbuf is an object that describes an image in memory.
cairo_t *cr = cairo_create(surface); gdk_cairo_set_source_pixbuf(cr, pb, 0, 0); cairo_paint(cr);
In the above code lines, we create a Cairo drawing context on the image surface that we have created earlier. We place the pixbuf on the drawing context and paint it on the surface.
cairo_surface_write_to_png(surface, "image.png");
The image surface is written to a PNG image using the write_to_png() method.
cairo_destroy(cr); cairo_surface_destroy(surface);
We clean up resources.
static void do_drawing(cairo_t *cr) { cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); cairo_paint(cr); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); } static void setup(GtkWidget *win) { gtk_widget_set_app_paintable(win, TRUE); gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DOCK); gtk_window_set_keep_below(GTK_WINDOW(win), TRUE); GdkScreen *screen = gdk_screen_get_default(); GdkVisual *visual = gdk_screen_get_rgba_visual(screen); if (visual != NULL && gdk_screen_is_composited(screen)) { gtk_widget_set_visual(win, visual); }
int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *lbl; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); setup(window); lbl = gtk_label_new("ZetCode, tutorials for programmers"); PangoFontDescription *fd = pango_font_description_from_string("Serif 20"); gtk_widget_modify_font(lbl, fd); gtk_container_add(GTK_CONTAINER(window), lbl); GdkColor color; gdk_color_parse("white", &color); gtk_widget_modify_fg(lbl, GTK_STATE_NORMAL, &color); g_signal_connect(G_OBJECT(window), "draw", G_CALLBACK(on_draw_event), NULL); g_signal_connect(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), 350, 250); gtk_widget_show_all(window); gtk_main(); } return 0;
We use the CAIRO_OPERATOR_CLEAR operator to clear the background of the window. Then we set the CAIRO_OPERATOR_OVER to let the label widget be drawn.
gtk_widget_set_app_paintable(win, TRUE);
We keep the application always at the bottom, just over the root window.
GdkScreen *screen = gdk_screen_get_default(); GdkVisual *visual = gdk_screen_get_rgba_visual(screen); if (visual != NULL && gdk_screen_is_composited(screen)) { gtk_widget_set_visual(win, visual); }
With the help of the Pango module, we select a specific font for the text.
gtk_container_add(GTK_CONTAINER(window), lbl);
Figure: Message on the root window In this chapter we have worked with the desktop window in Cairo.