Search code examples
cpnglibpng

Read and write a PNG file using libpng in C


My goal is to read a PNG file, change the pixel values and store the updated PNG file using libpng.

I wrote two functions called read_png and write_png by following the official libpng manual. The example code doesn't change the pixel values, because it's a minimum, reproducible example. It also doesn't check if the input file is actually a PNG file.

#include <png.h>

png_infop info_ptr;
png_bytepp row_pointers;

void read_png(char *file_name)
{
    FILE *fp = fopen(file_name, "rb");
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop info_ptr = png_create_info_struct(png_ptr);
    png_init_io(png_ptr, fp);
    png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
    row_pointers = png_get_rows(png_ptr, info_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    fclose(fp);
}

void write_png(char *file_name)
{
    FILE *fp = fopen(file_name, "wb");
    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_init_io(png_ptr, fp);
    png_set_rows(png_ptr, info_ptr, row_pointers);
    png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(fp);
}

int main(int argc, char *argv[])
{
    read_png(argv[1]);
    write_png(argv[2]);
    return 0;
}

The code compiles without any errors.

$ gcc -o rw_png -lpng rw_png.c

And the program can be run with two arguments.

$ ./rw_png input.png output.png

The problem is, that although output.png is created, it's an empty file instead of a PNG file.

$ file output.png
output.png: empty

Solution

  • libpng uses info_struct to keep around information about what the pixel data looks like. So you need to keep it around for write_png (or recreate it).

    png_infop info_ptr;  // <-- Global info_ptr (good)
    png_bytepp row_pointers; 
    
    void read_png(char *file_name)
    {
        FILE *fp = fopen(file_name, "rb");
        png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        png_infop info_ptr = png_create_info_struct(png_ptr);  // <-- creating a new, local info_ptr 
        png_init_io(png_ptr, fp);
        png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
        row_pointers = png_get_rows(png_ptr, info_ptr);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL); // <-- destroying the info_ptr (as well).
        fclose(fp);
    }
    

    So, a fixed read_png would be:

    void read_png(char *file_name)
    {
        FILE *fp = fopen(file_name, "rb");
        png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        info_ptr = png_create_info_struct(png_ptr);  
        png_init_io(png_ptr, fp);
        png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
        row_pointers = png_get_rows(png_ptr, info_ptr);
        png_destroy_read_struct(&png_ptr, NULL, NULL); 
        fclose(fp);
    }
    

    Assuming a read_png / write_png, you'll free info_ptr in write_png.