// ==============================================================
// This file is part of MegaGlest (www.glest.org)
//
// Image.cpp -- Copyright (c) 2006 David Henry
// changed for use with MegaGlest: Copyright (C) 2011-  by Mark Vejvoda
//
// This code is licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) <year> <copyright holders>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.//
//
// Implementation of image loader classes for DDS, TGA, PCX, JPEG
// and PNG image formats.
//
/////////////////////////////////////////////////////////////////////////////

#include "Image.h"

namespace Shared { namespace Graphics { namespace md5 {

/////////////////////////////////////////////////////////////////////////////
//
// class ImageBuffer implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// ImageBuffer::ImageBuffer
//
// Constructor.  Load a file into memory buffer.
// --------------------------------------------------------------------------
ImageBuffer::ImageBuffer (const string &filename)
  : _filename (filename), _data (NULL), _length (0) {
  // Open file
  std::ifstream ifs (filename.c_str(), std::ios::in | std::ios::binary);

  if (ifs.fail ())
    throw ImageException ("Couldn't open image file: " + filename, filename);

  // Get file length
  ifs.seekg (0, std::ios::end);
  _length = ifs.tellg ();
  ifs.seekg (0, std::ios::beg);

  try {
      // Allocate memory for holding file data
      _data = new GLubyte[_length];

      // Read whole file data
      ifs.read (reinterpret_cast<char *> (_data), _length);
      ifs.close ();
    }
  catch (...)
    {
      delete [] _data;
      throw;
    }
}

// --------------------------------------------------------------------------
// ImageBuffer::~ImageBuffer
//
// Destructor.  Free memory buffer.
// --------------------------------------------------------------------------
ImageBuffer::~ImageBuffer () {
  delete [] _data;
  _data = NULL;
}

/////////////////////////////////////////////////////////////////////////////
//
// class Image implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// Image::Image
//
// Constructors.  Create an image from a pixel buffer.  It allows to
// create an image from an other source of data than an ImageBuffer.
// This is also the copy constructor to use if you want pixel data
// to be copied, since the Image(const Image&) constructor is protected.
// --------------------------------------------------------------------------

Image::Image (const string &name, GLsizei w, GLsizei h, GLint numMipMaps,
	      GLenum format, GLint components, const GLubyte *pixels,
	      bool stdCoordSystem)
  : _width (w), _height (h), _numMipmaps (numMipMaps),
    _format (format), _components (components), _name (name),
    _standardCoordSystem (stdCoordSystem) {
  // NOTE: pixels should be a valid pointer. w, h and components
  // have to be non-zero positive values.

  long size = _width * _height * _components;

  if (size <= 0)
    throw std::invalid_argument
      ("Image::Image: Invalid width, height or component value");

  if (!pixels)
    throw std::invalid_argument
      ("Image::Image: Invalid pixel data source");

  // allocate memory for pixel data
  _pixels = new GLubyte[size];

  // Copy pixel data from buffer
  memcpy (_pixels, pixels, size);
}

// --------------------------------------------------------------------------
// Image::~Image
//
// Destructor.  Delete all memory allocated for this object, i.e. pixel
// data.
// --------------------------------------------------------------------------
Image::~Image () {
  delete [] _pixels;
  _pixels = NULL;
}

// --------------------------------------------------------------------------
// Image::isCompressed
//
// Check if the image is using S3TC compression (this is the case for
// DDS image only).
// --------------------------------------------------------------------------
bool Image::isCompressed () const {
  switch (_format)
    {
    case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
      return true;

    default:
      return false;
    }
}

// --------------------------------------------------------------------------
// Image::isPowerOfTwo
//
// Check if the image dimensions are powers of two.
// --------------------------------------------------------------------------
bool Image::isPowerOfTwo () const {
  GLsizei m;
  for (m = 1; m < _width; m *= 2)
    ;

  if (m != _width)
    return false;

  for (m = 1; m < _height; m *= 2)
    ;

  if (m != _height)
    return false;

  return true;
}

/////////////////////////////////////////////////////////////////////////////
//
// class ImageDDS implementation.
//
/////////////////////////////////////////////////////////////////////////////

// FourCC auto generation with templates
template <char ch0, char ch1, char ch2, char ch3>
struct MakeFourCC {
  enum {
    Val =(((ch3 << 24) & 0xFF000000) |
	  ((ch2 << 16) & 0x00FF0000) |
	  ((ch1 <<  8) & 0x0000FF00) |
	  (ch0         & 0x000000FF))
  };
};

const unsigned int FOURCC_DXT1 = MakeFourCC<'D', 'X', 'T', '1'>::Val;
const unsigned int FOURCC_DXT3 = MakeFourCC<'D', 'X', 'T', '3'>::Val;
const unsigned int FOURCC_DXT5 = MakeFourCC<'D', 'X', 'T', '5'>::Val;

// --------------------------------------------------------------------------
// ImageDDS::ImageDDS
//
// Constructor.  Read a DDS image from memory.
// --------------------------------------------------------------------------
ImageDDS::ImageDDS (const ImageBuffer &ibuff)
  : _ddsd (NULL) {
  _name = ibuff.filename ();

  // DDS images use the GDI+ coordinate system (starts upper-left corner)
  _standardCoordSystem = false;

  // There are extensions required for handling compressed DDS
  // images as textures
  if (!GLEW_ARB_texture_compression)
    throw ImageException ("missing GL_ARB_texture_compression"
			  " extension", _name);

  if (!GLEW_EXT_texture_compression_s3tc)
    throw ImageException ("missing GL_EXT_texture_compression_s3tc"
			  " extension", _name);

  try {
      // Get pointer on file data
      const GLubyte *data_ptr = ibuff.data ();
      int bufferSize = ibuff.length ();

      // Check if is a valid DDS file
      string magic;
      magic.assign (reinterpret_cast<const char *>(data_ptr), 4);
      if (magic.compare (0, 4, "DDS ") != 0)
	throw ImageException ("Not a valid DDS file", _name);

      // Eat 4 bytes magic number
      data_ptr += 4;
      bufferSize -= 4;

      // Read the surface descriptor and init some member variables
      _ddsd = reinterpret_cast<const DDSurfaceDesc*>(data_ptr);
      data_ptr += sizeof (DDSurfaceDesc);
      bufferSize -= sizeof (DDSurfaceDesc);

      _width = _ddsd->width;
      _height = _ddsd->height;
      _numMipmaps = _ddsd->mipMapLevels;

      switch (_ddsd->format.fourCC)
	{
	case FOURCC_DXT1:
	  // DXT1's compression ratio is 8:1
	  _format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
	  _components = 3;
	  break;

	case FOURCC_DXT3:
	  // DXT3's compression ratio is 4:1
	  _format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
	  _components = 4;
	  break;

	case FOURCC_DXT5:
	  // DXT5's compression ratio is 4:1
	  _format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	  _components = 4;
	  break;

	default:
	  // Bad fourCC, unsupported or bad format
	  throw ImageException ("Unsupported DXT format", _name);
	}

      // Read pixel data with mipmaps
      _pixels = new GLubyte[bufferSize];
      memcpy (_pixels, data_ptr, bufferSize);
    }
  catch (...)
    {
      delete [] _pixels;
      throw;
    }
}

/////////////////////////////////////////////////////////////////////////////
//
// class ImageTGA implementation.
//
/////////////////////////////////////////////////////////////////////////////

// Pixel's component table access
int ImageTGA::rgbaTable[4] = { 2, 1, 0, 3 };
int ImageTGA::bgraTable[4] = { 0, 1, 2, 3 };

// --------------------------------------------------------------------------
// ImageTGA::ImageTGA
//
// Constructor.  Read a TGA image from memory.
// --------------------------------------------------------------------------
ImageTGA::ImageTGA (const ImageBuffer &ibuff)
  : _header (NULL) {
  const GLubyte *colormap = NULL;
  const GLubyte *data_ptr;

  try
    {
      _name = ibuff.filename ();
      data_ptr = ibuff.data ();
      _standardCoordSystem = true;

      // Read TGA header
      _header = reinterpret_cast<const TGA_Header *>(data_ptr);
      data_ptr += sizeof (TGA_Header) + _header->id_lenght;

      // Get image information
      getTextureInfo ();

      // Memory allocation for pixel data
      _pixels = new GLubyte[_width * _height * _components];

      // Read color map, if present
      if (_header->colormap_type)
	{
	  // NOTE: color map is stored in BGR
	  colormap = data_ptr;
	  data_ptr += _header->cm_length * (_header->cm_size >> 3);
	}

      // Decode image data
      switch (_header->image_type)
	{
	case 0:
	  // No data
	  break;

	case 1:
	  // Uncompressed 8 bits color index
	  readTGA8bits (data_ptr, colormap);
	  break;

	case 2:
	  // Uncompressed 16-24-32 bits
	  switch (_header->pixel_depth)
	    {
	    case 16:
	      readTGA16bits (data_ptr);
	      break;

	    case 24:
	      readTGA24bits (data_ptr);
	      break;

	    case 32:
	      readTGA32bits (data_ptr);
	      break;
	    }

	  break;

	case 3:
	  // Uncompressed 8 or 16 bits grayscale
	  if (_header->pixel_depth == 8)
	    readTGAgray8bits (data_ptr);
	  else // 16 bits
	    readTGAgray16bits (data_ptr);

	  break;

	case 9:
	  // RLE compressed 8 bits color index
	  readTGA8bitsRLE (data_ptr, colormap);
	  break;

	case 10:
	  // RLE compressed 16-24-32 bits
	  switch (_header->pixel_depth)
	    {
	    case 16:
	      readTGA16bitsRLE (data_ptr);
	      break;

	    case 24:
	      readTGA24bitsRLE (data_ptr);
	      break;

	    case 32:
	      readTGA32bitsRLE (data_ptr);
	      break;
	    }

	  break;

	case 11:
	  // RLE compressed 8 or 16 bits grayscale
	  if (_header->pixel_depth == 8)
	    readTGAgray8bitsRLE (data_ptr);
	  else // 16 bits
	    readTGAgray16bitsRLE (data_ptr);

	  break;

	default:
	  // Image type is not correct, free memory and quit
	  throw ImageException ("Unknown TGA image type", _name);
	}
    }
  catch (...)
    {
      delete [] _pixels;
      throw;
    }
}

// --------------------------------------------------------------------------
// ImageTGA::getTextureInfo
//
// Extract OpenGL texture informations from TGA header.
// --------------------------------------------------------------------------
void ImageTGA::getTextureInfo () {
  _width = _header->width;
  _height = _header->height;

  switch (_header->image_type)
    {
    case 3:  // grayscale 8 bits
    case 11: // grayscale 8 bits (RLE)
      {
	if (_header->pixel_depth == 8)
	  {
	    _format = GL_LUMINANCE;
	    _components = 1;
	  }
	else // 16 bits
	  {
	    _format = GL_LUMINANCE_ALPHA;
	    _components = 2;
	  }

	break;
      }

    case 1:    // 8 bits color index
    case 2:    // BGR 16-24-32 bits
    case 9:    // 8 bits color index (RLE)
    case 10:   // BGR 16-24-32 bits (RLE)
      {
	// 8 bits and 16 bits images will be converted to 24 bits
	if (_header->pixel_depth <= 24)
	  {
	    _format = GLEW_EXT_bgra ? GL_BGR : GL_RGB;
	    _components = 3;
	  }
	else // 32 bits
	  {
	    _format = GLEW_EXT_bgra ? GL_BGRA : GL_RGBA;
	    _components = 4;
	  }

	break;
      }
    }
}

// --------------------------------------------------------------------------
// ImageTGA::readTGA8bits
//
// Read 8 bits pixel data from TGA file.
// --------------------------------------------------------------------------
void ImageTGA::readTGA8bits (const GLubyte *data, const GLubyte *colormap) {
  const GLubyte *pData = data;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  for (int i = 0; i < _width * _height; ++i)
    {
      // Read index color byte
      GLubyte color = *(pData++);

      // Convert to BGR/RGB 24 bits
      _pixels[(i * 3) + compTable[0]] = colormap[(color * 3) + 0];
      _pixels[(i * 3) + compTable[1]] = colormap[(color * 3) + 1];
      _pixels[(i * 3) + compTable[2]] = colormap[(color * 3) + 2];
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGA16bits
//
// Read 16 bits pixel data from TGA file.
// --------------------------------------------------------------------------
void ImageTGA::readTGA16bits (const GLubyte *data) {
  const GLubyte *pData = data;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  for (int i = 0; i < _width * _height; ++i, pData += 2) {
      // Read color word
      unsigned short color = pData[0] + (pData[1] << 8);

      // convert to BGR/RGB 24 bits
      _pixels[(i * 3) + compTable[2]] = (((color & 0x7C00) >> 10) << 3);
      _pixels[(i * 3) + compTable[1]] = (((color & 0x03E0) >>  5) << 3);
      _pixels[(i * 3) + compTable[0]] = (((color & 0x001F) >>  0) << 3);
    }
}

// --------------------------------------------------------------------------
// ImageTGA::readTGA24bits
//
// Read 24 bits pixel data from TGA file.
// --------------------------------------------------------------------------
void ImageTGA::readTGA24bits (const GLubyte *data) {
  if (GLEW_EXT_bgra)
    {
      memcpy (_pixels, data, _width * _height * 3);
    }
  else
    {
      const GLubyte *pData = data;

      for (int i = 0; i < _width * _height; ++i)
	{
	  // Read RGB 24 bits pixel
	  _pixels[(i * 3) + rgbaTable[0]] = *(pData++);
	  _pixels[(i * 3) + rgbaTable[1]] = *(pData++);
	  _pixels[(i * 3) + rgbaTable[2]] = *(pData++);
	}
    }
}

// --------------------------------------------------------------------------
// ImageTGA::readTGA32bits
//
// Read 32 bits pixel data from TGA file.
// --------------------------------------------------------------------------
void ImageTGA::readTGA32bits (const GLubyte *data) {
  if (GLEW_EXT_bgra)
    {
      memcpy (_pixels, data, _width * _height * 4);
    }
  else
    {
      const GLubyte *pData = data;

      for (int i = 0; i < _width * _height; ++i)
	{
	  // Read RGB 32 bits pixel
	  _pixels[(i * 4) + rgbaTable[0]] = *(pData++);
	  _pixels[(i * 4) + rgbaTable[1]] = *(pData++);
	  _pixels[(i * 4) + rgbaTable[2]] = *(pData++);
	  _pixels[(i * 4) + rgbaTable[3]] = *(pData++);
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGAgray8bits
//
// Read grey 8 bits pixel data from TGA file.
// --------------------------------------------------------------------------
void ImageTGA::readTGAgray8bits (const GLubyte *data) {
  memcpy (_pixels, data, _width * _height);
}

// --------------------------------------------------------------------------
// ImageTGA::readTGAgray16bits
//
// Read grey 16 bits pixel data from TGA file.
// --------------------------------------------------------------------------

void
ImageTGA::readTGAgray16bits (const GLubyte *data)
{
  memcpy (_pixels, data, _width * _height * 2);
}


// --------------------------------------------------------------------------
// ImageTGA::readTGA8bitsRLE
//
// Read 8 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGA8bitsRLE (const GLubyte *data, const GLubyte *colormap)
{
  GLubyte *ptr = _pixels;
  const GLubyte *pData = data;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  while (ptr < _pixels + (_width * _height) * 3)
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet
	  GLubyte color = *(pData++);

	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      ptr[0] = colormap[(color * 3) + compTable[0]];
	      ptr[1] = colormap[(color * 3) + compTable[1]];
	      ptr[2] = colormap[(color * 3) + compTable[2]];
	    }
	}
      else
	{
	  // Non run-length packet
	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      GLubyte color = *(pData++);

	      ptr[0] = colormap[(color * 3) + compTable[0]];
	      ptr[1] = colormap[(color * 3) + compTable[1]];
	      ptr[2] = colormap[(color * 3) + compTable[2]];
	    }
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGA16bitsRLE
//
// Read 16 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGA16bitsRLE (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  while (ptr < _pixels + (_width * _height) * 3)
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet
	  unsigned short color = pData[0] + (pData[1] << 8);
	  pData += 2;

	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      ptr[compTable[2]] = (((color & 0x7C00) >> 10) << 3);
	      ptr[compTable[1]] = (((color & 0x03E0) >>  5) << 3);
	      ptr[compTable[0]] = (((color & 0x001F) >>  0) << 3);
	    }
	}
      else
	{
	  // Non run-length packet
	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      unsigned short color = pData[0] + (pData[1] << 8);
	      pData += 2;

	      ptr[compTable[2]] = (((color & 0x7C00) >> 10) << 3);
	      ptr[compTable[1]] = (((color & 0x03E0) >>  5) << 3);
	      ptr[compTable[0]] = (((color & 0x001F) >>  0) << 3);
	    }
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGA24bitsRLE
//
// Read 24 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGA24bitsRLE (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  while (ptr < _pixels + (_width * _height) * 3)
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet
	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      ptr[0] = pData[compTable[0]];
	      ptr[1] = pData[compTable[1]];
	      ptr[2] = pData[compTable[2]];
	    }

	  pData += 3;
	}
      else
	{
	  // Non run-length packet
	  for (int i = 0; i < size; ++i, ptr += 3)
	    {
	      ptr[0] = pData[compTable[0]];
	      ptr[1] = pData[compTable[1]];
	      ptr[2] = pData[compTable[2]];
	      pData += 3;
	    }
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGA32bitsRLE
//
// Read 32 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGA32bitsRLE (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;
  int *compTable = GLEW_EXT_bgra ? bgraTable : rgbaTable;

  while (ptr < _pixels + (_width * _height) * 4)
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet */
	  for (int i = 0; i < size; ++i, ptr += 4)
	    {
	      ptr[0] = pData[compTable[0]];
	      ptr[1] = pData[compTable[1]];
	      ptr[2] = pData[compTable[2]];
	      ptr[3] = pData[compTable[3]];
	    }

	  pData += 4;
	}
      else
	{
	  // Non run-length packet
	  for (int i = 0; i < size; ++i, ptr += 4)
	    {
	      ptr[0] = pData[compTable[0]];
	      ptr[1] = pData[compTable[1]];
	      ptr[2] = pData[compTable[2]];
	      ptr[3] = pData[compTable[3]];
	      pData += 4;
	    }
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGAgray8bitsRLE
//
// Read grey 8 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGAgray8bitsRLE (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;

  while (ptr < _pixels + (_width * _height))
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet
	  GLubyte color = *(pData++);

	  memset (ptr, color, size);
	  ptr += size;
	}
      else
	{
	  // Non run-length packet
	  memcpy (ptr, pData, size);
	  ptr += size;
	  pData += size;
	}
    }
}


// --------------------------------------------------------------------------
// ImageTGA::readTGAgray16bitsRLE
//
// Read grey 16 bits pixel data from TGA file with RLE compression.
// --------------------------------------------------------------------------

void
ImageTGA::readTGAgray16bitsRLE (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;

  while (ptr < _pixels + (_width * _height) * 2)
    {
      // Read first byte
      GLubyte packet_header = *(pData++);
      int size = 1 + (packet_header & 0x7f);

      if (packet_header & 0x80)
	{
	  // Run-length packet
	  GLubyte color = *(pData++);
	  GLubyte alpha = *(pData++);

	  for (int i = 0; i < size; ++i, ptr += 2)
	    {
	      ptr[0] = color;
	      ptr[1] = alpha;
	    }
	}
      else
	{
	  // Non run-length packet
	  memcpy (ptr, pData, size * 2);
	  ptr += size * 2;
	  pData += size * 2;
	}
    }
}


/////////////////////////////////////////////////////////////////////////////
//
// class ImagePCX implementation.
//
/////////////////////////////////////////////////////////////////////////////

// Pixel's component table access
int ImagePCX::rgbTable[3] = { 0, 1, 2 };
int ImagePCX::bgrTable[3] = { 2, 1, 0 };


// --------------------------------------------------------------------------
// ImagePCX::ImagePCX
//
// Constructor.  Read a PCX image from memory.
// --------------------------------------------------------------------------

ImagePCX::ImagePCX (const ImageBuffer &ibuff)
  : _header (NULL)
{
  const GLubyte *data_ptr;

  try
    {
      _name = ibuff.filename ();
      _standardCoordSystem = true;

      // Get pointer on file data
      data_ptr = ibuff.data ();

      // Read PCX header
      _header = reinterpret_cast<const PCX_Header *>(data_ptr);
      data_ptr += sizeof (PCX_Header);

      // Check if is valid PCX file
      if (_header->manufacturer != 0x0a)
	throw ImageException ("Bad version number", _name);

      // Initialize image variables
      _width = _header->xmax - _header->xmin + 1;
      _height = _header->ymax - _header->ymin + 1;
      _format = GLEW_EXT_bgra ? GL_BGR : GL_RGB;
      _components = 3;
      _pixels = new GLubyte[_width * _height * _components];

      int bitcount = _header->bitsPerPixel * _header->numColorPlanes;
      int palette_pos = ibuff.length () - 768;

      // Read image data
      switch (bitcount)
	{
	case 1:
	  // 1 bit color index
	  readPCX1bit (data_ptr);
	  break;

	case 4:
	  // 4 bits color index
	  readPCX4bits (data_ptr);
	  break;

	case 8:
	  // 8 bits color index
	  readPCX8bits (data_ptr, ibuff.data () + palette_pos);
	  break;

	case 24:
	  // 24 bits
	  readPCX24bits (data_ptr);
	  break;

	default:
	  // Unsupported
	  throw ImageException ("Unhandled PCX format (bad bitcount)", _name);
	}
    }
  catch (...)
    {
      delete [] _pixels;
      throw;
    }
}


// --------------------------------------------------------------------------
// ImagePCX::readPCX1bit
//
// Read 1 bit PCX image.
// --------------------------------------------------------------------------

void
ImagePCX::readPCX1bit (const GLubyte *data)
{
  int rle_count = 0, rle_value = 0;
  const GLubyte *pData = data;
  GLubyte *ptr = _pixels;
  int *compTable = GLEW_EXT_bgra ? bgrTable : rgbTable;

  for (int y = 0; y < _height; ++y)
    {
      ptr = &_pixels[(_height - (y + 1)) * _width * 3];
      int bytes = _header->bytesPerScanLine;

      // Decode line number y
      while (bytes--)
	{
	  if (rle_count == 0)
	    {
	      if ( (rle_value = *(pData++)) < 0xc0)
		{
		  rle_count = 1;
		}
	      else
		{
		  rle_count = rle_value - 0xc0;
		  rle_value = *(pData++);
		}
	    }

	  rle_count--;

	  // Fill height pixels chunk
	  for (int i = 7; i >= 0; --i, ptr += 3)
	    {
	      int colorIndex = ((rle_value & (1 << i)) > 0);

	      ptr[0] = _header->palette[colorIndex * 3 + compTable[0]];
	      ptr[1] = _header->palette[colorIndex * 3 + compTable[1]];
	      ptr[2] = _header->palette[colorIndex * 3 + compTable[2]];
	    }
	}
    }
}


// --------------------------------------------------------------------------
// ImagePCX::readPCX4bits
//
// Read 4 bits PCX image.
// --------------------------------------------------------------------------

void
ImagePCX::readPCX4bits (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *colorIndex = NULL;
  GLubyte *line = NULL;
  GLubyte *ptr;
  int rle_count = 0, rle_value = 0;
  int *compTable = GLEW_EXT_bgra ? bgrTable : rgbTable;

  try
    {
      // Memory allocation for temporary buffers
      colorIndex = new GLubyte[_width];
      line = new GLubyte[_header->bytesPerScanLine];
    }
  catch (std::bad_alloc &err)
    {
      delete [] colorIndex;
      delete [] line;
      throw;
    }

  for (int y = 0; y < _height; ++y)
    {
      ptr = &_pixels[(_height - (y + 1)) * _width * 3];
      memset (colorIndex, 0, _width);

      for (int c = 0; c < 4; ++c)
	{
	  GLubyte *pLine = line;
	  int bytes = _header->bytesPerScanLine;

	  // Decode line number y
	  while (bytes--)
	    {
	      if (rle_count == 0)
		{
		  if ( (rle_value = *(pData++)) < 0xc0)
		    {
		      rle_count = 1;
		    }
		  else
		    {
		      rle_count = rle_value - 0xc0;
		      rle_value = *(pData++);
		    }
		}

	      rle_count--;
	      *(pLine++) = rle_value;
	    }

	  // Compute line's color indexes
	  for (int x = 0; x < _width; ++x)
	    {
	      if (line[x / 8] & (128 >> (x % 8)))
		colorIndex[x] += (1 << c);
	    }
	}

      // Decode scanline.  color index => rgb
      for (int x = 0; x < _width; ++x, ptr += 3)
	{
	  ptr[0] = _header->palette[colorIndex[x] * 3 + compTable[0]];
	  ptr[1] = _header->palette[colorIndex[x] * 3 + compTable[1]];
	  ptr[2] = _header->palette[colorIndex[x] * 3 + compTable[2]];
	}
    }

  // Release memory
  delete [] colorIndex;
  delete [] line;
}


// --------------------------------------------------------------------------
// ImagePCX::readPCX8bits
//
// Read 8 bits PCX image.
// --------------------------------------------------------------------------

void
ImagePCX::readPCX8bits (const GLubyte *data, const GLubyte *palette)
{
  const GLubyte *pData = data;
  int rle_count = 0, rle_value = 0;
  GLubyte *ptr;
  int *compTable = GLEW_EXT_bgra ? bgrTable : rgbTable;

  // Palette should be preceded by a value of 0x0c (12)...
  GLubyte magic = palette[-1];
  if (magic != 0x0c)
    {
      // ... but sometimes it is not
      std::cerr << "Warning: PCX palette should start with "
		<< "a value of 0x0c (12)!" << std::endl;
    }

  // Read pixel data
  for (int y = 0; y < _height; ++y)
    {
      ptr = &_pixels[(_height - (y + 1)) * _width * 3];
      int bytes = _header->bytesPerScanLine;

      // Decode line number y
      while (bytes--)
	{
	  if (rle_count == 0)
	    {
	      if( (rle_value = *(pData++)) < 0xc0)
		{
		  rle_count = 1;
		}
	      else
		{
		  rle_count = rle_value - 0xc0;
		  rle_value = *(pData++);
		}
	    }

	  rle_count--;

	  ptr[0] = palette[rle_value * 3 + compTable[0]];
	  ptr[1] = palette[rle_value * 3 + compTable[1]];
	  ptr[2] = palette[rle_value * 3 + compTable[2]];
	  ptr += 3;
	}
    }
}


// --------------------------------------------------------------------------
// ImagePCX::readPCX24bits
//
// Read 24 bits PCX image.
// --------------------------------------------------------------------------

void
ImagePCX::readPCX24bits (const GLubyte *data)
{
  const GLubyte *pData = data;
  GLubyte *ptr;
  int rle_count = 0, rle_value = 0;
  int *compTable = GLEW_EXT_bgra ? bgrTable : rgbTable;

  for (int y = 0; y < _height; ++y)
    {
      // For each color plane
      for (int c = 0; c < 3; ++c)
	{
	  ptr = &_pixels[(_height - (y + 1)) * _width * 3];
	  int bytes = _header->bytesPerScanLine;

	  // Decode line number y
	  while (bytes--)
	    {
	      if (rle_count == 0)
		{
		  if( (rle_value = *(pData++)) < 0xc0)
		    {
		      rle_count = 1;
		    }
		  else
		    {
		      rle_count = rle_value - 0xc0;
		      rle_value = *(pData++);
		    }
		}

	      rle_count--;
	      ptr[compTable[c]] = static_cast<GLubyte>(rle_value);
	      ptr += 3;
	    }
	}
    }
}


/////////////////////////////////////////////////////////////////////////////
//
// class ImageJPEG implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// ImageJPEG::ImageJPEG
//
// Constructor.  Read a JPEG image from memory, using libjpeg.
// --------------------------------------------------------------------------

ImageJPEG::ImageJPEG (const ImageBuffer &ibuff)
{
  jpeg_decompress_struct cinfo;
  my_error_mgr jerr;
  jpeg_source_mgr jsrc;
  JSAMPROW j;

  try
    {
      _name = ibuff.filename ();
      _standardCoordSystem = true;

      // Create and configure decompress object
      jpeg_create_decompress (&cinfo);
      cinfo.err = jpeg_std_error (&jerr.pub);
      cinfo.src = &jsrc;

      // Configure error manager
      jerr.pub.error_exit = errorExit_callback;
      jerr.pub.output_message = outputMessage_callback;

      if (setjmp (jerr.setjmp_buffer))
	throw ImageException (jerr.errorMsg, _name);

      // Configure source manager
      jsrc.next_input_byte = ibuff.data ();
      jsrc.bytes_in_buffer = ibuff.length ();

      jsrc.init_source = initSource_callback;
      jsrc.fill_input_buffer = fillInputBuffer_callback;
      jsrc.skip_input_data = skipInputData_callback;
      jsrc.resync_to_restart = jpeg_resync_to_restart;
      jsrc.term_source = termSource_callback;

      // Read file's header and prepare for decompression
      jpeg_read_header (&cinfo, TRUE);
      jpeg_start_decompress (&cinfo);

      // Initialize image's member variables
      _width = cinfo.image_width;
      _height = cinfo.image_height;
      _components = cinfo.num_components;
      _format = (cinfo.num_components == 1) ? GL_LUMINANCE : GL_RGB;
      _pixels = new GLubyte[_width * _height * _components];

      // Read scanlines
      for (int i = 0; i < _height; ++i)
	{
	  //j = &_pixels[_width * i * _components];
	  j = (_pixels + ((_height - (i + 1)) * _width * _components));
	  jpeg_read_scanlines (&cinfo, &j, 1);
	}

      // Finish decompression and release memory
      jpeg_finish_decompress (&cinfo);
      jpeg_destroy_decompress (&cinfo);
    }
  catch (...)
    {
      delete [] _pixels;
      jpeg_destroy_decompress (&cinfo);

      throw;
    }
}


// --------------------------------------------------------------------------
// ImageJPEG::initSource_callback
// ImageJPEG::fillInputBuffer_callback
// ImageJPEG::skipInputData_callback
// ImageJPEG::termSource_callback
//
// Callback functions used by libjpeg for reading data from memory.
// --------------------------------------------------------------------------

void
ImageJPEG::initSource_callback (j_decompress_ptr cinfo)
{
  // Nothing to do here
}


boolean
ImageJPEG::fillInputBuffer_callback (j_decompress_ptr cinfo)
{
  JOCTET eoi_buffer[2] = { 0xFF, JPEG_EOI };
  struct jpeg_source_mgr *jsrc = cinfo->src;

  // Create a fake EOI marker
  jsrc->next_input_byte = eoi_buffer;
  jsrc->bytes_in_buffer = 2;

  return TRUE;
}


void
ImageJPEG::skipInputData_callback (j_decompress_ptr cinfo, long num_bytes)
{
  struct jpeg_source_mgr *jsrc = cinfo->src;

  if (num_bytes > 0)
    {
      while (num_bytes > static_cast<long>(jsrc->bytes_in_buffer))
	{
	  num_bytes -= static_cast<long>(jsrc->bytes_in_buffer);
	  fillInputBuffer_callback (cinfo);
	}

      jsrc->next_input_byte += num_bytes;
      jsrc->bytes_in_buffer -= num_bytes;
    }
}


void
ImageJPEG::termSource_callback (j_decompress_ptr cinfo)
{
  // Nothing to do here
}


// --------------------------------------------------------------------------
// ImageJPEG::errorExit
// ImageJPEG::outputMessage
//
// Callback functions used by libjpeg for error handling.
// --------------------------------------------------------------------------

void
ImageJPEG::errorExit_callback (j_common_ptr cinfo)
{
  my_error_ptr jerr = reinterpret_cast<my_error_ptr>(cinfo->err);

  // Create the error message
  char message[JMSG_LENGTH_MAX];
  (*cinfo->err->format_message) (cinfo, message);
  jerr->errorMsg.assign (message);

  // Return control to the setjmp point
  longjmp (jerr->setjmp_buffer, 1);
}


void
ImageJPEG::outputMessage_callback (j_common_ptr cinfo)
{
  my_error_ptr jerr = reinterpret_cast<my_error_ptr>(cinfo->err);

  // Create the error message
  char message[JMSG_LENGTH_MAX];
  (*cinfo->err->format_message) (cinfo, message);
  jerr->errorMsg.assign (message);

  // Send it to stderr, adding a newline
  std::cerr << "libjpeg: " << jerr->errorMsg << std::endl;
}


/////////////////////////////////////////////////////////////////////////////
//
// class ImagePNG implementation.
//
/////////////////////////////////////////////////////////////////////////////

// --------------------------------------------------------------------------
// ImagePNG::ImagePNG
//
// Constructor.  Read a PNG image from memory, using libpng.
// --------------------------------------------------------------------------

ImagePNG::ImagePNG (const ImageBuffer &ibuff)
{
  png_structp png_ptr = NULL;
  png_infop info_ptr = NULL;
  png_bytep *row_pointers = NULL;
  int bit_depth, color_type;
  my_source_mgr src_mgr (ibuff);

  try
    {
      _name = ibuff.filename ();
      _standardCoordSystem = true;

      png_byte sig[8];
      memcpy (sig, reinterpret_cast<const png_byte*>(ibuff.data ()), 8);

      // Check for valid magic number
      if (!png_sig_cmp (sig, 0, 8))
	throw ImageException ("Not a valid PNG file", _name);

      // Create PNG read struct
      png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, &errorMsg,
					error_callback, warning_callback);
      if (!png_ptr)
	throw ImageException ("Failed to create png read struct", _name);

      if (setjmp (png_jmpbuf (png_ptr)))
	throw ImageException (errorMsg, _name);

      // Create PNG info struct
      info_ptr = png_create_info_struct (png_ptr);
      if (!info_ptr)
	throw ImageException ("Failed to create png info struct", _name);

      // Set "read" callback function and give source of data
      png_set_read_fn (png_ptr, &src_mgr, read_callback);

      // Read png info
      png_read_info (png_ptr, info_ptr);

      // Get some usefull information from header
      bit_depth = png_get_bit_depth (png_ptr, info_ptr);
      color_type = png_get_color_type (png_ptr, info_ptr);

      // Convert index color images to RGB images
      if (color_type == PNG_COLOR_TYPE_PALETTE)
	png_set_palette_to_rgb (png_ptr);

      // Convert 1-2-4 bits grayscale images to 8 bits
      // grayscale.
      if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
	png_set_expand_gray_1_2_4_to_8(png_ptr);

      if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
	png_set_tRNS_to_alpha (png_ptr);

      if (bit_depth == 16)
	png_set_strip_16 (png_ptr);
      else if (bit_depth < 8)
	png_set_packing (png_ptr);

      // Update info structure to apply transformations
      png_read_update_info (png_ptr, info_ptr);

      // Get updated information
      png_get_IHDR (png_ptr, info_ptr,
		    reinterpret_cast<png_uint_32*>(&_width),
		    reinterpret_cast<png_uint_32*>(&_height),
		    &bit_depth, &color_type,
		    NULL, NULL, NULL);

      // Get image format and components per pixel
      getTextureInfo (color_type);

      // Memory allocation for storing pixel data
      _pixels = new GLubyte[_width * _height * _components];

      // Pointer array.  Each one points at the begening of a row.
      row_pointers = new png_bytep[_height];

      for (int i = 0; i < _height; ++i)
	{
	  row_pointers[i] = (png_bytep)(_pixels +
		((_height - (i + 1)) * _width * _components));
	}

      // Read pixel data using row pointers
      png_read_image (png_ptr, row_pointers);

      // Finish decompression and release memory
      png_read_end (png_ptr, NULL);
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);

