1
votes

I've been working on gtk+/cairo application where I need to display an image on a drawing area and draw some text and lines on top of it. I already made the functions that draw all the necessary graphics into the cairo context, but the image needs to be scaled in order to fit into the drawing area (and also to avoid scaling issues when changing the window's size).I planned on doing this by just using the cairo_scale function, but this option seems to be rather slow.

Here's how I'm currently scaling the cairo context:

***
typedef struct diagram_properties_{
    cairo_surface_t * surface;
    d_tank_ tanks[DIAGRAM_TANK_COUNT];
    d_valve_ valves[DIAGRAM_VALVE_COUNT];
    d_info_ infos[DIAGRAM_INFO_COUNT];
    int last_error_;
    double width, height;
    bool is_saved;
    GtkDrawingArea * drawing_area;
}diagram_properties_;
***
gboolean on_draw_diagram_event(GtkWidget *widget, cairo_t *cr, gpointer user_data){
    //Initialize diagram (d_properties)
    draw_diagram(cr, &d_properties);
    return FALSE;
}
***
void draw_diagram( cairo_t *cr, diagram_properties_ *diagram ){

    GtkWidget * da_widget = GTK_WIDGET(diagram->drawing_area);
    gtk_widget_queue_draw(da_widget);
    GtkAllocation allocation;
    gtk_widget_get_allocation(da_widget, &allocation);
    diagram->width = cairo_image_surface_get_width(diagram->surface);
    diagram->height = cairo_image_surface_get_height(diagram->surface);
    cairo_scale(cr, allocation.width / diagram->width, allocation.height /     diagram->height);
    cairo_set_source_surface(cr, diagram->surface, 0, 0);
    cairo_paint(cr);
    update_diagram_tanks( cr, diagram );
    update_diagram_valves( cr, diagram );
    update_diagram_info( cr, diagram );
}
***

The PNG file with the diagram is used to create the diagram surface at the beginning of the code. So my question is, is there a way to save the cairo context on a buffer and only scale it whenever the window's dimensions change? I'm already trying to scale the surfaces themselves instead, as well as the coordinates of the drawings and text, but I'd rather avoid doing so if there's a better way to do it available.

1

1 Answers

0
votes

You could do a combination of cairo_push_group(), cairo_get_group_target() and cairo_pop_group_to_source()+cairo_paint() to draw your scaled content.

Pseudo-Code:

static cairo_surface_t *cache_surface = NULL;
static double cache_width = -1, cache_height = -1;

// s must be an image surface and it must always be the same image surface (= content must not change)
void draw_at_size(cairo_t *cr, cairo_surface_t *s, double width, double height)
{
  double diagram_width = cairo_image_surface_get_width(s);
  double diagram_height = cairo_image_surface_get_height(s);
  cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
  if (width == cache_width && height == cache_height) {
    cairo_set_source_surface(cr, cache_surface, 0, 0);
    cairo_paint(cr);
  } else {
    cairo_push_group(cr);
    cairo_scale(cr, width / diagram_width, height / diagram_height);
    cairo_set_source_surface(cr, surface, 0, 0);
    cairo_paint(cr);

    cairo_surface_destroy(cache_surface);
    cache_surface = cairo_reference(cairo_get_group_target(cr));
    cache_width = width;
    cache_height = height;

    cairo_pop_group_to_source(cr);
    cairo_paint(cr);
  }
}

No idea if this is really faster, but it means that the surface is only scaled once. Of course you can also extend this by drawing other stuff onto the scaled surface.

However, that is basically the same as allocating another surface yourself. The only difference is that the surface allocation is hidden and that you might get a "better" surface, e.g. I guess you are currently allocating an image surface for the temporary surface, but cairo would use an X11 surface on X11.