Dibujar Lineas Rectas

Ahora que entiende lo básico sobre la biblioteca de gráficos Cairo, está casi listo para empezar a dibujar. Empezará con el elemento de dibujo más simple: la línea recta. Pero, primero necesita saber algo del sistema de coordenadas de Cairo. El origen del sistema de coordenadas de Cairo está en la esquina superior izquierda de la ventana, con valores positivos de x hacia la derecha y valores positivos de y hacia abajo.

Dado que la biblioteca de gráficos Cairo se escribió con soporte para múltiples objetivos de salida (el sistema de ventanas X, imágenes PNG, OpenGL, etc), hay una distinción entre las coordenadas espaciales del usuario y las del dispositivo. De manera predeterminada, estos dos sistemas de coordenadas están mapeados uno a uno, por lo que los valores enteros mapean aproximadamente a píxeles en la pantalla, pero esta configuración puede ajustarse si así se desea. A veces, puede ser útil escalar las coordenadas para que la anchura y la altura completos de la ventana vayan de 0 a 1 (el «cuadrado unitario»), o algún otro mapeo que funcione en su aplicación. Esto puede realizarse con la función Cairo::Context::scale().

17.2.1. Ejemplo

En este ejemplo, se construirá un programa gtkmm pequeño pero funcional y se dibujarán algunas líneas en la ventana. Las líneas se dibujan creando un camino y luego rellenándolo. Un camino se crea usando las funciones Cairo::Context::move_to() y Cairo::Context::line_to(). La función move_to() es similar al acto de levantar el bolígrafo del papel y ponerlo en algún otro lado: no se dibuja ninguna línea entre el punto en el que estaba y el punto al que se movió. Para dibujar una línea entre dos puntos, use la función line_to().

Después de terminar de crear su camino, todavía no ha dibujado nada visible. Para hacer el camino visible, debe usar la función stroke() que rellenará el camino actual con la anchura y estilo de línea que se ha especificado en su objeto Cairo::Context. Después de rellenar, el camino actual se despejará para que pueda comenzar el próximo.

Muchas funciones de dibujo de Cairo tienen una variante _preserve(). Normalmente, las funciones de dibujo como clip(), fill(), o stroke() despejarán el camino actual. Si usa la variante _preserve(), el camino actual se retendrá, por lo que podrá usar el mismo camino con la próxima función de dibujo.

Figura 17-1Área de dibujo: líneas

Código fuente

File: myarea.h (For use with gtkmm 3, not gtkmm 2)

#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_draw(const Cairo::RefPtr<Cairo::Context>& cr);
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: myarea.cc (For use with gtkmm 3, not gtkmm 2)

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

MyArea::MyArea()
{
}

MyArea::~MyArea()
{
}

bool MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
{
  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;

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

  return true;
}

File: main.cc (For use with gtkmm 3, not gtkmm 2)

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

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

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

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

   return app->run(win);
}

Este programa contiene una sola clase, MyArea, que es una subclase de Gtk::DrawingArea y contiene una función miembro on_draw(). Esta función se llama siempre que la imagen en el área de dibujo deba redibujarse. Se le pasa un puntero Cairo::RefPtr al Cairo::Context que se usa para el dibujo. El código de dibujo real establece el color que queremos usar para dibujar usando set_source_rgb(), que toma argumentos definiendo los componentes de rojo, verde, y azul del color deseado (los valores válidos se hallan entre 0 y 1). Después de establecer el color, creamos un camino nuevo usando las funciones move_to() y line_to(), y luego lo rellenamos usando stroke().

Dibujar con coordenadas relativas

En el ejemplo anterior, se ha dibujado todo usando coordenadas absolutas. También puede dibujar usando coordenadas relativas. Para una línea recta, esto se hace con la función Cairo::Context::rel_line_to().

17.2.2. Estilos de línea

Además de dibujar líneas rectas básicas, también puede personalizar algunas cosas de las líneas. Ya ha visto ejemplos de cómo establecer el color y anchura de una línea, pero también hay otras cosas.

Si ha dibujado una serie de líneas que forman un camino, tal vez quiera que se junten de alguna manera. Cairo le ofrece tres maneras distintas de juntar líneas: «Miter», «Bevel», y «Round». Se muestran a continuación:

Figura 17-2Distintos tipos de uniones en Cairo

El estilo de unión de línea se establece usando la función Cairo::Context::set_line_join().

Las puntas de las líneas pueden también tener distintos estilos. El estilo predeterminado consiste en que la línea comience y se detenga exactamente en sus puntos de destino. Esto se llama terminación «Butt». Las otras opciones son «Round» (usa una terminación redondeada, con el centro del círculo en el último punto) o «Square» (usa una terminación cuadrada, con el centro del cuadrado en el último punto). Esta opción se establece usando la función Cairo::Context::set_line_cap().

Además, hay otras cosas que puede personalizar, incluyendo la creación de líneas punteadas y otras cosas. Para obtener más información, consulte la documentación de la API de Cairo.

17.2.3. Dibujar líneas estrechas

Si intenta dibujar líneas de un píxel de anchura, notará que a veces la línea sale más borrosa y ancha de lo que debería. Esto sucede porque Cairo intentará dibujar desde la posición seleccionada, a ambos lados (mitad a cada uno), por que lo que si está posicionado justo en la intersección de los píxeles, y quiere líneas de un píxel de anchura, Cairo intentará usar la mitad de cada píxel adyacente, lo que no es posible (un píxel es la menor unidad posible). Esto sucede cuando la anchura de la línea es un número impar de píxeles (no sólo uno).

El truco está en posicionarse en la mitad del píxel en el que quiere que se dibuje la línea, garantizando así que obtendrá los resultados deseados. Consulte las preguntas más frecuentes de Cairo.

Figura 17-3Área de dibujo: líneas estrechas

Código fuente

File: myarea.h (For use with gtkmm 3, not gtkmm 2)

#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);
  void force_redraw();

protected:
  //Override default signal handler:
  virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr);

private:
  double m_fix;
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm/window.h>
#include <gtkmm/grid.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::Grid m_Container;
  MyArea m_Area_Lines;
  Gtk::CheckButton m_Button_FixLines;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: myarea.cc (For use with gtkmm 3, not gtkmm 2)

#include "myarea.h"

MyArea::MyArea()
: m_fix (0)
{
  set_size_request (200, 100);
}

MyArea::~MyArea()
{
}

bool MyArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
{
  Gtk::Allocation allocation = get_allocation();
  const int width = allocation.get_width();
  const int height = allocation.get_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();

  return true;
}

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

// force the redraw of the image
void MyArea::force_redraw()
{
  Glib::RefPtr<Gdk::Window> win = get_window();
  if (win)
  {
    Gdk::Rectangle r(0, 0, get_allocation().get_width(), get_allocation().get_height());
    win->invalidate_rect(r, false);
  }
}

File: examplewindow.cc (For use with gtkmm 3, not gtkmm 2)

#include "examplewindow.h"

ExampleWindow::ExampleWindow()
: m_Button_FixLines("Fix lines")
{
  set_title("Thin lines example");

  m_Container.set_orientation(Gtk::ORIENTATION_HORIZONTAL);

  m_Container.add(m_Area_Lines);
  m_Container.add(m_Button_FixLines);

  add(m_Container);

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

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

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

File: main.cc (For use with gtkmm 3, not gtkmm 2)

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

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

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window);
}