4. Basic drawing

In this part of the Cairo graphics tutorial, we will draw some basic primitives. We will draw simple lines, use fill and stroke operations, we will talk about dashes, line caps and line joins.

Lines

Lines a 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.

drawLINE.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
#include <cairo.h>
#include <gtk/gtk.h> /*include the necessary Cairo and GTK headers */

double coordx[100];
double coordy[100];

int count = 0;

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

gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data);

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

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(window, "expose-event", G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
  
  /*connect the button-press-event to the clicked callback */
  g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "lines");
  gtk_window_set_default_size(GTK_WINDOW(window), 600, 480);
  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;
  cr = gdk_cairo_create(widget->window);

  /* The lines will be drawn in black ink and will be 2.0 points wide */
  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_set_line_width(cr, 2.0);

  /* connect every point from the array to every other point */
  int i, j;
  for(i = 0; i <= count - 1; i++){
    for(j = 0; j <= count - 1; j++){
      cairo_move_to(cr, coordx[i], coordy[i]);
      cairo_line_to(cr, coordx[j], coordy[j]);
      
    }
  }

  count = 0;
  cairo_stroke(cr); /* the cairo_stroke() call draws the lines */
  cairo_destroy(cr);

  return FALSE;
}

gboolean clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data){
  /* when click with a left mouse button, we store the (x, y) coordinates into the arrays */
  if(event->button == 1){
    printf("button 1 pressed\n");
    coordx[count] = event->x;
    coordy[count++] = event->y;
  }

  if(event->button == 3){
    printf("button 3 pressed\n");
    gtk_widget_queue_draw(widget); /* by right clicking, redraw the window */
  }

  return TRUE;
}

drawLINE.c の実行結果は:

_images/drawline15.png

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.

Fill and stroke

The stroke operation draws the outlines of shapes and the fill operation fills the insides of shapes.

fillstroke.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
#include <cairo.h>
#include <gtk/gtk.h> /*include the necessary Cairo and GTK headers */
#include <math.h> /* this header file is needed for the M_PI constant */

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);

  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  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_title(GTK_WINDOW(window), "fillstroke");
  gtk_window_set_default_size(GTK_WINDOW(window), 600, 480);
  
  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;
  cr = gdk_cairo_create(widget->window);

  /* get the width and height of the window */
  int width, height;
  gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
  
  cairo_set_line_width(cr, 16);

  /* draw the outline of a circle */
  cairo_set_source_rgb(cr, 255, 255, 255);
  cairo_arc(cr, width/2, height/2, (width < height ? width: height)/2-10, 0, 2*M_PI);
  cairo_stroke_preserve(cr);

  /* fill the circle with green color */
  cairo_set_source_rgb(cr, 0, 255, 0);
  cairo_fill(cr);
  
  cairo_destroy(cr);
  
  return FALSE;
}

fillstroke.c の実行結果は:

_images/fillstroke5.png

Dash

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 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 symmectric pattern assumed. The offset value specifies, where the dashing begins. Or in other words, it is an empty space at the beginning.

dash.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
#include <cairo.h>
#include <gtk/gtk.h>

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

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

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  /*
    In this exmaple, we do not draw directly on the window but on the drawing area.
  */
  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER(window), darea);

  g_signal_connect(darea, "expose-event", G_CALLBACK(on_expose_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), 600, 480);

  gtk_widget_show_all(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);

  cairo_set_source_rgba(cr, 0, 0, 255, 1);

  /*
    Here we specify our first dash pattern.
    It will draw a pattern of 40.0 pixels and 10.0 empty points.
  */
  static const double dashed1[] = {40.0, 10.0};
  
  /* get the size of the array */
  static int len1 = sizeof(dashed1) / sizeof(dashed1[0]);

  static const double dashed2[] = {20.0, 40.0};
  static int len2 = sizeof(dashed2) / sizeof(dashed2[0]);

  static const double dashed3[] = {1.0};

  /* ラインの幅 */
  cairo_set_line_width(cr, 1.5);

  /* the first line */
  cairo_set_dash(cr, dashed1, len1, 0);
  cairo_move_to(cr, 40, 50);
  cairo_line_to(cr, 560, 50);
  cairo_stroke(cr);

  /* the second line */
  cairo_set_dash(cr, dashed2, len2, 2);
  cairo_move_to(cr, 40, 80);
  cairo_line_to(cr, 560, 80);
  cairo_stroke(cr);

  /* the third line */
  cairo_set_dash(cr, dashed3, 1, 0);
  cairo_move_to(cr, 40, 110);
  cairo_line_to(cr, 560, 110);
  cairo_stroke(cr);

  /* the fourth line */
  static const double dashed4[] = {5.0, 100.0};
  static int len4 = sizeof(dashed4) / sizeof(dashed4[0]);
  cairo_set_dash(cr, dashed4, len4, 0);
  cairo_move_to(cr, 40, 140);
  cairo_line_to(cr, 560, 140);
  cairo_stroke(cr);

  cairo_destroy(cr);

  return FALSE;
}

