giovedì 3 gennaio 2008

Drawing pixbuf to Cairo context (2)

On my previous post a told about how to render a png image file to a Cairo context. But allow the user to select only png image file on my GraphicEditor is a very big restriction and does not satisfy me.
The problem is the integration of Cairo on Gtk+, how say this page on live.gnome. But on this page there is also the Python pizza-code for render pixbuf to Cairo context.



On gtkmm we have a set of API for work with Cairo context, the Cairo namespace, the c++ wrapper for the gdk cairo interaction functions.

#include <gdkmm/general.h>


The function we have to use is the set_source_pixbuf, since loading images in this way is slower we first create a new Cairo::Context containing the pixbuf and on the on_expose_event of the gtkmm widget we simply copy the image.

Write the new widget
For a demo we can write a widget that inherits from DrawingArea:

#ifndef IMAGEAREA_H_
#define IMAGEAREA_H_

#include <gtkmm/drawingarea.h>

#include <gdkmm/pixbuf.h>
#include <glibmm/refptr.h>
#include <cairomm/surface.h>

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

bool loadFile(const Glib::ustring& file_name);

protected:
Cairo::RefPtr< Cairo::Context > image_context_ptr_;
Cairo::RefPtr< Cairo::ImageSurface > image_surface_ptr_;
Glib::RefPtr< Gdk::Pixbuf > image_ptr_;
bool loaded_;

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

#endif /*IMAGEAREA_H_*/


Load the image file
We can implement the loadFile methods in this way:

bool ImageArea::loadFile(const Glib::ustring& file_name)
{
// Load pixbuf
image_ptr_ = Gdk::Pixbuf::create_from_file (file_name);

// Detect transparent colors for loaded image
Cairo::Format format = Cairo::FORMAT_RGB24;
if (image_ptr_->get_has_alpha())
{
format = Cairo::FORMAT_ARGB32;
}

// Create a new ImageSurface
image_surface_ptr_ = Cairo::ImageSurface::create (format, image_ptr_->get_width(), image_ptr_->get_height());

// Create the new Context for the ImageSurface
image_context_ptr_ = Cairo::Context::create (image_surface_ptr_);

// Draw the image on the new Context
Gdk::Cairo::set_source_pixbuf (image_context_ptr_, image_ptr_, 0.0, 0.0);
image_context_ptr_->paint();

loaded_ = true;

return true;
}


The function load the image file using the pixbuf class, the create_from_file static method throw an exception if the file does not exist.
After create the pixbuf we can create the Cairo::Context, but we have to detect if the image has transparent color, for use the correct Cairo::Context format. We create a new Cairo::ImageSurface and bind this to a new Cairo::Context. Now we have a new context and we can paint the loaded image using the set_source_pixbuf.

Draw the image on the Widget Context
We can override the on_expose_event in this way:

bool ImageArea::on_expose_event(GdkEventExpose* event)
{
if (!loaded_)
return true;

// Create the context for the widget
Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();

// Select the clipping rectangle
context->rectangle(event->area.x, event->area.y,
event->area.width, event->area.height);

context->clip();

// Store context
context->save();

// Draw the source image on the widget context
context->set_source (image_surface_ptr_, 0.0, 0.0);
context->rectangle (0.0, 0.0, image_ptr_->get_width(), image_ptr_->get_height());
context->clip();
context->paint();

// Restore context
context->restore();

return true;
}



We create the Cairo::Context for the widget, and then we can paint the ImageSurface for loaded image to the widget using the usual way.

Finally a screenshot of my GraphicEditor showing a jpeg image file.

8 commenti:

Anonimo ha detto...

Hello,

Sorry to barge into your blog. I found it while looking for some reference on cairomm.

I see that you are far more knowledgeable than me and I would like to ask you if there is any way to READ the color of a pixel element from a Cairo Surface. I need some sort of "old school" getPixel(cx,cy) to read values from the screen but it seems there is no way to acces the surface "directly", so to speak.

Any insights you might have are much appreciated.

Thanx.

kapo ha detto...

Hi,

I think test a Pixel is not a good idea on Cairo, since it is vector-based and pixels are hidden in the implementation. So, if you can, try a different solution (why you need to get a pixel color?).

The Gdk::Image class of Gtkmm allow to work with pixel. For obtain the Gdk::Image object from the current window use Gdk::Drawable::get_image() and then you can use get_pixel() and put_pixel().
I never use these functions let me know if it work.

bye

Anonimo ha detto...

Hello, and thanx for your answer.

The ideea is I'm porting an old 2d engine of mine to use Cairo as a drawing front-end. I blit images (as a bitmap surface) but also use vector shapes and transformations.

I've looked deeper into it last night and it seems using the surface inner byte-structure isn't possible - the properties are private or protected in cairomm and there's a convoluted syntax in the "pure" C object that cairomm wraps . Also there's no way to control backbuffers and such, so I'll have to rewrite part of my code.

The thing is I found two cairo wrappers for python and ruby that CAN i/o on individual pixels (they both have some {surface}->$pixels{x,y} stuff) but I have no clue as to what code is called by the .pic translators...

Anyway, thanks for your time.

Anonimo ha detto...

Hello again.

I think I solved it. It seems that Cairo isn't the "owner" object of the actual drawing surface. The GTK/GDK instance is. So you can actually grab a rectangular area of "the screen" (the GTK app) into a Gdk::PixBuf, from which you can geta device-independent array of pixies with get_pixels().

Anyway, thanks for your time.

kapo ha detto...

Yes this is correct, the Cairo context perform all operation on the Gdk::Drawable when it was created using Gdk::Drawable::create_cairo_context.

Good work!

Anonimo ha detto...

Should be pretty unlikely to get an answer, but: how would you draw the image to a specific position?
E.g. I want it to be drawn to 100/100?
I think I should use move_to from Cairo::Context, but that doesn't work as expected.

ruben-verweij ha detto...

Thanks for your howto.
However, I can't get it to compile. My compiler is having difficulties with this line:
Gdk::Cairo::set_source_pixbuf(cr, boat, 0.0, 0.0);
The error message is:
error: ‘Gdk::Cairo’ has not been declared
Do you have any idea what I should do?
Thanks in advance!

kapo ha detto...

@ruben-verweij: sorry I see your post only today...the error you give usually is a missing "include" directive

Posta un commento