Monday, April 23, 2012

libPNG integration

I decided to integrate libPNG while I was tracking a memory leak that seemed to originate from the SOIL image library. Later it turned out that the leak was caused by my engine code not by SOIL, but still, integrating libPNG was the right thing to do. Not only the integration went very smooth, it also worked on iPhone on the first try.

The main advantage of libPNG is that it's designed to let you customize memory allocation and file i/o functions, in contrast with SOIL. This made the integration very smooth, as I did not need to modify a single line of code in libPNG to make it use my custom allocator and File class. Here is my full PNGLoader class, which returns the texture data needed by glTexImage2D. It is largely inspired from the PNG loading code from Morten Nobel's blog.


/* 

Amine Rehioui
Created: April 22nd 2012

*/

#include "Precompiled.h"

#include "PNGLoader.h"

#include "File.h"

#include "png.h"
#include "pnginfo.h"

namespace shoot
{
    //! PNG malloc
    png_voidp PNGMalloc(png_structp png_ptr, png_size_t size)
    {
        return snew u8[size];
    }

    //! PNG free
    void PNGFree(png_structp png_ptr, png_voidp ptr)
    {
        delete[] (u8*)ptr;
    }

    //! PNG error
    void PNGError(png_structp png_ptr, png_const_charp msg)
    {
        SHOOT_ASSERT(false, msg);
    }

    //! PNG warning
    void PNGWarning(png_structp png_ptr, png_const_charp msg)
    {
        SHOOT_WARNING(false, msg);
    }

    //! PNG read
    void PNGRead(png_structp png_ptr, png_bytep data, png_size_t length)
    {
         File* pFile = (File*)png_get_io_ptr(png_ptr);
         pFile->Read(data, length);
    }

    //! loads a texture
    u8* PNGLoader::Load(const char* strPath, s32& width, s32& height, s32& channels)
    {
         File* pFile = File::Create(strPath);
         pFile->Open(File::M_ReadBinary);

         png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, 
         /*error_ptr*/NULL,
         PNGError,
         PNGWarning,
         /*mem_ptr*/NULL, 
         PNGMalloc,
         PNGFree);

         SHOOT_ASSERT(png_ptr, "png_create_read_struct_2 failed");

         png_infop info_ptr = png_create_info_struct(png_ptr);
         SHOOT_ASSERT(info_ptr, "png_create_info_struct failed");

         png_set_read_fn(png_ptr, pFile, PNGRead);

         u32 sig_read = 0;
         png_set_sig_bytes(png_ptr, sig_read);
  
         png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);

         width = info_ptr->width;
         height = info_ptr->height;
         switch (info_ptr->color_type)
         {
         case PNG_COLOR_TYPE_RGBA:
             channels = 4;
         break;

         case PNG_COLOR_TYPE_RGB:
             channels = 3;
         break;

         default: SHOOT_ASSERT(false, "Unsupported PNG format");
         }

         u32 row_bytes = png_get_rowbytes(png_ptr, info_ptr);        
         png_bytepp row_pointers = png_get_rows(png_ptr, info_ptr);

         u8* data = snew u8[row_bytes * height];  
         for (s32 i=0; i<height; ++i)
         {
              memcpy(data+(row_bytes*i), row_pointers[i], row_bytes);
         }

         png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
         pFile->Close(); 
         delete pFile;
         return data;
    }
}

The drawback is that libPNG obviously does not support formats other than PNG, in contrast with SOIL which could also do JPG, BMP, TGA. But the smooth integration and the fact that it also worked on iPhone makes it really worth it. Also, PNG answers all needs for my 3D game for now.

No comments:

Post a Comment