cairo blur image surface

This really should have been included in cairo. Instead, everyone that wants to have shadows has to roll their own blur function. Here's my take on it. I'll even release this into the public domain.

This is used in the back-end for www.websequencediagrams.com.

// to build:
// gcc -I/usr/include/cairo -lcairo -o blur blur.c

#include 
#include 
#include 
#include "cairo.h"

void cairo_image_surface_blur( cairo_surface_t* surface, double radius )
{
    // Steve Hanov, 2009
    // Released into the public domain.
    
    // get width, height
    int width = cairo_image_surface_get_width( surface );
    int height = cairo_image_surface_get_height( surface );
    unsigned char* dst = (unsigned char*)malloc(width*height*4);
    unsigned* precalc = 
        (unsigned*)malloc(width*height*sizeof(unsigned));
    unsigned char* src = cairo_image_surface_get_data( surface );
    double mul=1.f/((radius*2)*(radius*2));
    int channel;
    
    // The number of times to perform the averaging. According to wikipedia,
    // three iterations is good enough to pass for a gaussian.
    const MAX_ITERATIONS = 3; 
    int iteration;

    memcpy( dst, src, width*height*4 );

    for ( iteration = 0; iteration < MAX_ITERATIONS; iteration++ ) {
        for( channel = 0; channel < 4; channel++ ) {
            int x,y;

            // precomputation step.
            unsigned char* pix = src;
            unsigned* pre = precalc;

            pix += channel;
            for (y=0;y0) tot+=pre[-1];
                    if (y>0) tot+=pre[-width];
                    if (x>0 && y>0) tot-=pre[-width-1];
                    *pre++=tot;
                    pix += 4;
                }
            }

            // blur step.
            pix = dst + (int)radius * width * 4 + (int)radius * 4 + channel;
            for (y=radius;y= width ? width - 1 : x + radius;
                    int b = y + radius >= height ? height - 1 : y + radius;
                    int tot = precalc[r+b*width] + precalc[l+t*width] - 
                        precalc[l+b*width] - precalc[r+t*width];
                    *pix=(unsigned char)(tot*mul);
                    pix += 4;
                }
                pix += (int)radius * 2 * 4;
            }
        }
        memcpy( src, dst, width*height*4 );
    }

    free( dst );
    free( precalc );
}

int main(int argc, char* argv[])
{
    cairo_surface_t* surface;
    cairo_t* ctx;
    cairo_text_extents_t text_extents;
    cairo_font_extents_t font_extents;
    double FontSize = 100;
    double radius = 7;
    double width, height;

    if ( argc != 3 ) {
        printf("Syntax: %s  ""n", argv[0]);
        return -1;
    }

    // Get text size.
    surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, 10, 10 );
    ctx = cairo_create( surface );
    cairo_set_font_size( ctx, FontSize );

    cairo_font_extents( ctx, &font_extents );
    cairo_text_extents( ctx, argv[2], &text_extents );

    height = font_extents.ascent + font_extents.descent + radius * 2;
    width = text_extents.x_advance + radius * 2;

    cairo_destroy( ctx );
    cairo_surface_destroy( surface );

    // Draw text.
    surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );

    ctx = cairo_create( surface );
    cairo_set_font_size( ctx, FontSize );

    cairo_move_to( ctx, 0 + radius, font_extents.ascent + radius );
    cairo_show_text( ctx, argv[2] );
    cairo_fill( ctx );

    cairo_image_surface_blur( surface, 5 );

    cairo_move_to( ctx, 0, font_extents.ascent );
    cairo_show_text( ctx, argv[2] );
    cairo_fill( ctx );

    cairo_destroy( ctx );

    cairo_surface_write_to_png( surface, argv[1] );

    cairo_surface_destroy( surface );

    return 0;
}

Comments