      delete [] row_pointers;
    }
  catch (...)
    {
      delete [] _pixels;
      delete [] row_pointers;

      if (png_ptr)
	png_destroy_read_struct (&png_ptr, &info_ptr, NULL);

      throw;
    }
}


// --------------------------------------------------------------------------
// ImagePNG::getTextureInfo
//
// Extract OpenGL texture informations from PNG info.
// --------------------------------------------------------------------------

void
ImagePNG::getTextureInfo (int color_type)
{
  switch (color_type)
    {
    case PNG_COLOR_TYPE_GRAY:
      _format = GL_LUMINANCE;
      _components = 1;
      break;

    case PNG_COLOR_TYPE_GRAY_ALPHA:
      _format = GL_LUMINANCE_ALPHA;
      _components = 2;
      break;

    case PNG_COLOR_TYPE_RGB:
      _format = GL_RGB;
      _components = 3;
      break;

    case PNG_COLOR_TYPE_RGB_ALPHA:
      _format = GL_RGBA;
      _components = 4;
      break;

    default:
      // Badness
      throw ImageException ("Bad PNG color type", _name);
    }
}


// --------------------------------------------------------------------------
// ImagePNG::read_callback
// ImagePNG::error_callback
// ImagePNG::warning_callback
//
// Callback functions used by libpng for reading data from memory
// and error handling.
// --------------------------------------------------------------------------

void
ImagePNG::read_callback (png_structp png_ptr, png_bytep data, png_size_t length)
{
  my_source_ptr src = static_cast<my_source_ptr>(png_get_io_ptr(png_ptr));

  // Copy data from image buffer
  memcpy (data, src->pibuff->data () + src->offset, length);

  // Advance in the file
  src->offset += length;
}


void
ImagePNG::error_callback (png_structp png_ptr, png_const_charp error_msg)
{
  static_cast<string *>(png_get_error_ptr(png_ptr))->assign (error_msg);

  longjmp (png_jmpbuf (png_ptr), 1);
}


void
ImagePNG::warning_callback (png_structp png_ptr, png_const_charp warning_msg)
{
  std::cerr << "libpng: " << warning_msg << std::endl;
}

}}} //end namespace
