Back to Index

Maemo for mobile developers

Drawing with GDK/Cairo

Table of contents

  1. Introduction: GDK and Cairo
  2. Draw example application
  3. Getting into the source

Introduction: GDK and Cairo

GDK (GIMP Drawing Kit) is an intermediate layer between the X server and the GTK+ library, which acts as a wrapper around the low-level drawing and windowing functions, among hardware keys, interrupts, and others. Cairo is a vector-based two-dimensional graphics library with support for various output devices. It has a powerful API that provides developers the necessary primitives for two-dimensional drawing across a number of different backends, such as image buffers, PostScript, PDF, SVG, X Window System, and others.

These libraries have their respective C++ wrappers, known as gdkmm (included in gtkmm) and cairomm. These libraries are used to create an application called "Draw Example". Here you can see some GDK examples and also Cairo context intialization and editing.

Draw Example application

After following this tutorial, you should have this application:
Draw Example

This example has a combo box, some color buttons, and two labeled buttons called "Draw" and "Clear". The "Draw" button draws the object that was selected from the combo box on the screen. The "Clear" button simply clears the screen, painting it all to white. This application uses some GDK and Cairo methods for context creation, with 3 examples of usage (create a circle, a line, or a rectangle randomly on the screen).

The color buttons are used to select 2 different colors: one for the object border and one for the color that fills the object. There is also a combo box where you can select the object that will be drawn.

As you can see, there is some alpha blending on the border made by Cairo. It is achieved by using a special method that has the option to insert the level of alpha as an argument. The source code and its details are discussed in the next section.

Getting into the source

Headers

In this example we deal with new widgets from different libraries. These are explained below:

MyWindow implementation

In order to create randomic numbers (used for putting object coordinates on the window), we need to use rand() from cmath header:

#include <cmath> // Header for rand()

m_frame_box has an "homogeneous" attribute that means the child widgets inside this one doesn't need to be necessarily the same size. This allows us to make the draw_area larger and the button_box at its minimum size:

MyWindow::MyWindow() :
        m_item_exit(_("Exit")),
        m_frame_box(false, 10), // homogeneous: false, spacing: 10
        m_label_border(_("Border color")),
        m_label_fill(_("Fill color")),
        m_button_draw(_("Draw")),
        m_button_clear(_("Clear"))

Every menu option, combo option and button (including color buttons) have a callback method that is called by signal handlers:

/* "Quit" menu button */
m_item_exit.signal_activate().connect(sigc::mem_fun(*this,
		&MyWindow::on_menu_quit));

/* Border color button */
m_cb_border.connect_property_changed("color", sigc::mem_fun(*this,
		&MyWindow::on_border_color_button_color_changed));

/* Fill color button */
m_cb_fill.connect_property_changed("color", sigc::mem_fun(*this,
		&MyWindow::on_fill_color_button_color_changed));

/* Clear button */
m_button_clear.signal_clicked().connect(sigc::mem_fun(*this,
		&MyWindow::on_button_clear_clicked));

/* Draw box */
m_button_draw.signal_clicked().connect(sigc::mem_fun(*this,
		&MyWindow::on_button_draw_clicked));

At the ending of constructor class, we initialize the Cairo context, by using m_draw_area as target:

/* Initialize Cairo context */
m_cairo_ctx = m_draw_area.get_window()->create_cairo_context();

Callback methods implementation

The color button callbacks acts like the same, storing the chosen color from the color selection window ("color" property of this object) on a Gdk::Color object:

void MyWindow::on_border_color_button_color_changed()
{
        /* The border color button gets the properties of the chosen color */
        m_cb_border.get_property("color", m_color_border);
}

void MyWindow::on_fill_color_button_color_changed()
{
        /* The fill color button gets the properties of the chosen color */
        m_cb_fill.get_property("color", m_color_fill);
}

The next method is responsible for treatment of the selected option from the m_combo_box. Each option calls a different object drawing method.

void MyWindow::on_button_draw_clicked()
{
        Glib::ustring text = m_combo_box.get_active_text();
        if (text == _("Circle")) // If "Circle" option from combo box gets selected
                on_combo_circle_selected();
        if (text == _("Line")) // If "Line" option from combo box gets selected
                on_combo_line_selected();
        if (text == _("Rectangle")) // If "Rectangle" option from combo box gets selected
                on_combo_rectangle_selected();
}

The next methods are responsible for the object drawings. They manipulate the cairo context using cairomm methods. It is a good practice to put save() before and restore() after manipulating the cairo context, in order to preserve the context state.

In order to draw an object, first we set the object parameters (like color, for example) using set_source_rgb(), then we shape the object with methods like arc(), line_to(), move_to() and rectangle(). Finally we draw the object shapes into the drawing area using methods like stroke(), paint() or fill().

