// Copyright (C) 2008  Davis E. King (davis@dlib.net), Nils Labugt
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_PNG_LOADER_CPp_
#define DLIB_PNG_LOADER_CPp_

// only do anything with this file if DLIB_PNG_SUPPORT is defined
#ifdef DLIB_PNG_SUPPORT

#include "../array2d.h"
#include "../pixel.h"
#include "../dir_nav.h"
#include "png_loader.h"
#include <png.h>

namespace dlib
{

// ----------------------------------------------------------------------------------------

    struct LibpngData
    {
        png_bytep* row_pointers_;
        png_structp png_ptr_;
        png_infop info_ptr_;
        png_infop end_info_;
    };

// ----------------------------------------------------------------------------------------

    png_loader::
    png_loader( const char* filename ) : height_( 0 ), width_( 0 )
    {
        read_image( filename );
    }

// ----------------------------------------------------------------------------------------

    png_loader::
    png_loader( const std::string& filename ) : height_( 0 ), width_( 0 )
    {
        read_image( filename.c_str() );
    }

// ----------------------------------------------------------------------------------------

    png_loader::
    png_loader( const dlib::file& f ) : height_( 0 ), width_( 0 )
    {
        read_image( f.full_name().c_str() );
    }

// ----------------------------------------------------------------------------------------

    const unsigned char* png_loader::get_row( unsigned i ) const
    {
        return ld_->row_pointers_[i];
    }

// ----------------------------------------------------------------------------------------

    png_loader::~png_loader()
    {
        if ( ld_ && ld_->row_pointers_ != NULL )
            png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) );
    }

// ----------------------------------------------------------------------------------------

    bool png_loader::is_gray() const
    {
        return ( color_type_ == PNG_COLOR_TYPE_GRAY );
    }

// ----------------------------------------------------------------------------------------

    bool png_loader::is_rgb() const
    {
        return ( color_type_ == PNG_COLOR_TYPE_RGB );
    }

// ----------------------------------------------------------------------------------------

    bool png_loader::is_rgba() const
    {
        return ( color_type_ == PNG_COLOR_TYPE_RGB_ALPHA );
    }

// ----------------------------------------------------------------------------------------

    void png_loader::read_image( const char* filename )
    {
        ld_.reset(new LibpngData);
        if ( filename == NULL )
        {
            throw image_load_error("png_loader: invalid filename, it is NULL");
        }
        FILE *fp = fopen( filename, "rb" );
        if ( !fp )
        {
            throw image_load_error(std::string("png_loader: unable to open file ") + filename);
        }
        png_byte sig[8];
        if (fread( sig, 1, 8, fp ) != 8)
        {
            fclose( fp );
            throw image_load_error(std::string("png_loader: error reading file ") + filename);
        }
        if ( png_sig_cmp( sig, 0, 8 ) != 0 )
        {
            fclose( fp );
            throw image_load_error(std::string("png_loader: format error in file ") + filename);
        }
        ld_->png_ptr_ = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
        if ( ld_->png_ptr_ == NULL )
        {
            fclose( fp );
            throw image_load_error(std::string("png_loader: parse error in file ") + filename);
        }
        ld_->info_ptr_ = png_create_info_struct( ld_->png_ptr_ );
        if ( ld_->info_ptr_ == NULL )
        {
            fclose( fp );
            png_destroy_read_struct( &( ld_->png_ptr_ ), ( png_infopp )NULL, ( png_infopp )NULL );
            throw image_load_error(std::string("png_loader: parse error in file ") + filename);
        }
        ld_->end_info_ = png_create_info_struct( ld_->png_ptr_ );
        if ( ld_->end_info_ == NULL )
        {
            fclose( fp );
            png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), ( png_infopp )NULL );
            throw image_load_error(std::string("png_loader: parse error in file ") + filename);
        }
        png_init_io( ld_->png_ptr_, fp );
        png_set_sig_bytes( ld_->png_ptr_, 8 );
        // flags force one byte per channel output
        png_read_png( ld_->png_ptr_, ld_->info_ptr_, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING, NULL );
        height_ = png_get_image_height( ld_->png_ptr_, ld_->info_ptr_ );
        width_ = png_get_image_width( ld_->png_ptr_, ld_->info_ptr_ );
        bit_depth_ = png_get_bit_depth( ld_->png_ptr_, ld_->info_ptr_ );
        color_type_ = png_get_color_type( ld_->png_ptr_, ld_-> info_ptr_ );

        if (color_type_ != PNG_COLOR_TYPE_GRAY && 
            color_type_ != PNG_COLOR_TYPE_RGB && 
            color_type_ != PNG_COLOR_TYPE_RGB_ALPHA )
        {
            fclose( fp );
            png_destroy_read_struct( &( ld_->png_ptr_ ), &( ld_->info_ptr_ ), &( ld_->end_info_ ) );
            throw image_load_error(std::string("png_loader: unsupported color type in file ") + filename);
        }

        ld_->row_pointers_ = png_get_rows( ld_->png_ptr_, ld_->info_ptr_ );
        // FIXME: the following call makes libpng crash. Why?
        //png_read_end( ld_->png_ptr_, ld_->end_info_ );
        fclose( fp );
        if ( ld_->row_pointers_ == NULL )
        {
            throw image_load_error(std::string("png_loader: parse error in file ") + filename);
        }
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_PNG_SUPPORT

#endif // DLIB_PNG_LOADER_CPp_