[cairo] drag selection box over image surface

Peter Groves pdgroves at gmail.com
Fri Mar 7 21:22:04 PST 2008


On Sat, 08 Mar 2008 18:30:28 +1300
"Rick W. Chen" <stuffcorpse at gmail.com> wrote:

> Hello, I'm new here (and to cairo too). I'm trying to draw a selection
> box while the mouse is click-dragged on an image surface. Hope I have
> the right mailing list. If not let me know where I should bugger off to.
> 

This is as good a place as any :)

> Right now the box is drawn, but if the mouse moves (still dragging)
> back, all previous boxes which are not painted over (because the current
> box is smaller) remains. I can queue_draw() after painting the box each
> time. That gives the desired effect, but it becomes noticeably slower
> because on expose event, the image gets scaled to fit the gtk widget.
> Plus it makes flickers.
> 
> Not sure if I have got the idea right in what I'm trying to do. If
> someone can help me with this problem I would really appreciate it.
> 

I have an application that does something very similar. Unfortunately it's using the ruby bindings so I won't attach it. What I can tell you though is that your approach is correct except you need to save the background image in a buffer in it's final form (with all scaling and clipping done) and repaint that (plus the rectangles) on each mouse motion event. The most straightforward way is to draw the background to an ImageSurface and then draw that and then the rectangle onto the DrawingArea's context on each motion event. You can also create the surface to use as a buffer using create_similar_surface, but that's an optimization you may not need to bother with if ImageSurface performs well enough.

Since I get good performance (no flickering) even when using the ruby bindings (ruby is a slow language), I'm sure you'll get at least decent performance.

The complicated part is that you have to make sure the saved ImageSurface is replaced and redrawn every time the gtk window is resized with a new ImageSurface of the correct dimensions. 

Also, I should mention that if you do all the background drawing onto an ImageSurface, you only need to scale the cairo context when you're drawing to that. When you draw the ImageSurface onto the DrawingArea and then draw the rectangles, you should be able to stay in the default pixel coordinates. This will also reduce the amount of work cairo has to do on each redraw from the mouse motion events.

Let me know if that doesn't make sense. Good luck.

-Peter

> Below are some code related to this. paint_selection draws the box, and
> on_event overloads the one from the widget (a DrawingArea).
> 
> void ImageArea::paint_selection(Cairo::RefPtr<Cairo::Context> cr)
> {
>     if (!active)
>         return;
> 
>     cr->save();
>     cr->set_line_width(2);
>     cr->rectangle(x, y, w, h);
>     cr->set_source_rgba(1, 1, 0.74, 0.2);
>     cr->fill_preserve();
>     cr->set_source_rgba(1, 1, 0.74, 0.5);
>     cr->stroke();
>     cr->restore();
> }
> 
> bool ImageArea::on_event(GdkEvent *event)
> {
>     if (!loaded_)
>         return true;
> 
>     GdkEventButton *bevent;
>     GdkEventMotion *mevent;
>     GdkEventExpose *xevent;
> 
>     int width  = get_allocation().get_width();
>     int height = get_allocation().get_height();
> 
>     // Create the context for the widget
>     Cairo::RefPtr<Cairo::Context> cr = get_window()->create_cairo_context();
> 
>     switch((gint)event->type)
>     {
>         case Gdk::BUTTON_PRESS:
>             bevent = (GdkEventButton *) event;
>             if(!select.active)
>             {
>                 select.active = TRUE;
>                 select.x = bevent->x/scaled_res.x;
>                 select.y = bevent->y/scaled_res.y;
>                 select.w = 0;
>                 select.h = 0;
>                 queue_draw();
>             }
>             break;
> 
>         case Gdk::BUTTON_RELEASE:
>             bevent = (GdkEventButton *) event;
>             select.active = FALSE;
>             queue_draw();
>             break;
> 
>         case Gdk::MOTION_NOTIFY:
>             mevent = (GdkEventMotion *) event;
>             if(select.active)
>             {
>                 select.w = mevent->x/scaled_res.x - select.x;
>                 select.h = mevent->y/scaled_res.y - select.y;
> 
>                 cr->rectangle (0.0, 0.0, width, height);
>                 cr->scale( scaled_res.x, scaled_res.y );
>                 cr->clip();
> 
>                 cr->save();
>                 cr->set_source (image_surface_ptr_, 0.0, 0.0);
>                 cr->rectangle (select.x, select.y, select.w, select.h);
>                 cr->clip();
> 
>                 cr->paint();
>                 select.paint_selection(cr);
> 
>                 cr->restore();
>             }
>             break;
> 
>         case Gdk::EXPOSE:
>             xevent = (GdkEventExpose *) event;
> 
>             // Select the clipping rectangle
>             cr->rectangle(xevent->area.x, xevent->area.y,
>                           xevent->area.width, xevent->area.height);
> 
>             // Scale image to fit drawing surface
>             cr->scale( scaled_res.x, scaled_res.y );
>             cr->clip();
> 
>             cr->save();
>             // Draw the source image on the widget context
>             cr->set_source (image_surface_ptr_, 0.0, 0.0);
>             cr->rectangle (0.0, 0.0, image.width, image.height);
>             cr->clip();
>             cr->paint();
>             cr->restore();
> 
>     }
>     return true;
> }
> 
> -- 
>   Rick
> _______________________________________________
> cairo mailing list
> cairo at cairographics.org
> http://lists.cairographics.org/mailman/listinfo/cairo


More information about the cairo mailing list