Jérôme Belleman
Home  •  Tools  •  Posts  •  Talks  •  Travels  •  Graphics  •  About Me

Working with PNGs with libpng

4 Oct 2008

I get to work with the PNG format all the time. I was very pleased when I discovered the libpng C library makes it easy to programmatically work with it too.

I enjoy the decent compression and lossless quality offered by the PNG image format, plus the fact it supports transparency. A word of confession, here, though: libpng is easy to work with only because of how the API is organised, not because it comes with good documentation. All the same, here's a few notes to get the newcomer started. I enjoyed using libpng when I wrote my remote LeCroy WaveJet series oscilloscope client.

1 Foreword

As far as I understood, libpng is the one official PNG library. As a consequence it's widely spread. On various Linux distributions, it comes in packages whose names start with libpng:

2 Getting Started

2.1 Header Files

The /usr/include/png.h header file is all you'll ever need:

#include <png.h>

int main(void)
{

2.2 Open a File

No surprises here:

    FILE *fp = fopen("file.png", "r");

2.3 Initialise the Library

You'll be dealing with two structures most of the time:

    png_structp pngptr =
        png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop pnginfo = png_create_info_struct(pngptr);

You'll see in the documentation there's an extra call to png_create_info_struct() to create something called end_info. Since it's neither told nor clear what it's meant for, just forget it. Libpng doesn't miss it as far as I could experience.

2.4 RGB

Sometimes, you come across colour-indexed PNGs which you might want to convert to RGB. Just call:

    png_set_palette_to_rgb(pngptr);

... to do this.

2.5 Error Handling

It's unclear in the doc whether or not there's anything to set up to later handle errors, especially dealing with twisted things such as setjmp/longjmp. But libpng doesn't seem to miss it, so let's not bother with it. A message will show up anyway when something turns ill.

2.6 Set Up I/O

This is basically useful to say where we read the data from:

    png_init_io(pngptr, fp);

2.7 High-Level Reading

You'll find I've skipped a number of sections in the doc, notably about callbacks called after each row has been read. Forget about it, I didn't need it. Let's move on to actually reading the PNG file to put the data into an intelligible array:

    png_bytepp rows;
    png_read_png(pngptr, pnginfo, PNG_TRANSFORM_IDENTITY, NULL);
    rows = png_get_rows(pngptr, pnginfo);

The doc simply uses an array of png_bytep for the rows. But the compiler will never buy it. So I used a png_bytepp as the API intended in the first place. (I don't understand why the doc allocates HEIGHT lines for this array as function png_get_rows() handles the allocation anyway.) The PNG_TRANSFORM_IDENTITY constant means you apply no transformation.

2.8 Read Pixel Values

You have to read the image on a per-row basis. For each row, there's a series of continuous RGB values for each value:

    for (i = 0; i < HEIGHT; i++) {
        for (j = 0; j < WIDTH * 3; j += 3) {
            printf("%d %d %d ", rows[i][j], rows[i][j + 1], rows[i][j + 2]);
        }   
        printf("\n");
    }
}

If you're dealing with indexed colour images, you'll have only one value per pixel (a palette index).

3 Changing the I/O Method

So far, we've seen how to read PNG data from a file. How to do the same from, say, an array? Take a look at section V. Modifying/Customizing libpng from the official doc. The idea is to call png_set_read_fn() instead of png_init_io():

    png_set_read_fn(pngptr, input, readpng);

The readpng() home-made function is called to write the PNG data. The input variable is actually a pointer to whatever you may need within readpng(). Here, we'll pass an array where the input PNG data is waiting. It may be retrieved calling png_get_io_ptr():

void readpng(png_structp _pngptr, png_bytep _data, png_size_t _len)
{
    static int offset = 0;
    char *input;

    /* Get input */
    input = png_get_io_ptr(_pngptr);

    /* Copy data from input */
    memcpy(_data, input + offset, _len);
    offset += _len;
}

The _data argument is where you're supposed to copy the PNG input data. Note that this function is called multiple times before everything has been read so you'll need to keep track of how many bytes have already been copied and offset the readout or you'll keep reading the same data.

4 Checking the Signature

You should read the first 8 bytes of the file. The doc says that the more you read, the better the signature check is accurate:

    char header[HEADERLEN];
    fread(header, 1, HEADERLEN, fp);    /* #define HEADER 8 */
    int sig = png_sig_cmp(header, 0, HEADERLEN);
    if (sig == 0) {
        /* It's a fine PNG file */
    } else {
        /* It's no valid PNG file */
    }

You have to tell libpng how many bytes you read for this signature check. You may traditionally do so just before a call to png_read_png(), or something:

    png_set_sig_bytes(pngptr, HEADERLEN);