[cairo] drawing with a mask

M Joonas Pihlaja jpihlaja at cc.helsinki.fi
Wed Feb 18 14:12:00 PST 2009


Took me a while to realize what was going on... :)

On Wed, 18 Feb 2009, Dan Ventura wrote:

>         finalsurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 200, 200);
>         finalcr = cairo_create (finalsurface);
>         cairo_scale (finalcr, 200, 200);

Looks good so far.

>         color=makecolor(BROWN);               //returns a cairo_pattern_t
>         cairo_set_source(finalcr,color);
>         cairo_paint(finalcr);

Okay, we're all brown.

>         color=makecolor(GREEN);
>         cairo_set_source(finalcr,color);

Setting up for the green.

>         masksurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 200, 200);
>         maskcr = cairo_create (masksurface);
>         cairo_scale (maskcr, 200, 200);
>         cairo_set_source_rgba(maskcr, 0, 0, 0, 0);

You're setting the source to be transparent, okay.

>         cairo_paint(maskcr);                         //make most of mask transparent

Aha, but you're using the default operator OVER with a transparent 
source to clear the surface, but transparent OVER anything is a no-op.  
Not that that actually matters in this particular case. :)  You should 
use CAIRO_OPERATOR_CLEAR for clearing surfaces.

>         cairo_set_source_rgba(maskcr, 1, 1, 1, 1);
>         cairo_rectangle(maskcr, .4, .4, .2, .2);
>         cairo_fill(maskcr);                          //make small square in mask opaque

Okay, looks good.

>         cairo_mask_surface(finalcr,masksurface,0,0);

Ah, but here..  The masksurface doesn't carry the scaling you set on 
the maskcr, so the masksurface is by cairo_mask_surface() as if one of 
its pixels represents one userspace unit square.  Since finalcr has 
scaled userspace by 200 x, what happens is that marksurface is scaled 
up 200 x first and *then* used as the mask.  So the very top left 
pixel of masksurface is scaled up to cover all of finalcr and then 
masked as per the description of cairo_mask_surface().  Of course the 
top left pixel is totally transparent, so nothing happens.

I think the default scaling settings might also explain the gradient 
you mentioned seeing if using an entirely opaque mask. In this case 
the pixels outside the mask are treated as transparent, so when 
scaling up they "leak" into the mask area covered by the top-left 
pixel due to filtering.  The most way to fix all this is to reset the 
current transformation matrix back to the identity before calling 
cairo_mask_surface() or use cairo_push_group/pop_group().

Namely using temporary targets provided by cairo_push_group() and 
cairo_pop_group() instead of an explicit masksurface/maskcr makes the 
code a little clearer I think, and lets you not worry about the 
current userspace -> device space transforms:

	surface = cairo_image_surface_create (
		cr, CAIRO_FORMAT_ARGB32, 200, 200);

	cr = cairo_create (surface);

	cairo_scale (cr, 200, 200);

	cairo_set_source_rgb (cr, ...brown r,g,b...);

	cairo_paint (cr);

	cairo_set_source_rgb (cr, ... green r,g,b...);

	cairo_push_group (cr); {

		cairo_rectangle (cr, 0.2,0.2,0.4,0.4);

		cairo_fill (cr);

	cairo_pattern_t *mask = cairo_pop_group (cr);

	cairo_mask (cr, mask);

	cairo_pattern_destroy (mask);



More information about the cairo mailing list