Converting Cairo output to PNG

This is an example program of converting the output of Cairo into a PNG using the C PNG library libpng without using the Cairo PNG writing functions. The PNG writing code is much the same as another example on this site, Write a PNG file using C and libpng. This program creates an image like the one on the right called file.png.

In this example, the PNG input data is pointed directly to the Cairo output data and then transformed using the libpng option PNG_TRANSFORM_BGR.

I did this because I wanted to add text information to the PNG output by Cairo. I originally thought there would be a way to add information using Cairo's interface, but according to the answer to a query on the Cairo mailing list, no such ability exists. See also Create a PNG with text segments.

/* This example program shows how to write the data output by the
   Cairo library into a PNG file without using the Cairo library's
   PNG-writing functions, using the libpng functions alone. */

#include <stdlib.h>
#include <png.h>
#include <cairo.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

typedef struct 
{
    /* The image data. */
    unsigned char * data;
    int height;
    int width;
    /* The number of bytes in one pixel. */
    int pixel_bytes;
}
bitmap_t;

#define CLEAN_UP						\
    if (png_ptr) {						\
	png_destroy_write_struct (& png_ptr, & info_ptr);	\
    }								\
    if (row_pointers) {						\
	free (row_pointers);					\
    }								\
    if (fp) {							\
	fclose (fp);						\
    }

#define FAIL(test)					\
    if (test) {						\
	fprintf (stderr, "%s:%d: failed test '%s'",	\
		 __FILE__, __LINE__, #test);		\
	CLEAN_UP;					\
	return -1;					\
    }

/* Write "bitmap" to a PNG file specified by "path". This function
   returns 0 on success, and non-zero on error.  This is based on the
   code at 'http://www.lemoda.net/png/c-write-png/' */

int
save_png_to_file (const bitmap_t * bitmap, const char *path)
{
    FILE * fp;
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    size_t y;
    png_byte ** row_pointers = NULL;
    int depth = 8;
    
    fp = fopen (path, "wb");
    FAIL (! fp);
    png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    FAIL (! png_ptr);
    info_ptr = png_create_info_struct (png_ptr);
    FAIL (! info_ptr);
    
    /* Set up error handling. */

    if (setjmp (png_jmpbuf (png_ptr))) {

	/* We arrive here if an error occurred in the PNG library. */

	fprintf (stderr, "An error occurred in the PNG library.\n");
	CLEAN_UP;
	return -1;
    }
    
    /* Set image attributes. */

    png_set_IHDR (png_ptr,
                  info_ptr,
                  bitmap->width,
                  bitmap->height,
                  depth,
                  PNG_COLOR_TYPE_RGBA,
                  PNG_INTERLACE_NONE,
                  PNG_COMPRESSION_TYPE_DEFAULT,
                  PNG_FILTER_TYPE_DEFAULT);
    
    /* Initialize rows of PNG. */

    row_pointers = malloc (bitmap->height * sizeof (png_byte *));
    FAIL (! row_pointers);
    for (y = 0; y < bitmap->height; ++y) {
        row_pointers[y] = bitmap->data
                        + bitmap->width * bitmap->pixel_bytes * y;
    }
    
    /* Write the image data to "fp". */

    png_init_io (png_ptr, fp);
    png_set_rows (png_ptr, info_ptr, row_pointers);

    /* The option PNG_TRANSFORM_BGR is necessary to convert the Cairo
       format for the red, green, blue data into the PNG format. */

    png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL);

    CLEAN_UP;
    return 0;
}

int main ()
{
    int SIZEX = 80;
    int SIZEY = 80;
    char * fname = "file.png";
    cairo_t *c;
    cairo_surface_t *cs;
    bitmap_t bitmap;
    int rv;

    cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, SIZEX, SIZEY);
    c = cairo_create (cs);

    /* Draw some coloured rectangles in the image. */

    cairo_rectangle (c, 2.5, 0.0, SIZEX - 1.0, SIZEY / 2.0);
    cairo_set_source_rgb (c, 0.1, 0.0, 0.7);
    cairo_fill (c);
    cairo_rectangle (c, 0.0, SIZEY / 2.0, SIZEX / 2.0, SIZEY / 2.0);
    cairo_set_source_rgb (c, 1.0, 1.0, 0.0);
    cairo_fill (c);
    cairo_rectangle (c, SIZEX / 3.0, SIZEY / 3.0,
                     (SIZEX) / 3.0, (SIZEY) / 3.0);
    cairo_set_source_rgb (c, 1.0, 0.0, 0.0);
    cairo_fill (c);

    /* We have to call this before reading the data. */

    cairo_surface_flush (cs);
    bitmap.data = cairo_image_surface_get_data (cs);
    bitmap.pixel_bytes = 4;
    bitmap.width = SIZEX;
    bitmap.height = SIZEY;
    rv = save_png_to_file (& bitmap, fname);
    if (rv != 0) {
	fprintf (stderr, "Failed to write PNG to file.\n");
    }
    cairo_surface_destroy (cs);
    return 0;
}

Here is a makefile to compile this on a Unix-like system:

CAIRO_LD_FLAGS=`pkg-config --libs cairo`
PNG_LD_FLAGS=-L /usr/local/lib -l png
CAIRO_INC_FLAGS=`pkg-config --cflags cairo`
PNG_INC_FLAGS=-I /usr/local/include
CFLAGS=-Wall -g
OBJS=cairo-png.o

file.png:       cairo-png
        ./cairo-png

cairo-png:      $(OBJS)
        $(CC) $(CFLAGS) -o $@ $(OBJS) $(CAIRO_LD_FLAGS) $(PNG_LD_FLAGS)

cairo-png.o: cairo-png.c
        $(CC) $(CFLAGS) -c -o $@ cairo-png.c $(CAIRO_INC_FLAGS) $(PNG_INC_FLAGS)

clean:
        rm -f file.png cairo-png $(OBJS)

Copyright © Ben Bullock 2009-2023. All rights reserved. For comments, questions, and corrections, please email Ben Bullock (benkasminbullock@gmail.com) or use the discussion group at Google Groups. / Privacy / Disclaimer