Creating PNGs with libPNG
The libPNG
library is a C library for creating
Portable Network Graphics (PNG)
image files. This page provides a simple example of using the libPNG
library.
Writing PNG files
An example programme that uses libPNG
is linked to below. There are two main stages.
The first is generating an image that is stored in memory. In this case a simple fractal is
generated. The image is held in a 1D float array of length width x height. The second stage is then to
write the image to an actual file.
> gcc -lm -lpng -o makePNG makePNG.c > ./makePNG output.png
If you're using Ubuntu, you'll probably need to install libpng-dev
. You might also need to try a different compile command:
> sudo apt-get install libpng-dev > gcc -o makePNG makePNG.c -lm -lpng > ./makePNG output.png
The part of the programme that performs the actual writing of the PNG file is described below.
This is just the declaration of a number of variables. Note that the png.h
header file defines a
set of pointer types; png_ptr
, info_ptr
, row
are actually pointers.
int writeImage(char* filename, int width, int height, float *buffer, char* title) { int code = 0; FILE *fp = NULL; png_structp png_ptr = NULL; png_infop info_ptr = NULL; png_bytep row = NULL;
Now we open the file that the image will be written to. There's also a check to make sure the file opened was successful.
// Open file for writing (binary mode) fp = fopen(filename, "wb"); if (fp == NULL) { fprintf(stderr, "Could not open file %s for writing\n", filename); code = 1; goto finalise; }
Two libPNG
structures are allocated and initialised.
- The write structure contains information about how the PNG file will be written (or read).
- The info structure contains information about the PNG image that will be written into the actual file. This allow programmes to find out characteristics of the image.
// Initialize write structure png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { fprintf(stderr, "Could not allocate write struct\n"); code = 1; goto finalise; } // Initialize info structure info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { fprintf(stderr, "Could not allocate info struct\n"); code = 1; goto finalise; }
This is a form of exception handling for C. Basically, after this little block of code, if any libPNG function fails,
execution will jump back to the setjmp
function with a non-zero value. The if
statement is then entered.
Within this example code, the jump point is only set once at this point. Therefore, if an
'exception' occurs, it is not possible to determine from which libPNG
function it was
thrown. However, it is possible to repeat this block before each libPNG
function call,
defining a new point to jump back to with an appropriate response.
// Setup Exception handling if (setjmp(png_jmpbuf(png_ptr))) { fprintf(stderr, "Error during png creation\n"); code = 1; goto finalise; }
Various meta data for the image is now set, such as the size and the colour depth per channel.
A further piece of meta information is also set, an image title. There are various other bits of standard text that can be set, such as an author.
png_init_io(png_ptr, fp); // Write header (8 bit colour depth) png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); // Set title if (title != NULL) { png_text title_text; title_text.compression = PNG_TEXT_COMPRESSION_NONE; title_text.key = "Title"; title_text.text = title; png_set_text(png_ptr, info_ptr, &title_text, 1); } png_write_info(png_ptr, info_ptr);
Now the image data is written one row at a time. A single row buffer is created which is of the correct format. For each row, the floating-point image data is converted and written into the row buffer.
// Allocate memory for one row (3 bytes per pixel - RGB) row = (png_bytep) malloc(3 * width * sizeof(png_byte)); // Write image data int x, y; for (y=0 ; y<height ; y++) { for (x=0 ; x<width ; x++) { setRGB(&(row[x*3]), buffer[y*width + x]); } png_write_row(png_ptr, row); } // End write png_write_end(png_ptr, NULL);
The last stage is just some cleaning up. This point is jumped to if there has been an error.
finalise: if (fp != NULL) fclose(fp); if (info_ptr != NULL) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL); if (row != NULL) free(row); return code; }
After executing the programme, the result is a PNG file that should look like the one below.
Page Revisions
Rev Number | Date | Details |
---|---|---|
1.1 | 27/06/2013 | Corrected fractal code in example. Many thanks to Jan-Oliver Frohlich for pointing the mistakes out. |
1.2 | 18/07/2015 | Properly initialised some variables to NULL. Many thanks to Daniel Gibson for this correction. |