Tracé de droites

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.

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.

XVI.II.I. Exemple

Dans cet exemple, nous allons écrire un programme gtkmm petit, mais pleinement fonctionnel, et tracer quelques lignes dans la fenêtre. Les lignes sont dessinées en créant un chemin, puis en le traçant. Un tracé est créé avec les fonctions Cairo::Context::move_to() et Cairo::Context::line_to(). La fonction move_to() est semblable à l'action de lever la pointe de votre crayon au dessus du papier et de la placer quelque part ailleurs — aucune ligne n'est tracée entre le point où vous étiez et celui où vous vous êtes déplacé. Pour tracer une ligne entre deux points, utilisez la fonction line_to().

Après avoir terminé la création de votre chemin, vous n'avez encore dessiné rien de visible. Pour rendre le chemin visible, il faut utiliser la fonction stroke() qui dessine le tracé actuel avec une ligne d'épaisseur et de style précisés dans l'objet Cairo::Context. Après le traçage, le chemin actuel est effacé ; vous pouvez donc en débuter un nouveau.

Beaucoup de fonctions de dessin de Cairo disposent d'une variante _preserve(). Normalement, les fonctions de dessin telles que clip(), fill() ou stroke() effacent le chemin actuel. Si vous utilisez la variante _preserve(), le chemin actuel sera préservé de sorte que vous pouvez à nouveau l'utiliser avec une autre fonction de dessin.

Figure XVI.1 Zone de dessin ‑ Lignes

Source Code

File: myarea.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H

#include <gtkmm/drawingarea.h>

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

protected:
  void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: main.cc (For use with gtkmm 4)

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

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();

protected:
  MyArea m_area;
};

ExampleWindow::ExampleWindow()
{
  set_title("DrawingArea");
  set_child(m_area);
}

int main(int argc, char** argv)
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: myarea.cc (For use with gtkmm 4)

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

MyArea::MyArea()
{
  set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}

MyArea::~MyArea()
{
}

void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
  // coordinates for the center of the window
  int xc, yc;
  xc = width / 2;
  yc = height / 2;

  cr->set_line_width(10.0);

  // 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();
}

This program contains a single class, MyArea, which is a subclass of Gtk::DrawingArea and contains an on_draw() member function. This function becomes the draw function by a call to set_draw_func() in MyArea's constructor. on_draw() is then called whenever the image in the drawing area needs to be redrawn. It is passed a Cairo::RefPtr pointer to a Cairo::Context that we use for the drawing. 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().

Tracé en coordonnées relatives

Dans l'exemple ci-dessus nous avons tout tracé en utilisant des coordonnées absolues. Nous pouvons également tracer en coordonnées relatives. Pour une droite, on utilise la fonction Cairo::Context::rel_line_to().

XVI.II.II. Styles de trait

En plus du tracé élémentaire de droites, vous pouvez personnaliser un certain nombre de choses dans les tracés de lignes. Nous avons déjà vu des exemples de paramétrage de couleur et d'épaisseur de trait, mais il en existe bien d'autres.

Si vous avez tiré une série de traits formant un chemin, vous pouvez vouloir les raccorder d'une certaine façon. Cairo offre trois façons différentes de raccorder les lignes entre elles : l'onglet, le biseau et l'arrondi. Voyez-les ci-dessous :

Figure XVI.2 Différents types de raccord dans Cairo

Le type de raccord est défini avec la fonction Cairo::Context::set_line_join().

Les fins de ligne peuvent avoir différents styles également. Le style par défaut est de débuter et arrêter la ligne exactement aux points de définition. On nomme cela la coupe droite. Les autres options sont l'arrondi (utilise une terminaison en demi-cercle, le centre du demi-cercle étant au point terminal de la ligne) ou le carré (utilise une terminaison carrée diagonale, le centre du carré étant au point terminal de la ligne). Ce paramétrage est défini avec la fonction 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.

XVI.II.III. Drawing thin lines

If you try to draw one pixel wide lines, you may notice that the line sometimes comes up blurred and wider than it ought to be. This happens because Cairo will try to draw from the selected position, to both sides (half to each), so if you're positioned right on the intersection of the pixels, and want a one pixel wide line, Cairo will try to use half of each adjacent pixel, which isn't possible (a pixel is the smallest unit possible). This happens when the width of the line is an odd number of pixels (not just one pixel).

The trick is to position in the middle of the pixel where you want the line to be drawn, and thus guaranteeing you get the desired results. See Cairo FAQ.

Figure XVI.3 Drawing Area - Thin Lines

Source Code

File: examplewindow.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm/window.h>
#include <gtkmm/box.h>
#include <gtkmm/checkbutton.h>
#include "myarea.h"

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  virtual ~ExampleWindow();

protected:
  //Signal handlers:
  void on_button_toggled();

private:
  Gtk::Box m_HBox;
  MyArea m_Area_Lines;
  Gtk::CheckButton m_Button_FixLines;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: myarea.h (For use with gtkmm 4)

#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H

#include <gtkmm/drawingarea.h>

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

  void fix_lines(bool fix = true);

protected:
  void on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height);

private:
  double m_fix;
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: main.cc (For use with gtkmm 4)

#include "examplewindow.h"
#include <gtkmm/application.h>

int main(int argc, char* argv[])
{
  auto app = Gtk::Application::create("org.gtkmm.example");

  //Shows the window and returns when it is closed.
  return app->make_window_and_run<ExampleWindow>(argc, argv);
}

File: examplewindow.cc (For use with gtkmm 4)

#include "examplewindow.h"

ExampleWindow::ExampleWindow()
: m_HBox(Gtk::Orientation::HORIZONTAL),
  m_Button_FixLines("Fix lines")
{
  set_title("Thin lines example");

  m_HBox.append(m_Area_Lines);
  m_HBox.append(m_Button_FixLines);

  set_child(m_HBox);

  m_Button_FixLines.signal_toggled().connect(
    sigc::mem_fun(*this, &ExampleWindow::on_button_toggled));

  // Synchonize the drawing in m_Area_Lines with the state of the toggle button.
  on_button_toggled();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_toggled()
{
  m_Area_Lines.fix_lines(m_Button_FixLines.get_active());
}

File: myarea.cc (For use with gtkmm 4)

#include "myarea.h"

MyArea::MyArea()
: m_fix (0)
{
  set_content_width(200);
  set_content_height(100);
  set_draw_func(sigc::mem_fun(*this, &MyArea::on_draw));
}

MyArea::~MyArea()
{
}

void MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
{
  cr->set_line_width(1.0);

  // draw one line, every two pixels
  // without the 'fix', you won't notice any space between the lines,
  // since each one will occupy two pixels (width)
  for (int i = 0; i < width; i += 2)
  {
    cr->move_to(i + m_fix, 0);
    cr->line_to(i + m_fix, height);
  }

  cr->stroke();
}

// Toogle between both values (0 or 0.5)
void MyArea::fix_lines(bool fix)
{
  // to get the width right, we have to draw in the middle of the pixel
  m_fix = fix ? 0.5 : 0.0;

  // force the redraw of the image
  queue_draw();
}