N.B. this little trick isn't for everyone, but for some things it can really speed up your plotting code.
I am writing a widget to draw scattergrams. Each point on the scattergram is a little tiny circle and I have to draw thousands of them. Using straight X11 and drawing each arc individually is actually really quick, but a naïve port to Cairo, where each circle is individually stroked is really slow. Reaaaally slow.
Enter masking. In Cairo's language a mask is something you apply a source to to draw on a surface. In effect, you can think of the mask as a stencil (or masking tape); that you spray paint in your source colour over; to produce an image on your drawing surface. Your mask is an 8-bit alpha channel, the more opaque each pixel the more colour gets through to the target surface.
To create a mask, you want to use the same type of surface as your target surface (e.g. an XlibSurface). Use cairo_surface_create(). The content is ALPHA. You can then draw in your mask like a regular surface. To paint your mask onto the target surface use cairo_mask_surface(), the source colour/pattern defines what colour/pattern will be applied through your mask onto the target surface.
For example here is a naïve example repeatedly using cairo_arc() and cairo_stroke():
static void
expose_da1 (GdkWindow *window)
{
cairo_t *cr = gdk_cairo_create (GDK_DRAWABLE (window));
double x, y;
cairo_set_line_width (cr, 1.0);
for (x = 0; x < SIZE; x += INC)
{
for (y = 0; y < SIZE; y += INC)
{
cairo_arc (cr, x+DIAM/2., y+DIAM/2., (DIAM-1)/2., -M_PI, M_PI);
cairo_stroke (cr);
}
}
cairo_destroy (cr);
}
Here is the same image but using a mask that we prepare in advance:
static void
expose_da3 (GdkWindow *window)
{
cairo_t *cr = gdk_cairo_create (GDK_DRAWABLE (window));
double x, y;
/* create the masking surface */
cairo_surface_t *circ = cairo_surface_create_similar (
cairo_get_target (cr), CAIRO_CONTENT_ALPHA,
DIAM, DIAM);
cairo_t *cr2 = cairo_create (circ);
cairo_set_line_width (cr2, 1.0);
cairo_arc (cr2, DIAM/2., DIAM/2., (DIAM-1)/2., -M_PI, M_PI);
cairo_stroke (cr2);
cairo_destroy (cr2);
for (x = 0; x < SIZE; x += INC)
{
for (y = 0; y < SIZE; y += INC)
{
cairo_mask_surface (cr, circ, x, y);
}
}
cairo_destroy (cr);
cairo_surface_destroy (circ);
}
()
So how much faster does it get? Well, the exact answer depends on your graphics hardware and video driver, but on my laptop, the Cairo implementation went from taking 0.72s to 0.08s. The straight X11 implementation takes 0.007s, but of course is not antialiased.
Masking works best at integer offsets and 1:1 scaling, it may actually be slower than individual stroking if you don't have these.
As for my widget? Well, that's a funny story, after sorting out this test case I ported the code into my widget and for my troubles received a factor of 2 slowdown! (but I've got a theory about what's causing this which I'll test out tomorrow).
(Big thanks to behdad and #cairo for help with my test cases today).
阅读(1280) | 评论(0) | 转发(0) |