dash.c の実行結果は:

_images/dash3.png

Line caps

There are three endpoints of lines:

1. CAIRO_LINE_CAP_SQUARE
2. CAIRO_LINE_CAP_ROUND
3. CAIRO_LINE_CAP_BUTT

Square, round and butt caps

_images/linecaps3.png

A line with a CAIRO_LINE_CAP_SQUARE cap will have a different size, than a line with a CAIRO_LINE_CAP_BUTT cap.

If a line is width px wide, the line with a CAIRO_LINE_CAP_SQUARE cap will be exactly width px greater in size. width/2 px at the beginning and width/2 px at the end.

linecap.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
#include <cairo.h>
#include <gtk/gtk.h>

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

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

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  /*
    In this exmaple, we do not draw directly on the window but on the drawing area.
  */
  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER(window), darea);

  g_signal_connect(darea, "expose-event", G_CALLBACK(on_expose_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), 600, 480);

  gtk_widget_show_all(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);

  cairo_set_source_rgba(cr, 0, 0, 255, 1);
  cairo_set_line_width(cr, 10); /* our lines will be 10 px wide */

  /* the first cap */
  cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
  cairo_move_to(cr, 30, 50);
  cairo_line_to(cr, 150, 50); /* here we draw a horizontal line */
  cairo_stroke(cr);

  /* the second cap */
  cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
  cairo_move_to(cr, 30, 100);
  cairo_line_to(cr, 150, 100);
  cairo_stroke(cr);

  /* the third cap */
  cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
  cairo_move_to(cr, 30, 150);
  cairo_line_to(cr, 150, 150);
  cairo_stroke(cr);

  cairo_set_line_width(cr, 1.5);

  cairo_move_to(cr, 30, 40);
  cairo_line_to(cr, 30, 340);
  cairo_stroke(cr);

  cairo_move_to(cr, 150, 40);
  cairo_line_to(cr, 150, 340);
  cairo_stroke(cr);

  cairo_move_to(cr, 155, 40);
  cairo_line_to(cr, 155, 340);
  cairo_stroke(cr);
  
  cairo_destroy(cr);

  return FALSE;
}

linecap.c の実行結果は:

_images/linecap2.png

Line joins

The lines can be joined using three different join styles:

1. CAIRO_LINE_JOIN_BEVEL
2. CAIRO_LINE_JOIN_ROUND
3. CAIRO_LINE_JOIN_MITER
_images/join1.jpg

linejoins.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
#include <cairo.h>
#include <gtk/gtk.h>

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

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

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  /*
    In this exmaple, we do not draw directly on the window but on the drawing area.
  */
  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER(window), darea);

  g_signal_connect(darea, "expose-event", G_CALLBACK(on_expose_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), 600, 480);

  gtk_widget_show_all(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);

  cairo_set_source_rgb(cr, 0.1, 0, 0);

  /* the first rectangle */
  cairo_rectangle(cr, 30, 30, 50, 50);
  cairo_set_line_width(cr, 14);
  cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL);
  cairo_stroke(cr);

  /* the second rectangle */
  cairo_rectangle(cr, 160, 30, 100, 100);
  cairo_set_line_width(cr, 14);
  cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
  cairo_stroke(cr);

  /* the third rectangle */
  cairo_rectangle(cr, 100, 160, 200, 200);
  cairo_set_line_width(cr, 14);
  cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
  cairo_stroke(cr);

  /* the fourth rectangle */
  cairo_rectangle(cr, 0, 0, 600, 480);
  cairo_set_line_width(cr, 14);
  cairo_set_line_join(cr, CAIRO_LINE_JOIN_MITER);
  cairo_stroke(cr);

  cairo_destroy(cr);

  return FALSE;
}

linejoins.c の実行結果は:

_images/linejoins1.png

Table Of Contents

Previous topic

3. Cairo backends

Next topic

5. Shapes and fills