Tuesday, August 31, 2010

Using Cairo with Gtkmm

Okay we should be all familiar with the Cairo API. If this is the first time you've ever heard of Cairo outside of a reference to Egypt then let me give you a quick peek at Cairo.



Cairo is pretty much the main library used for drawing anything with vectors. It is used everywhere, sort of like SQLite. The website is http://cairographics.org/



In the GNOME trifecta of things, Cairo provides the 2D drawing API, whereas Pango provides the Text Rendering API, and Gtk-Glib provide the toolkit/glue for all the above.



The neat thing about Cairo is that output is not limited to the screen. Backends for Cairo can be written for anything, so things drawn in Cairo can be outputted to an SVG file, OpenGL commands, a printer, or to a Pixbuf for use in a very popular web browser (Firefox anyone).



So today I'm going to use Cairomm (C++ bindings for Cairo) to do some basic drawing. Here is our header file justin_draw.h that defines our DrawingArea.



#ifndef SAMPLE04_JUSTIN
#define SAMPLE04_JUSTIN

#include <gtkmm/drawingarea.h>

class JustinDraw : public Gtk::DrawingArea
{

public:
JustinDraw();
virtual ~JustinDraw();

protected:
virtual bool on_expose_event(GdkEventExpose* event);

};

#endif



The only thing of note here is that we are going to define an on_expose_event. This is important because this is the method that is called when the window needs to draw itself. Either because the Window is new, or we've uncovered the window an exposed a part of it that was under another window.




Now let's implement our Gtk::DrawingArea with our justin_draw.cc file:



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

JustinDraw::JustinDraw() {}

JustinDraw::~JustinDraw() {}

bool JustinDraw::on_expose_event(GdkEventExpose* event)
{
Glib::RefPtr<Gdk::Window> window = get_window();

if (window)
{
Gtk::Allocation a = get_allocation();
const int w = a.get_width();
const int h = a.get_height();

int xc, yc;
xc = w/2;
yc = h/2;

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

cr->rectangle(event->area.x, event->area.y, event->area.width, event->area.height);
cr->clip();

cr->set_source_rgb(0.8, 0.0, 0.0);
cr->move_to(0,0);
cr->line_to(xc,yc);
cr->line_to(0,h);
cr->move_to(xc,yc);
cr->line_to(w,yc);
cr->stroke();
}

return true;
}



Notice a couple of things here before we move on. Drawing an entire screen with Cairo should be done very rarely. Not to say Cairo is slow but there is no need to waste time on drawing. The cr->rectangle(...); cr->clip(); parts allow us to focus on only the part of the window that has changed. Pretty much all code that uses Cairo has something similar to this so it's a good idea to include something like this in you code before you actually do any drawing.




Also notice that we are using a Cairo::RefPtr. Yes Cairo has it's own type of RefPtr's that you must use to hold Cairo related things. You have no idea how much time you can waste using the wrong type of RefPtr.




Finally, our program that uses our new Gtk::DrawingArea. main.cc

#include "justin_draw.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");

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

Gtk::Main::run(win);

return 0;
}



And that's all there is to it! You can hit up basically any manual on Cairo to find all the 2D API commands. It's basically a 1:1 translation between the C and C++ bindings so you shouldn't have any problems there.




Tomorrow I'm going to cover Making a throbbing image in Java, because I'm gotten two emails over the last four days asking how to do that. Till then, Cheers!

No comments: