emboss

Emboss a cairo surface

This sample shows how to apply an emboss effect to a cairo A8 image surface.

/*
Adapted from "Fast Embossing Effects on Raster Image Data"

    Original code by: John Schlag, <jfs@kerner.com>
    Adapted code by: Jeremy Moles, <cubicool@gmail.com>

Found in the publication "Graphics Gems IV", Academic Press, 1994
*/

#include <cairo.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define PIXEL_SCALE 255
#define WIDTH_45    1

/* This function will use an exisiting surface's data and create and NEW surface copy
// of that data which has the emboss lighting algorithm applied to it. */
static cairo_surface_t* _create_embossed_surface_from_surface(
    cairo_surface_t* surface,
    double           azimuth,
    double           elevation
) {
    unsigned char* src    = cairo_image_surface_get_data(surface);
    unsigned int   width  = cairo_image_surface_get_width(surface);
    unsigned int   stride = cairo_image_surface_get_stride(surface);
    unsigned int   height = cairo_image_surface_get_height(surface);

    cairo_surface_t* dstSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height);

    unsigned char* dst = cairo_image_surface_get_data(dstSurface);

    /* Compute the light vector from the input parameters.
    // Normalize the length to PIXEL_SCALE for fast shading calculation. */
    long Lx   = cos(azimuth) * cos(elevation) * PIXEL_SCALE;
    long Ly   = sin(azimuth) * cos(elevation) * PIXEL_SCALE;
    long Lz   = sin(elevation) * PIXEL_SCALE;
    long Nz   = (6 * 255) / WIDTH_45;
    long Nz2  = Nz * Nz;
    long NzLz = Nz * Lz;

    unsigned int y = 1;
    unsigned int x = 1;

    /* TODO: Would it be better to fill DST with SRC instead of the Lz value?
    // memcpy(dst, src, stride * height); */
    memset(dst, Lz, stride * height);

    for(y = 1; y < height - 2; y++) {
        for(x = 1; x < stride - 1; x++) {
            unsigned int   offset = (y * stride) + x;
            unsigned char* s1     = src + offset;
            unsigned char* s2     = s1 + stride;
            unsigned char* s3     = s2 + stride;
            unsigned char  shade  = 0;

            /* Compute the normal from the source. The type of the expression
            // before the cast is compiler dependent. In some cases the sum is
            // unsigned, in others it is signed. Ergo, cast to signed. */
            long Nx = (long)(s1[-1] + s2[-1] + s3[-1] - s1[1] - s2[1] - s3[1]);
            long Ny = (long)(s3[-1] + s3[0] + s3[1] - s1[-1] - s1[0] - s1[1]);

            long NdotL = Nx * Lx + Ny * Ly + NzLz;

            /* Shade with distant light source. */
            if(!Nx && !Ny) shade = Lz;

            else if(NdotL < 0) shade = 0;

            else shade = NdotL / sqrt(Nx * Nx + Ny * Ny + Nz2);

            *(dst + offset) = shade;
        }
    }

    return dstSurface;
}

/* This function only supports A8 surfaces at the moment, and delegates work to the
// similarly named function above. */
static cairo_surface_t* create_embossed_surface_from_surface(
    cairo_surface_t* surface,
    double           azimuth,
    double           elevation
) {
    if(cairo_image_surface_get_format(surface) != CAIRO_FORMAT_A8) {
        printf("This example only supports embossing A8 surfaces.\n");
        printf("It shouldn't be too wildy hard to add other format types.\n");

        return 0;
    }

    else return _create_embossed_surface_from_surface(surface, azimuth, elevation);
}

int main(int argc, char** argv) {
    unsigned int w        = 400;
    unsigned int h        = 50;
    unsigned int textSize = 35;
    const char*  text     = "Cairo Emboss!!!";

    cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_A8, w, h);
    cairo_t*         cr      = cairo_create(surface);

    cairo_text_extents_t extents;

    if(argc != 3) {
        printf(
            "usage: %s <int> <int>\nThe two integral arguments passed in are "
            "the light azimuth and elevation values, in angles.\n",
            argv[0]
        );

        goto cleanup;
    }

    cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size(cr, textSize);
    cairo_text_extents(cr, text, &extents);

    cairo_move_to(
        cr,
        ((double)(w - extents.width) / 2.0f) - extents.x_bearing,
        (double)(h - extents.y_bearing) / 2.0f
    );

    cairo_show_text(cr, text);

    cairo_surface_t* emboss = create_embossed_surface_from_surface(
        surface,
        atoi(argv[1]) * (M_PI / 180.0f),
        atoi(argv[2]) * (M_PI / 180.0f)
    );

    if(!emboss) {
        printf("Calling the emboss function failed; cleaning up and leaving.\n");

        goto cleanup;
    }

    cairo_surface_write_to_png(surface, "cairo_emboss_normal.png");
    cairo_surface_write_to_png(emboss, "cairo_emboss_embossed.png");

    printf("Wrote cairo_emboss_{normal,embossed}.png to your local directory.\n");

    cairo_surface_destroy(emboss);

cleanup:
    cairo_surface_destroy(surface);
    cairo_destroy(cr);

    return 0;
}