Cairo Graphics is a powerful 2D graphics library. Although Cairo is a C library, there are bindings available for many languages. The RCairo gem allows Ruby programs to use Cairo. This page has the simple samples from the Cairographics website, ported to run under Ruby using the RCairo gem.
The assumption is that you were able to install RCairo on Windows and are ready to get started. I am taking reference from the samples on the CairoGraphics website for the code; I have made minimal changes to the code. As mentioned on that page, the original snippets were created by Øyvind Kolås for a paper submitted to GUADEC 2004. All of his original snippet code is considered to be part of the public domain. All the samples here can similarly be used for any purpose.
All the ported samples are available on https://github.com/mohits/rcairo_samples in case you want to grab them. Based on what I learnt while writing this, I also wrote up a related post about “porting Cairo code from C to Ruby”::/2021/cairo-rcairo-porting/ which you might also find useful.
OK, let’s get started!
Arc
The first sample draws an arc. The C code is shown first, and then the Ruby code follows it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
double xc = 128.0;
double yc = 128.0;
double radius = 100.0;
double angle1 = 45.0 * (M_PI/180.0); /* angles are specified */
double angle2 = 180.0 * (M_PI/180.0); /* in radians */
cairo_set_line_width (cr, 10.0);
cairo_arc (cr, xc, yc, radius, angle1, angle2);
cairo_stroke (cr);
/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);
cairo_arc (cr, xc, yc, 10.0, 0, 2*M_PI);
cairo_fill (cr);
cairo_arc (cr, xc, yc, radius, angle1, angle1);
cairo_line_to (cr, xc, yc);
cairo_arc (cr, xc, yc, radius, angle2, angle2);
cairo_line_to (cr, xc, yc);
cairo_stroke (cr);
The code below does the same but has some additions for requiring cairo, creating an image surface, and saving the PNG to a file. This follows exactly the C code style and there is a slightly more idiomatic way to use it in Ruby but here, I have focused on keeping it closest to the samples.
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
require 'cairo'
# Set up the parameters
xc = 128.0
yc = 128.0
radius = 100.0
angle1 = 45.0 * (Math::PI/180.0) # angles are specified
angle2 = 180.0 * (Math::PI/180.0) # in radians
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main arc
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(10.0)
cr.arc(xc, yc, radius, angle1, angle2)
cr.stroke
# Draw helping lines
cr.set_source_rgba(1, 0.2, 0.2, 0.6);
cr.set_line_width(6.0);
# First, the circle at the centre
cr.arc(xc, yc, 10.0, 0, 2*Math::PI);
cr.fill;
# Then, the lines reaching out
cr.arc(xc, yc, radius, angle1, angle1);
cr.line_to(xc, yc);
cr.arc(xc, yc, radius, angle2, angle2);
cr.line_to(xc, yc);
cr.stroke;
# Save the image surface to a PNG file
surface.write_to_png("cairo-arc.png")
The resulting output is as below.
You will see that the code is organised into an object-oriented fashion but the actual drawing code is almost the same.
Arc Negative
The code below is for the arc negative sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
double xc = 128.0;
double yc = 128.0;
double radius = 100.0;
double angle1 = 45.0 * (M_PI/180.0); /* angles are specified */
double angle2 = 180.0 * (M_PI/180.0); /* in radians */
cairo_set_line_width (cr, 10.0);
cairo_arc_negative (cr, xc, yc, radius, angle1, angle2);
cairo_stroke (cr);
/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);
cairo_arc (cr, xc, yc, 10.0, 0, 2*M_PI);
cairo_fill (cr);
cairo_arc (cr, xc, yc, radius, angle1, angle1);
cairo_line_to (cr, xc, yc);
cairo_arc (cr, xc, yc, radius, angle2, angle2);
cairo_line_to (cr, xc, yc);
cairo_stroke (cr);
The equivalent Ruby code is very similar.
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
require 'cairo'
# Set up the parameters
xc = 128.0
yc = 128.0
radius = 100.0
angle1 = 45.0 * (Math::PI/180.0) # angles are specified
angle2 = 180.0 * (Math::PI/180.0) # in radians
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(1, 1, 1)
cr.paint
# The main negative arc
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(10.0)
cr.arc_negative(xc, yc, radius, angle1, angle2)
cr.stroke
# Draw helping lines
cr.set_source_rgba(1, 0.2, 0.2, 0.6);
cr.set_line_width(6.0);
# First, the circle at the centre
cr.arc(xc, yc, 10.0, 0, 2*Math::PI);
cr.fill;
# Then, the lines reaching out
cr.arc(xc, yc, radius, angle1, angle1);
cr.line_to(xc, yc);
cr.arc(xc, yc, radius, angle2, angle2);
cr.line_to(xc, yc);
cr.stroke;
# Save the image surface to a PNG file
surface.write_to_png("cairo-arc-negative.png")
The resulting image is shown below.
Clip
The code below is for the clip sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cairo_arc (cr, 128.0, 128.0, 76.8, 0, 2 * M_PI);
cairo_clip (cr);
cairo_new_path (cr); /* current path is not
consumed by cairo_clip() */
cairo_rectangle (cr, 0, 0, 256, 256);
cairo_fill (cr);
cairo_set_source_rgb (cr, 0, 1, 0);
cairo_move_to (cr, 0, 0);
cairo_line_to (cr, 256, 256);
cairo_move_to (cr, 256, 0);
cairo_line_to (cr, 0, 256);
cairo_set_line_width (cr, 10.0);
cairo_stroke (cr);
The equivalent Ruby code is very similar.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(1, 1, 1)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.arc(128.0, 128.0, 76.8, 0, 2 * Math::PI)
cr.clip
cr.new_path # current path is not by clip
cr.rectangle(0, 0, 256, 256)
cr.fill
cr.set_source_rgb(0, 1, 0)
cr.move_to(0, 0)
cr.line_to(256, 256)
cr.move_to(256, 0)
cr.line_to(0, 256)
cr.set_line_width(10.0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-clip.png")
The resulting image is shown below.
Clip Image
Next up, is the clip image sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int w, h;
cairo_surface_t *image;
cairo_arc (cr, 128.0, 128.0, 76.8, 0, 2*M_PI);
cairo_clip (cr);
cairo_new_path (cr); /* path not consumed by clip()*/
image = cairo_image_surface_create_from_png ("data/romedalen.png");
w = cairo_image_surface_get_width (image);
h = cairo_image_surface_get_height (image);
cairo_scale (cr, 256.0/w, 256.0/h);
cairo_set_source_surface (cr, image, 0, 0);
cairo_paint (cr);
cairo_surface_destroy (image);
This needed a few more changes to work:
- set_source_surface → set_source(image)
- cairo_image_surface_create_from_png (image_path) → Cairo::ImageSurface.from_png
- cairo_image_surface_get_height → [surface_variable].height
- cairo_image_surface_get_width → [surface_variable].width
- cairo_surface_destroy is not needed
The equivalent Ruby code is below. I didn’t have the original image, so I got a different one and you can get the resized and converted version from this page
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
cr.arc(128.0, 128.0, 76.8, 0, 2*Math::PI)
cr.clip
cr.new_path # path not consumed by clip
# Source image is from:
# - https://www.publicdomainpictures.net/en/view-image.php?image=7683&picture=breaking-blue-wave
# Converted to PNG before using it
image = Cairo::ImageSurface.from_png("breaking-blue-wave.png")
w = image.width
h = image.height
cr.scale(256.0/w, 256.0/h)
cr.set_source(image, 0, 0)
cr.paint
# Save the image surface to a PNG file
surface.write_to_png("cairo-clip-image.png")
The resulting image is shown below.
Curve to
The following code is from the curve to sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
double x=25.6, y=128.0;
double x1=102.4, y1=230.4,
x2=153.6, y2=25.6,
x3=230.4, y3=128.0;
cairo_move_to (cr, x, y);
cairo_curve_to (cr, x1, y1, x2, y2, x3, y3);
cairo_set_line_width (cr, 10.0);
cairo_stroke (cr);
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);
cairo_move_to (cr,x,y); cairo_line_to (cr,x1,y1);
cairo_move_to (cr,x2,y2); cairo_line_to (cr,x3,y3);
cairo_stroke (cr);
This is simpler and maps more easily to the code below.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
x=25.6
y=128.0
x1=102.4
y1=230.4
x2=153.6
y2=25.6
x3=230.4
y3=128.0
cr.set_source_rgb(0, 0, 0)
cr.move_to(x, y)
cr.curve_to(x1, y1, x2, y2, x3, y3)
cr.set_line_width(10.0)
cr.stroke
cr.set_source_rgba( 1, 0.2, 0.2, 0.6)
cr.set_line_width( 6.0)
cr.move_to(x,y)
cr.line_to(x1,y1)
cr.move_to(x2,y2)
cr.line_to(x3,y3)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-curve-to.png")
The resulting image is shown below.
Dashes
The following code is from the dash sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
double dashes[] = {50.0, /* ink */
10.0, /* skip */
10.0, /* ink */
10.0 /* skip*/
};
int ndash = sizeof (dashes)/sizeof(dashes[0]);
double offset = -50.0;
cairo_set_dash (cr, dashes, ndash, offset);
cairo_set_line_width (cr, 10.0);
cairo_move_to (cr, 128.0, 25.6);
cairo_line_to (cr, 230.4, 230.4);
cairo_rel_line_to (cr, -102.4, 0.0);
cairo_curve_to (cr, 51.2, 230.4, 51.2, 128.0, 128.0, 128.0);
cairo_stroke (cr);
This is very similar – you just don’t need the size as a parameter to set_dash.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
dashes = [ 50.0, # ink
10.0, # skip
10.0, # ink
10.0 # skip
]
offset = -50.0
cr.set_source_rgb(0, 0, 0)
cr.set_dash( dashes, offset) # does not need the size
cr.set_line_width( 10.0)
cr.move_to( 128.0, 25.6)
cr.line_to( 230.4, 230.4)
cr.rel_line_to( -102.4, 0.0)
cr.curve_to( 51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-dash.png")
The resulting image is shown below.
Fill and Stroke 2
The next example is the fill and stroke2 sample.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cairo_move_to (cr, 128.0, 25.6);
cairo_line_to (cr, 230.4, 230.4);
cairo_rel_line_to (cr, -102.4, 0.0);
cairo_curve_to (cr, 51.2, 230.4, 51.2, 128.0, 128.0, 128.0);
cairo_close_path (cr);
cairo_move_to (cr, 64.0, 25.6);
cairo_rel_line_to (cr, 51.2, 51.2);
cairo_rel_line_to (cr, -51.2, 51.2);
cairo_rel_line_to (cr, -51.2, -51.2);
cairo_close_path (cr);
cairo_set_line_width (cr, 10.0);
cairo_set_source_rgb (cr, 0, 0, 1);
cairo_fill_preserve (cr);
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_stroke (cr);
This is very similar and easy to get running!
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.move_to(128.0, 25.6)
cr.line_to(230.4, 230.4)
cr.rel_line_to(-102.4, 0.0)
cr.curve_to(51.2, 230.4, 51.2, 128.0, 128.0, 128.0)
cr.close_path
cr.move_to(64.0, 25.6)
cr.rel_line_to(51.2, 51.2)
cr.rel_line_to(-51.2, 51.2)
cr.rel_line_to(-51.2, -51.2)
cr.close_path
cr.set_line_width(10.0)
cr.set_source_rgb(0, 0, 1)
cr.fill_preserve
cr.set_source_rgb(0, 0, 0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-fill-stroke2.png")
The resulting image is shown below.
Fill Style
The fill style sample is easy to convert.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cairo_set_line_width (cr, 6);
cairo_rectangle (cr, 12, 12, 232, 70);
cairo_new_sub_path (cr); cairo_arc (cr, 64, 64, 40, 0, 2*M_PI);
cairo_new_sub_path (cr); cairo_arc_negative (cr, 192, 64, 40, 0, -2*M_PI);
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
cairo_set_source_rgb (cr, 0, 0.7, 0); cairo_fill_preserve (cr);
cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr);
cairo_translate (cr, 0, 128);
cairo_rectangle (cr, 12, 12, 232, 70);
cairo_new_sub_path (cr); cairo_arc (cr, 64, 64, 40, 0, 2*M_PI);
cairo_new_sub_path (cr); cairo_arc_negative (cr, 192, 64, 40, 0, -2*M_PI);
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
cairo_set_source_rgb (cr, 0, 0, 0.9); cairo_fill_preserve (cr);
cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr);
This is easy to get running. Just remember that constants like CAIRO_FILL_RULE_EVEN_ODD
become namespaced as Cairo::FILL_RULE_EVEN_ODD
but other than that, there is no trouble in getting the sample working.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.set_line_width( 6)
cr.rectangle( 12, 12, 232, 70)
cr.new_sub_path
cr.arc( 64, 64, 40, 0, 2*Math::PI)
cr.new_sub_path
cr.arc_negative( 192, 64, 40, 0, -2*Math::PI)
cr.set_fill_rule(Cairo::FILL_RULE_EVEN_ODD)
cr.set_source_rgb( 0, 0.7, 0)
cr.fill_preserve
cr.set_source_rgb( 0, 0, 0)
cr.stroke
cr.translate( 0, 128)
cr.rectangle( 12, 12, 232, 70)
cr.new_sub_path
cr.arc( 64, 64, 40, 0, 2*Math::PI)
cr.new_sub_path
cr.arc_negative( 192, 64, 40, 0, -2*Math::PI)
cr.set_fill_rule(Cairo::FILL_RULE_WINDING)
cr.set_source_rgb( 0, 0, 0.9)
cr.fill_preserve
cr.set_source_rgb( 0, 0, 0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-fill-style.png")
The resulting image is shown below.
Gradient
The gradient sample needs a few small changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cairo_pattern_t *pat;
pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0);
cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 0, 1);
cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 1);
cairo_rectangle (cr, 0, 0, 256, 256);
cairo_set_source (cr, pat);
cairo_fill (cr);
cairo_pattern_destroy (pat);
pat = cairo_pattern_create_radial (115.2, 102.4, 25.6,
102.4, 102.4, 128.0);
cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 1);
cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 0, 1);
cairo_set_source (cr, pat);
cairo_arc (cr, 128.0, 128.0, 76.8, 0, 2 * M_PI);
cairo_fill (cr);
cairo_pattern_destroy (pat);
There are a few small differences in this example:
- You need to create patterns using different constructors, such as:
- pat = Cairo::LinearPattern.new(0.0, 0.0, 0.0, 256.0)
- pat = Cairo::RadialPattern.new(115.2,102.4,25.6, 102.4,102.4,128.0)
- Code such as
cairo_pattern_add_color_stop_rgba (pat, ...);
becomespat.add_color_stop_rgba(...)
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
#cr.pattern_t *pat
# Create the Linear Pattern
pat = Cairo::LinearPattern.new(0.0, 0.0, 0.0, 256.0)
pat.add_color_stop_rgba(1, 0, 0, 0, 1)
pat.add_color_stop_rgba(0, 1, 1, 1, 1)
cr.rectangle( 0, 0, 256, 256)
cr.set_source( pat)
cr.fill
# Create the radial pattern
pat = Cairo::RadialPattern.new(115.2, 102.4, 25.6,
102.4, 102.4, 128.0)
pat.add_color_stop_rgba(0, 1, 1, 1, 1)
pat.add_color_stop_rgba(1, 0, 0, 0, 1)
cr.set_source(pat)
# Draw the circle filled with the radial pattern
cr.arc( 128.0, 128.0, 76.8, 0, 2 * Math::PI)
cr.fill
# Save the image surface to a PNG file
surface.write_to_png("cairo-gradient.png")
The resulting image is shown below.
Image
The image sample is easier.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int w, h;
cairo_surface_t *image;
image = cairo_image_surface_create_from_png ("data/romedalen.png");
w = cairo_image_surface_get_width (image);
h = cairo_image_surface_get_height (image);
cairo_translate (cr, 128.0, 128.0);
cairo_rotate (cr, 45* M_PI/180);
cairo_scale (cr, 256.0/w, 256.0/h);
cairo_translate (cr, -0.5*w, -0.5*h);
cairo_set_source_surface (cr, image, 0, 0);
cairo_paint (cr);
cairo_surface_destroy (image);
There are a few very small differences in this example:
- Loading a PNG image changes from
cairo_image_surface_create_from_png
toCairo::ImageSurface.from_png
- You use
set_source(image..)
instead ofcairo_set_source_surface
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
image = Cairo::ImageSurface.from_png("breaking-blue-wave.png")
w = image.width
h = image.height
cr.translate(128.0, 128.0)
cr.rotate( 45* Math::PI/180)
cr.scale(256.0/ w, 256.0/ h)
cr.translate( -0.5*w, -0.5*h)
cr.set_source( image, 0, 0)
cr.paint
image.destroy
# Save the image surface to a PNG file
surface.write_to_png("cairo-image.png")
The resulting image is shown below.
Image Gradient
The image gradient sample needs just a few small changes.
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
int w, h;
cairo_surface_t *image;
cairo_pattern_t *pattern;
cairo_matrix_t matrix;
image = cairo_image_surface_create_from_png ("data/romedalen.png");
w = cairo_image_surface_get_width (image);
h = cairo_image_surface_get_height (image);
pattern = cairo_pattern_create_for_surface (image);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
cairo_translate (cr, 128.0, 128.0);
cairo_rotate (cr, M_PI / 4);
cairo_scale (cr, 1 / sqrt (2), 1 / sqrt (2));
cairo_translate (cr, -128.0, -128.0);
cairo_matrix_init_scale (&matrix, w/256.0 * 5.0, h/256.0 * 5.0);
cairo_pattern_set_matrix (pattern, &matrix);
cairo_set_source (cr, pattern);
cairo_rectangle (cr, 0, 0, 256.0, 256.0);
cairo_fill (cr);
cairo_pattern_destroy (pattern);
cairo_surface_destroy (image);
There are a few small differences in this example:
- As in the previous case, you create the pattern using Cairo::SurfacePattern.new(image)
- You assign the
Cairo::EXTEND_REPEAT
directly topattern.extend
- The scale matrix is created and assigned as
pattern.matrix = Cairo::Matrix.scale(...)
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
image = Cairo::ImageSurface.from_png("breaking-blue-wave.png")
w = image.width
h = image.height
# Load the image as a surface pattern
pattern = Cairo::SurfacePattern.new(image)
pattern.extend = Cairo::EXTEND_REPEAT
cr.translate( 128.0, 128.0)
cr.rotate( Math::PI / 4)
cr.scale( 1 / Math.sqrt(2), 1 / Math.sqrt(2))
cr.translate( -128.0, -128.0)
# Set up the scale matrix
pattern.matrix = Cairo::Matrix.scale(w/256.0 * 5.0, h/256.0 * 5.0)
cr.set_source(pattern)
cr.rectangle( 0, 0, 256.0, 256.0)
cr.fill
image.destroy
# Save the image surface to a PNG file
surface.write_to_png("cairo-image-pattern.png")
The resulting image is shown below.
Multi Segment Caps
The multi segment caps sample is straightforward.
1
2
3
4
5
6
7
8
9
10
11
12
cairo_move_to (cr, 50.0, 75.0);
cairo_line_to (cr, 200.0, 75.0);
cairo_move_to (cr, 50.0, 125.0);
cairo_line_to (cr, 200.0, 125.0);
cairo_move_to (cr, 50.0, 175.0);
cairo_line_to (cr, 200.0, 175.0);
cairo_set_line_width (cr, 30.0);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_stroke (cr);
With what we have done up till now, the resulting Ruby code is quite predictable.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.move_to(50.0, 75.0)
cr.line_to(200.0, 75.0)
cr.move_to(50.0, 125.0)
cr.line_to(200.0, 125.0)
cr.move_to(50.0, 175.0)
cr.line_to(200.0, 175.0)
cr.set_line_width(30.0)
cr.set_line_cap(Cairo::LINE_CAP_ROUND)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-multi-segment-caps.png")
The resulting image is shown below.
Rounded Rectangle
The rounded rectangle sample is also easy to port.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* a custom shape that could be wrapped in a function */
double x = 25.6, /* parameters like cairo_rectangle */
y = 25.6,
width = 204.8,
height = 204.8,
aspect = 1.0, /* aspect ratio */
corner_radius = height / 10.0; /* and corner curvature radius */
double radius = corner_radius / aspect;
double degrees = M_PI / 180.0;
cairo_new_sub_path (cr);
cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
cairo_close_path (cr);
cairo_set_source_rgb (cr, 0.5, 0.5, 1);
cairo_fill_preserve (cr);
cairo_set_source_rgba (cr, 0.5, 0, 0, 0.5);
cairo_set_line_width (cr, 10.0);
cairo_stroke (cr);
With what we have done up till now, the resulting Ruby code is quite predictable.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
# A custom shape that could be wrapped in a function
x = 25.6 # parameters like cr.rectangle
y = 25.6
width = 204.8
height = 204.8
aspect = 1.0 # aspect ratio
corner_radius = height / 10.0 # and corner curvature radius */
radius = corner_radius / aspect
degrees = Math::PI / 180.0
cr.new_sub_path
cr.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
cr.arc(x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees)
cr.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
cr.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
cr.close_path
cr.set_source_rgb(0.5, 0.5, 1)
cr.fill_preserve
cr.set_source_rgba(0.5, 0, 0, 0.5)
cr.set_line_width(10.0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-rounded-rectangle.png")
The resulting image is shown below.
Set line cap
Next up, we look at the set line cap sample is easy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cairo_set_line_width (cr, 30.0);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); /* default */
cairo_move_to (cr, 64.0, 50.0); cairo_line_to (cr, 64.0, 200.0);
cairo_stroke (cr);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_move_to (cr, 128.0, 50.0); cairo_line_to (cr, 128.0, 200.0);
cairo_stroke (cr);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_move_to (cr, 192.0, 50.0); cairo_line_to (cr, 192.0, 200.0);
cairo_stroke (cr);
/* draw helping lines */
cairo_set_source_rgb (cr, 1, 0.2, 0.2);
cairo_set_line_width (cr, 2.56);
cairo_move_to (cr, 64.0, 50.0); cairo_line_to (cr, 64.0, 200.0);
cairo_move_to (cr, 128.0, 50.0); cairo_line_to (cr, 128.0, 200.0);
cairo_move_to (cr, 192.0, 50.0); cairo_line_to (cr, 192.0, 200.0);
cairo_stroke (cr);
With what we have done up till now, the resulting Ruby code is quite predictable.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(30.0)
cr.set_line_cap (Cairo::LINE_CAP_BUTT) # default
cr.move_to(64.0, 50.0)
cr.line_to(64.0, 200.0)
cr.stroke
cr.set_line_cap (Cairo::LINE_CAP_ROUND)
cr.move_to(128.0, 50.0)
cr.line_to(128.0, 200.0)
cr.stroke
cr.set_line_cap (Cairo::LINE_CAP_SQUARE)
cr.move_to(192.0, 50.0)
cr.line_to(192.0, 200.0)
cr.stroke
# draw helping lines */
cr.set_source_rgb(1, 0.2, 0.2)
cr.set_line_width(2.56)
cr.move_to(64.0, 50.0)
cr.line_to(64.0, 200.0)
cr.move_to(128.0, 50.0)
cr.line_to(128.0, 200.0)
cr.move_to(192.0, 50.0)
cr.line_to(192.0, 200.0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-set-line-cap.png")
The resulting image is shown below.
Set line join
Next up, we look at the set line join sample is easy.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cairo_set_line_width (cr, 40.96);
cairo_move_to (cr, 76.8, 84.48);
cairo_rel_line_to (cr, 51.2, -51.2);
cairo_rel_line_to (cr, 51.2, 51.2);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER); /* default */
cairo_stroke (cr);
cairo_move_to (cr, 76.8, 161.28);
cairo_rel_line_to (cr, 51.2, -51.2);
cairo_rel_line_to (cr, 51.2, 51.2);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
cairo_stroke (cr);
cairo_move_to (cr, 76.8, 238.08);
cairo_rel_line_to (cr, 51.2, -51.2);
cairo_rel_line_to (cr, 51.2, 51.2);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
cairo_stroke (cr);
With what we have done up till now, the resulting Ruby code is quite predictable.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(40.96)
cr.move_to(76.8, 84.48)
cr.rel_line_to(51.2, -51.2)
cr.rel_line_to(51.2, 51.2)
cr.set_line_join(Cairo::LINE_JOIN_MITER) # default */
cr.stroke
cr.move_to(76.8, 161.28)
cr.rel_line_to(51.2, -51.2)
cr.rel_line_to(51.2, 51.2)
cr.set_line_join(Cairo::LINE_JOIN_BEVEL)
cr.stroke
cr.move_to(76.8, 238.08)
cr.rel_line_to(51.2, -51.2)
cr.rel_line_to(51.2, 51.2)
cr.set_line_join(Cairo::LINE_JOIN_ROUND)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-set-line-join.png")
The resulting image is shown below.
Text
Finally, we get to move on to text – nothing very difficult here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 90.0);
cairo_move_to (cr, 10.0, 135.0);
cairo_show_text (cr, "Hello");
cairo_move_to (cr, 70.0, 165.0);
cairo_text_path (cr, "void");
cairo_set_source_rgb (cr, 0.5, 0.5, 1);
cairo_fill_preserve (cr);
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_set_line_width (cr, 2.56);
cairo_stroke (cr);
/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_arc (cr, 10.0, 135.0, 5.12, 0, 2*M_PI);
cairo_close_path (cr);
cairo_arc (cr, 70.0, 165.0, 5.12, 0, 2*M_PI);
cairo_fill (cr);
With what we have done up till now, the resulting Ruby code is quite predictable.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
cr.select_font_face("Sans", Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_BOLD)
cr.set_font_size(90.0)
cr.move_to(10.0, 135.0)
cr.show_text("Hello")
cr.move_to(70.0, 165.0)
cr.text_path("void")
cr.set_source_rgb(0.5, 0.5, 1)
cr.fill_preserve
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(2.56)
cr.stroke
# draw helping lines
cr.set_source_rgba(1, 0.2, 0.2, 0.6)
cr.arc(10.0, 135.0, 5.12, 0, 2*Math::PI)
cr.close_path
cr.arc(70.0, 165.0, 5.12, 0, 2*Math::PI)
cr.fill
# Save the image surface to a PNG file
surface.write_to_png("cairo-text.png")
The resulting image is shown below.
Text Align Center
The next sample is the text align center sample.
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
cairo_text_extents_t extents;
const char *utf8 = "cairo";
double x,y;
cairo_select_font_face (cr, "Sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size (cr, 52.0);
cairo_text_extents (cr, utf8, &extents);
x = 128.0-(extents.width/2 + extents.x_bearing);
y = 128.0-(extents.height/2 + extents.y_bearing);
cairo_move_to (cr, x, y);
cairo_show_text (cr, utf8);
/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);
cairo_arc (cr, x, y, 10.0, 0, 2*M_PI);
cairo_fill (cr);
cairo_move_to (cr, 128.0, 0);
cairo_rel_line_to (cr, 0, 256);
cairo_move_to (cr, 0, 128.0);
cairo_rel_line_to (cr, 256, 0);
cairo_stroke (cr);
The Ruby code is not complicated either.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
utf8 = "cairo"
cr.select_font_face("Sans",
Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_NORMAL)
cr.set_font_size(52.0)
extents = cr.text_extents(utf8)
x = 128.0-(extents.width/2 + extents.x_bearing)
y = 128.0-(extents.height/2 + extents.y_bearing)
cr.move_to(x, y)
cr.show_text(utf8)
# draw helping lines
cr.set_source_rgba(1, 0.2, 0.2, 0.6)
cr.set_line_width(6.0)
cr.arc(x, y, 10.0, 0, 2*Math::PI)
cr.fill
cr.move_to(128.0, 0)
cr.rel_line_to(0, 256)
cr.move_to(0, 128.0)
cr.rel_line_to(256, 0)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-text-align-center.png")
The resulting image is shown below.
Text Extents
The final sample is the text extents sample.
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
cairo_text_extents_t extents;
const char *utf8 = "cairo";
double x,y;
cairo_select_font_face (cr, "Sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size (cr, 100.0);
cairo_text_extents (cr, utf8, &extents);
x=25.0;
y=150.0;
cairo_move_to (cr, x,y);
cairo_show_text (cr, utf8);
/* draw helping lines */
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 6.0);
cairo_arc (cr, x, y, 10.0, 0, 2*M_PI);
cairo_fill (cr);
cairo_move_to (cr, x,y);
cairo_rel_line_to (cr, 0, -extents.height);
cairo_rel_line_to (cr, extents.width, 0);
cairo_rel_line_to (cr, extents.x_bearing, -extents.y_bearing);
cairo_stroke (cr);
The Ruby code is as below.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
utf8 = "cairo"
cr.select_font_face("Sans",
Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_NORMAL)
cr.set_font_size(100.0)
extents = cr.text_extents(utf8)
x=25.0
y=150.0
cr.move_to(x,y)
cr.show_text(utf8)
# draw helping lines */
cr.set_source_rgba(1, 0.2, 0.2, 0.6)
cr.set_line_width(6.0)
cr.arc(x, y, 10.0, 0, 2*Math::PI)
cr.fill
cr.move_to(x,y)
cr.rel_line_to(0, -extents.height)
cr.rel_line_to(extents.width, 0)
cr.rel_line_to(extents.x_bearing, -extents.y_bearing)
cr.stroke
# Save the image surface to a PNG file
surface.write_to_png("cairo-text-extents.png")
The resulting image is shown below.
Personally, I think this looks a bit wrong but the original image on the Cairographics site is clipped; so, you don’t see what the bit on the right looks like. The sample below looks better to me.
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
require 'cairo'
# Create a new image surface
width = 256
height = 256
surface = Cairo::ImageSurface.new(width, height)
# Create a Cairo Context for the surface
cr = Cairo::Context.new(surface)
cr.set_source_rgb(0.95, 0.95, 0.95)
cr.paint
# The main code
cr.set_source_rgb(0, 0, 0)
utf8 = "cairo"
cr.select_font_face("Sans",
Cairo::FONT_SLANT_NORMAL,
Cairo::FONT_WEIGHT_NORMAL)
cr.set_font_size(100.0)
extents = cr.text_extents(utf8)
x = 25.0
y = 150.0
cr.move_to(x, y)
cr.show_text(utf8)
# draw helping lines */
cr.set_source_rgba(1, 0.2, 0.2, 0.6)
cr.set_line_width(6.0)
cr.arc(x, y, 10.0, 0, 2 * Math::PI)
cr.fill
cr.move_to(x, y)
cr.rel_line_to(0, -extents.height)
cr.rel_line_to(extents.x_advance, 0)
cr.rel_line_to(0, extents.height)
cr.stroke
puts extents
# save the file to the output directory
surface.write_to_png('cairo-text-extents2.png')
The resulting image is shown below.
With that, we come to an end of the samples. I hope these help in some way. Remember, you can get the code on GitHub if you need it. If you have a comment, please leave it below so that I can get to it. As I said above, I also wrote up a related post about porting Cairo code from C to Ruby which you might also find useful.