Drawing Straight Lines

Now that we understand the basics of the Cairo graphics library, we're almost ready to start drawing. We'll start with the simplest of drawing elements: the straight line. But first you need to know a little bit about Cairo's coordinate system. The origin of the Cairo coordinate system is located in the upper-left corner of the window with positive x values to the right and positive y values going down.

[Tip] Tip

Since the Cairo graphics library was written with support for multiple output targets (the X window system, PNG images, OpenGL, etc), there is a distinction between user-space and device-space coordinates. The mapping between these two coordinate systems defaults to one-to-one so that integer values map roughly to pixels on the screen, but this setting can be adjusted if desired. Sometimes it may be useful to scale the coordinates so that the full width and height of a window both range from 0 to 1 (the 'unit square') or some other mapping that works for your application. this can be done with the Cairo::Context::scale() function.

Example

In this example, we'll construct a small but fully functional gtkmm program and draw some lines into the window. The lines are drawn by creating a path and then stroking it. A path is created using the functions Cairo::Context::move_to() and Cairo::Context::line_to(). The function move_to() is similar to the act of lifting your pen off of the paper and placing it somewhere else -- no line is drawn between the point you were at and the point you moved to. To draw a line between two points, use the line_to() function.

After you've finished creating your path, you still haven't drawn anything visible yet. To make the path visible, you must use the function stroke() which will stroke the current path with the line width and style specified in your Cairo::Context object. After stroking, the current path will be cleared so that you can start on your next path.

[Tip] Tip

Many Cairo drawing functions have a _preserve() variant. Normally drawing functions such as clip(), fill(), or stroke() will clear the current path. If you use the _preserve() variant, the current path will be retained so that you can use the same path with the next drawing function.

Figure 15.1. Drawing Area - Lines

Drawing Area - Lines

Source Code

File: myarea.h

#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H

#include <gtkmm/drawingarea.h>

class MyArea : public Gtk::DrawingArea
{
public:
  MyArea();
  virtual ~MyArea();

protected:
  //Override default signal handler:
  virtual bool on_expose_event(GdkEventExpose* event);
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: main.cc

#include "myarea.h"
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char** argv)
{
   Gtk::Main kit(argc, argv);

   Gtk::Window win;
   win.set_title("DrawingArea");

   MyArea area;
   win.add(area);
   area.show();

   Gtk::Main::run(win);

   return 0;
}

File: myarea.cc

#include "myarea.h"
#include <cairomm/context.h>

MyArea::MyArea()
{
}

MyArea::~MyArea()
{
}

bool MyArea::on_expose_event(GdkEventExpose* event)
{
  // This is where we draw on the window
  Glib::RefPtr<Gdk::Window> window = get_window();
  if(window)
  {
    Gtk::Allocation allocation = get_allocation();
    const int width = allocation.get_width();
    const int height = allocation.get_height();

    // coordinates for the center of the window
    int xc, yc;
    xc = width / 2;
    yc = height / 2;

    Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
    cr->set_line_width(10.0);

    // clip to the area indicated by the expose event so that we only redraw
    // the portion of the window that needs to be redrawn
    cr->rectangle(event->area.x, event->area.y,
            event->area.width, event->area.height);
    cr->clip();

    // draw red lines out from the center of the window
    cr->set_source_rgb(0.8, 0.0, 0.0);
    cr->move_to(0, 0);
    cr->line_to(xc, yc);
    cr->line_to(0, height);
    cr->move_to(xc, yc);
    cr->line_to(width, yc);
    cr->stroke();
  }

  return true;
}

This program contains a single class, MyArea, which is a subclass of Gtk::DrawingArea and contains an on_expose_event() member function. This method is called whenever the image in the drawing area needs to be redrawn. This function is passed a pointer to a GdkEventExpose structure which defines the area that needs to be redrawn. We use these values to create a rectangle path in Cairo (using the rectangle() function) and then clip() to this path. The clip() function sets a clip region. The current clip region affects all drawing operations by effectively masking out any changes to the surface that are outside the current clip region. This allows us to limit our redrawing to only the area that needs to be redrawn. The actual drawing code sets the color we want to use for drawing by using set_source_rgb() which takes arguments defining the Red, Green, and Blue components of the desired color (valid values are between 0 and 1). After setting the color, we created a new path using the functions move_to() and line_to(), and then stroked this path with stroke().

[Tip] Drawing with relative coordinates

In the example above we drew everything using absolute coordinates. You can also draw using relative coordinates. For a straight line, this is done with the function Cairo::Context::rel_line_to().

Line styles

In addition to drawing basic straight lines, there are a number of things that you can customize about a line. You've already seen examples of setting a line's color and width, but there are others as well.

If you've drawn a series of lines that form a path, you may want them to join together in a certain way. Cairo offers three different ways to join lines together: Miter, Bevel, and Round. These are show below:

Figure 15.2. Different join types in Cairo

Different join types in Cairo

The line join style is set using the function Cairo::Context::set_line_join().

Line ends can have different styles as well. The default style is for the line to start and stop exactly at the destination points of the line. This is called a Butt cap. The other options are Round (uses a round ending, with the center of the circle at the end point) or Square (uses a squared ending, with the center of the square at the end point). This setting is set using the function Cairo::Context::set_line_cap().

There are other things you can customize as well, including creating dashed lines and other things. For more information , see the Cairo API documentation.