Kreslení rovných čar

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.

16.2.1. Příklad

V tomto příkladu sestavíme malý, ale plně funkční program používající gtkmm a nakreslíme do okna pár čar. Čáry se kreslí vytvořením cesty a jejím obtažením. Cesta se vytváří pomocí funkcí Cairo::Context::move_to() a Cairo::Context::line_to(). Funkce move_to() funguje podobně, jako když zvednete pero nad papír a posunete ruku jinam a znovu jej přiložíte – během přesunu ruky pero nic nekreslilo. Pro kreslení čáry mezi dvěma body se používá funkce line_to().

Po dokončení tvorby cesty pořád nemáte nakreslené nic viditelného. Aby se cesta zviditelnila, musíte použít funkci stroke(), která obtáhne cestu čarou s tloušťkou a stylem definovanými v objektu Cairo::Context. Po obtažení se aktuální cesta smaže a můžete začít s novou cestou.

Mnoho kreslicích funkcí Cairo má variantu _preserve(). Normální kreslicí funkce, jako jsou clip(), fill() nebo stroke(), mažou aktuální cestu. Když použijete variantu _preserve(), bude aktuální cesta ponechána, takže ji můžete použít v následující kreslicí funkci.

Obrázek 16-1Kreslicí oblast – čáry

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

Kreslení s relativními souřadnicemi

V příkladu výše jsme všechno nakreslili pomocí absolutních souřadnic. Můžeme kreslit také pomocí relativních souřadnic. Pro přímé čáry se to dělá pomocí funkce Cairo::Context::rel_line_to().

16.2.2. Styly čar

Mimo kreslení základních rovných čar, existuje řada věcí, které si u čar můžete přizpůsobit. Již jste viděli příklad nastavení barvy a tloušťky čáry, ale jsou tu i další.

Když kreslíte posloupnost čar ve formě cesty, můžete je chtít spojovat dohromady konkrétním způsobem. Cairo nabízí tři různé způsoby, jak čáry spojit: hranatý spoj – Miter, zkosený spoj – Bevel a zaoblený spoj – Round. Zde jsou ukázané ve zmíněném pořadí:

Obrázek 16-2Různé typy spojů v Cairo

Styl spojování čar se nastavuje pomocí funkce Cairo::Context::set_line_join().

Stejně tak mohou mít různé styly konce čar. Výchozí styl pro čáru je začínat a končit přesně v koncových bodech čáry. To se nazývá rovný konec – Butt. Dalšími volbami jsou zaoblení – Round (zaoblení má střed v koncovém bodu) a čtvercový konec – Square (rovněž čtverec má střed v koncovém bodu). Tyto volby se nastavují pomocí funkce Cairo::Context::set_line_cap().

Existují i další věci, které si můžete přizpůsobit, včetně vytváření přerušovaných čar a jiného. Více informací najdete v dokumentaci k API Cairo.

16.2.3. Kreslení tenkých čar

Když zkusíte kreslit čáru tlustou jeden pixel, můžete být překvapeni, že místy bude čára rozmazaná a širší, než by měla. To se děje, protože Cairo se snaží kreslit z vybraných souřadnic obě strany (polovinu tloušťky na každou stranu), takže když zadáte umístění přesně mezi pixely a chcete jeden pixel tlustou čáru, pokouší se Cairo použí polovinu z každého pixelu, což není možní (pixel je nejmenší možná jednotka). Děje se to pokaždé, když je tloušťka čáry lichý počet pixelů (ne jen pro jeden pixel).

Trik je v umístění uprostřed pixelu, kde chcete čáru nakreslit, čímž se garantuje, že dostanete požadované výsledky. Viz Časté dotazy a odpovědi ke knihovně Cairo.

Obrázek 16-3Kreslicí oblast – tenké čáry

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