Loading PNG image with libpng 1.5.12 or later into memory
Autor: Santos | Publicado: 2012-08-27T07:41:00-03:00

I made this tutorial because saw that there is none or little documentation
about the use of libpng on newer versions. Since there are some great changes
in the libpng code, a lot of information may be invalid right now.
Well, first of all, you need to download it, compile and get libpng ready for use. I will
not cover those step in this tutorial.

With everything in place, open your favorite editor and create a header file:

PngLoader.h:


#ifndef __PNGLOADER_H__
#define __PNGLOADER_H__

#include &lt;string&gt;
#include &lt;png.h&gt;

class PngLoader
{
public:
    PngLoader();
    ~PngLoader();

    bool load( std::string file );
    const char *getData() const { return data; }
    int getWidth() const { return width; }
    int getHeight() const { return height; }
    int getDepth() const { return depth; }
    int getColorType() const { return color_type; }

private:
    void release();

    char *data;
    int width;
    int height;
    int depth;
    int color_type;
};

#endif


That is a simple class that will only grant access to read data from memory. You can,
of course, make the changes you want to change the data, for example (if you have the knowledge for this).
Note that there are some other information you can get through libpng about your png image. If you need
such information you will have to add the corresponding attributes and methods on class.

Now lets create the source file.

PngLoader.cpp:


#include "PngLoader.h"

#include &lt;fstream&gt;
#include &lt;string&gt;
#include &lt;sstream&gt;

void readFileCallback( png_structp png_ptr, png_bytep out, png_size_t count );

PngLoader::PngLoader(void)
          :data(0)
{
}

bool PngLoader::load( std::string file )
{
    png_structp png_ptr;
    png_infop info_ptr;
 
    std::ifstream ifile( file.c_str(), std::ios::binary );

    if( !ifile.is_open() )
        return false;

    char assinatura[8];
    ifile.read( &assinatura[0], 8 * sizeof(char) );
 
    if(!png_check_sig( (png_bytep)assinatura, 8) )
    {
        return false;
    }

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);

    if(png_ptr == 0)
    {
        return false;
    }

    info_ptr = png_create_info_struct( png_ptr );

    if( info_ptr == 0 )
    {
        png_destroy_read_struct( &png_ptr, 0, 0 );
        return false;
    }

    png_set_read_fn( png_ptr, (void*)&ifile, readFileCallback );

    png_set_sig_bytes( png_ptr, 8 );

    png_read_info( png_ptr, info_ptr ); 

    png_get_IHDR( png_ptr, info_ptr, (png_uint_32*)&width, (png_uint_32*)&height,
                  &depth, &color_type, 0, 0, 0 );

    png_size_t cols = png_get_rowbytes(png_ptr, info_ptr);

    png_bytepp row_pp = new png_bytep[height];
    data = new char[ cols * height ];

    for( int i = 0; i < height; ++i )
    {
        row_pp[height - i - 1] = (png_bytep)&data[ i * cols ];
    }

    png_read_image( png_ptr, row_pp );
    png_read_end( png_ptr, info_ptr );

    png_destroy_read_struct( &png_ptr, &info_ptr, 0 );

    delete[] row_pp;
    return true;
}

void PngLoader::release()
{
    if( data )
        delete[] data;
    data = 0;
}

PngLoader::~PngLoader(void)
{
    release();
}

void readFileCallback( png_structp png_ptr, png_bytep out, png_size_t count )
{
    png_voidp io_ptr = png_get_io_ptr( png_ptr );
 
    if( io_ptr == 0 )
    {
        return;
    }

    std::ifstream &ifs = *(std::ifstream*)io_ptr;

    ifs.read( (char*)out, count );
}


Here the code may seem a little confusing. The libpng guys chosen big names for their functions and data structures.
But this way, if you have some pure C programming skills, you know that things get easer to understand.
On the constructor there is no special code.
The load method is the bigger one, so lets start by seeing other parts of the code.
The release method is the one that will release the data attribute from memory. The user will can
call it right after use the data of the image. There is nothing hard to understand here,
there is only a check to ensure we will not try to delete from null pointer.
The destructor does only a call to the release method, so, if the user forget to call the release method, the data attribute
memory will be released right when the object is no longer necessary and get destructed.