The first callback method on_button_clear_clicked(), clears the whole drawing area with white color. paint() is used to paint the whole drawing area with the color set by set_source_rgb() (in this case, white):

void MyWindow::on_button_clear_clicked()
{
        /* These variables captures the total width and height of draw_area */
        const int width = m_draw_area.get_width();
        const int height = m_draw_area.get_height();

        m_cairo_ctx->save();

        /* Clear background to white */
        m_cairo_ctx->set_source_rgb(1, 1, 1);
        m_cairo_ctx->paint();

        m_cairo_ctx->restore();
}

The other callback methods are very similar to each other, just differing from the methods they call to shape the objects they represent. First they set the fill color, shape the object, and draw it using fill(), then they set the border color, shape the border (using the same method called for shaping the object), and draw it using stroke(). The border "opaque" effect is done using set_source_rgba() method, wich adds a fourth attribute that sets the level of alpha on the color.

on_combo_circle_selected callback method uses the arc() method to draw a circle:

void MyWindow::on_combo_circle_selected()
{
        const int width = m_draw_area.get_width();
        const int height = m_draw_area.get_height();

        /* These variables creates a randomic number in order to draw the object on different places */
        int randx = rand()%width;
        int randy = rand()%height;
        int rands = rand()%50;

        m_cairo_ctx->save();

        /* Set fill color (from color_fill) */
        cairo_ctx->set_source_rgb(m_color_fill.get_red_p(), m_color_fill.get_green_p(),
			m_color_fill.get_blue_p());

        /* Here we draw the circle */
        m_cairo_ctx->arc(randx, randy, rands, 0.0, 2.0 * M_PI);
        m_cairo_ctx->close_path();
        m_cairo_ctx->fill();

        /* Set border color */
        m_cairo_ctx->set_line_width(rands/4); // Set border line width to 1/4 of rands

        /* Notice that set_source_rgba has a fourth parameter that is the level
         * of alpha you want on the object you're drawing
         */
        m_cairo_ctx->set_source_rgba(m_color_border.get_red_p(), m_color_border.get_green_p(),
			m_color_border.get_blue_p(), 0.5);
        m_cairo_ctx->arc(randx, randy, rands, 0.0, 2.0 * M_PI);
        m_cairo_ctx->close_path();
        m_cairo_ctx->stroke();

        m_cairo_ctx->restore();
}

on_combo_line_selected uses move_to() and line_to() methods to shape a line (with no border, since it is too thin to have one):

void MyWindow::on_combo_line_selected()
{
        const int width = m_draw_area.get_width();
        const int height = m_draw_area.get_height();
        int randx = rand()%width;
        int randy = rand()%height;
        int randh = rand()%width;
        int randw = rand()%height;

        m_cairo_ctx->save();

        /* Set fill color */
        m_cairo_ctx->set_source_rgb(m_color_fill.get_red_p(), m_color_fill.get_green_p(),
			m_color_fill.get_blue_p());

        /* Here we draw the line */
        m_cairo_ctx->move_to(randx, randy);
        m_cairo_ctx->line_to(randh, randw);
        m_cairo_ctx->close_path();
        m_cairo_ctx->stroke();

        m_cairo_ctx->restore();
}

Finally, on_combo_rectangle_selected uses rectangle() method to draw a rectangle:

void MyWindow::on_combo_rectangle_selected()
{
        const int width = m_draw_area.get_width();
        const int height = m_draw_area.get_height();
        int randx = rand()%width;
        int randy = rand()%height;
        int randw = rand()%100;
        int randh = rand()%100;

        m_cairo_ctx->save();

        /* Set fill color (from color_fill) */
        m_cairo_ctx->set_source_rgb(m_color_fill.get_red_p(), m_color_fill.get_green_p(),
			m_color_fill.get_blue_p());

        /* Here we draw the rectangle */
        m_cairo_ctx->rectangle(randx, randy, randh, randw);
        m_cairo_ctx->close_path();
        m_cairo_ctx->fill();
        m_cairo_ctx->stroke();

        /* Set border color (from color_border) */
        m_cairo_ctx->set_line_width(randw/4);
        m_cairo_ctx->set_source_rgba(m_color_border.get_red_p(), m_color_border.get_green_p(),
                        m_color_border.get_blue_p(), 0.5);
        m_cairo_ctx->rectangle(randx, randy, randh, randw);
        m_cairo_ctx->close_path();
        m_cairo_ctx->stroke();

        m_cairo_ctx->restore();
}

You are now familiar with some basic GDK/Cairo drawing methods. The source code of this example is here.

Managing Application Information with GConf