Well, time to the load method. We start by declaring some local variables. The most
importants variables are png_ptr and info_ptr. The variable png_ptr is used by libpng to decode the image,
while info_ptr is used to keep information about the image. Since libpng version 1.5.0 the
direct acess for those structures members were deprecated. So you will need to use the png_get_*
and png_set_* functions, where the star means the member on the structure that you need to get from or set to.
The next step is to open the image file, what is done by the ifstream object ifile. So its passed, on the instantiation
of the object, the filename string and the access mode to the binary file.
With the image file opened and checked for success, we verify now the png file signature. What is done by png_check_sig function.
For this, we pass a little buffer of eight bytes, that is the size for the png signature.
Since the image file is ready to be read, it's allocated the png_ptr structure through the function png_create_read_struct.
After this, its allocated the info_ptr structure with png_create_info_struct. If everything is ok it's time for the next step.
Now we tell to libpng that we have one function callback for the image reading. This way you can read the image as you want. On this tutorial we
will be using the ifstream class from the C++ STL. So we also pass a pointer for the object. And the final
function call is:

png_set_read_fn( png_ptr, (void*)ifile, readFileCallback );

Where png_ptr is our png decode pointer; ifile is the ifstream reader object and readFileCallback is the function that will read every single
portion or chunk of the image file.

To better understanding of this function, let's see it in details.


void readFileCallback( png_structp png_ptr, png_bytep out, png_size_t count )
{
    png_voidp io_ptr = png_get_io_ptr( png_ptr );

    if( io_ptr == 0 )
    {
        return;
    }

    std::ifstream &ifs = *(std::ifstream*)io_ptr;

    ifs.read( (char*)out, count );
}


The function need to have the exact above signature. It accept three arguments. The first one is png_struct pointer, png_ptr. The png_byte pointer, out, is the pointer
to the read content inside the function. And the number of bytes to read is given by count argument of png_size_t type.
The first thing to do is to retrieve the pointer to our ifstream object. So we use the png_get_io_ptr, to get the io_ptr member of the png_ptr structure.
After verify if the pointer is set, it's created a ifstream object that points to io_ptr. So a cast is used.
At this point we only need to give to the out buffer the read content. So we read count bytes from the file using the read method of the ifstream
class and save the content into the out buffer.
One important thing to know here is that the ifstream object ifs points to the ifile object set before. And the read method will read and set the read position
on the ifstream object. So, on the next reading, it continue at the point it stopped before, and so on until the end of the file.

On the next step, into the load method, we need to tell libpng that the png signature has been already read. So the following function is called:

png_set_sig_bytes( png_ptr, 8 );

This function set the png_ptr sig_bytes member to 8. And it's how png knows that eight bytes has been read.

It is time to read the image information. And this is a job for the png_read_info function.

png_read_info( png_ptr, info_ptr );

Nothing special, but as said before, the read content will be into the info_ptr struct.
We get some information from the file. And the information we need is into the IHDR chunk. So we call:

png_get_IHDR( png_ptr, info_ptr, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color_type, 0, 0, 0 );

IHDR is a member of the info_ptr structure, so we pass first the png_sctruct pointer, then the info_struct pointer. All the others arguments
are for the wanted information. We only need the image width, height, depth and color type for the most image jobs (Create a OpenGL texture
or draw it with some graphical API that support reading of image from memory, for example).

Now, we need to get the number of bytes that is on each row of the image data.

png_size_t cols = png_get_rowbytes(png_ptr, info_ptr);

The number of bytes returned will depend on the channels of colors your image have. So, for example,
if your image have 512 of width and four channels (RGBA) the number of bytes per row will be 512 * 4 = 2048 bytes.

With this information we can allocate memory for the image data. But first, we create an array of byte pointers with height rows.

png_bytepp row_pp = new png_bytep[height];

The png_bytep is the same as png_byte* and png_bytepp is the same as png_byte**.

Then, we allocate the space in which our image data will be in.
data = new char[ cols * height ];

This time we will set the row_pp pointers of each line to point to a location on the data attribute. Since the data from libpng come in a matrix form, it's
needed to convert the pointers from bidimensional to unidimensional.


for( int i = 0; i < height; ++i )
{
    row_pp[height - i - 1] = (png_bytep)&data[ i * cols ];
}


What is done on this loop is go into each row, reverse order, and point to the corresponding adress position on the data array. The cast is used because
libpng need png_byte type do read the data and I need a char array. For some reason libpng returns an image data with the rows on the reversed order,
so row_pp is mapped on reverse order what make it be on the right way.

See on the image:


Finally, we can get the image data from the png file and store it into the data attribute array through row_pp:

png_read_image( png_ptr, row_pp );

The png_read_end function, called after png_read_image, will only read the remainder data on the file.

After all this steps we already have the image data. And the data attribute have the image data content.

It's the cleanup time. First, we clean the png_ptr and info_ptr structures:


png_destroy_read_struct( &png_ptr, &info_ptr, 0 );


The third argument is for info at the end of the file, as we did not used it, we keep it null at the function call.

The next thing to cleanup is row_pp, since I will not need it anymore. Note that row_pp is only an array of pointers used to point to the data attribute positions.


delete[] row_pp;