
//
//  Title:  Windows Bitmap Coder
//
//  Copyright 1997-1999, Colosseum Builders, Inc.
//  All rights reserved.
//
//  Colosseum Builders, Inc. makes no warranty, expressed or implied
//  with regards to this software. It is provided as is.
//
//  See the README.TXT file that came with this software for restrictions
//  on the use and redistribution of this file or send E-mail to
//  info@colosseumbuilders.com
//
//  Author:  John M. Miano (miano@colosseumbuilders.com)
//
//  Date:    April 15, 1999
//
//  Version: 3
//
//  Description:
//
//    Windows BMP Encoder
//
//  Revision History:
//
//

#include <windows.h>
#include <fstream>
#include <climits>

#include "bmpencoder.h"
using namespace std ;

namespace Colosseum
{

const UBYTE2 Signature = 'B'|('M'<<CHAR_BIT) ;

//
//  Description:
//
//    Default Constructor
//
BmpEncoder::BmpEncoder ()
: create_alpha_channel (true),
  bits_per_pixel (24)
{
}

//
//  Description:
//
//    Class copy constructor
//
BmpEncoder::BmpEncoder (const BmpEncoder &source)
: BitmapImageEncoder (source),
  create_alpha_channel (source.create_alpha_channel),
  bits_per_pixel (source.bits_per_pixel)
{
  return ;
}

//
//  Description:
//
//    Class assignment operator.
//
//  Parameters:
//    source:  The object to copy
//
BmpEncoder &BmpEncoder::operator=(const BmpEncoder &source)
{
  if (&source != this)
  {
    BitmapImageEncoder::operator=(source) ;
  }
  return *this ;
}

//
//  Description:
//
//    This function writes an image to a BMP stream.
//
//  Parameters:
//    strm:  The output stream
//    image:  The image to output
//
void BmpEncoder::writeImage (std::ostream &strm, const BitmapImage &image)
{
  if (image.getWidth () == 0 || image.getHeight () == 0)
    throw BmpError ("Empty Image") ;

  BITMAPFILEHEADER fileheader = {
          SystemToLittleEndian (Signature),
          0,
          0,
          0,
          0 } ;

  // The header is initialized for 32-Bit images. For other bit depths
  // we filling the values later.
  BITMAPV4HEADER infoheader = {
          SystemToLittleEndian (static_cast<UBYTE4>(sizeof (BITMAPV4HEADER))), // Header Size
          SystemToLittleEndian (static_cast<UBYTE4>(image.getWidth ())),
          SystemToLittleEndian (- static_cast<BYTE4>(image.getHeight ())),
          SystemToLittleEndian (static_cast<UBYTE2>(1)), // Planes
          SystemToLittleEndian (static_cast<BYTE4>(bits_per_pixel)),  // biBitCount
          SystemToLittleEndian (static_cast<UBYTE4>(BI_BITFIELDS)), // biCompression
          0,                // Image Size
          0,                // biXPelsPerMeter
          0,                // biYPelsPerMeter
          0,                // biClrUsed
          0,                // biClrImportant
          // This is the cut off from the BITMAPINFOHEADER
          SystemToLittleEndian (static_cast<UBYTE4>(0xFF0000)), // Red Mask
          SystemToLittleEndian (static_cast<UBYTE4>(0xFF00)),   // Green Mask
          SystemToLittleEndian (static_cast<UBYTE4>(0xFF)),     // Blue Mask
          create_alpha_channel ? SystemToLittleEndian (static_cast<UBYTE4>(0xFF000000)) : 0,    // Alpha Mask
          0,    // CS Type
          { { 0, 0, 0, }, { 0, 0, 0, }, { 0, 0, 0, } },   // CIE
          0,  // Gamma Red
          0,  // Gamma Green
          0,  // Gamma Blue
          } ;

  // Row width rounded up to 4 bytes.
  unsigned int rowwidth = (image.getWidth () * (bits_per_pixel / CHAR_BIT) + 0x3) & ~0x3 ;
  // Calulate the space required for the image.
  unsigned long datasize = image.getHeight () * rowwidth ;
  if (bits_per_pixel == 16)
  {
    infoheader.bV4RedMask   = SystemToLittleEndian (0x7C00U) ;
    infoheader.bV4GreenMask = SystemToLittleEndian (0x3E0U) ;
    infoheader.bV4BlueMask  = SystemToLittleEndian (0x1FU) ;
  }
  else if (bits_per_pixel == 24)
  {
    // For 24-Bit images we set the BMP header up for the LCD in BMP files.
    infoheader.bV4V4Compression = SystemToLittleEndian (static_cast<UBYTE4>(BI_RGB)) ;
    infoheader.bV4Size = SystemToLittleEndian (sizeof (BITMAPINFOHEADER)) ;
    infoheader.bV4Height = -infoheader.bV4Height ;
  }
  unsigned long spacerequired = sizeof (BITMAPFILEHEADER)
                              + datasize
                              + infoheader.bV4Size ;

  // Fill in the remaining header fields.
  fileheader.bfOffBits = SystemToLittleEndian (
                           static_cast<UBYTE4>(sizeof (BITMAPFILEHEADER)+ infoheader.bV4Size)
                                               ) ;
  fileheader.bfSize = SystemToLittleEndian (static_cast<UBYTE4>(spacerequired)) ;

  strm.write (reinterpret_cast<char*>(&fileheader), sizeof (BITMAPFILEHEADER)) ;
  strm.write (reinterpret_cast<char*>(&infoheader), infoheader.bV4Size) ;

  callProgressFunction (0) ;

  unsigned int padsize = rowwidth - image.getWidth () * (bits_per_pixel / CHAR_BIT) ;
  static const char pad [3] = { 0, 0, 0 } ;
  switch (bits_per_pixel)
  {
  case 16:
    {
      const BitmapImage::Pixel *pixel = &image [0] ;
      for (unsigned int ii = 0 ; ii < image.getHeight () ; ++ ii)
      {
        for (unsigned int jj = 0 ; jj < image.getWidth() ; ++ jj, ++ pixel)
        {
          UBYTE2 buffer = 0 ;
          buffer |= (pixel->blue >> 3) & 0x1FU ;
          buffer |= ((pixel->green >> 3) & 0x1FU) << 5 ;
          buffer |= ((pixel->red >> 3) & 0x1FU) << 10 ;
          strm.write (reinterpret_cast<const char*>(&buffer), sizeof (buffer)) ;
        }
        if (padsize != 0)
          strm.write (pad, padsize) ;
        callProgressFunction (ii * 100 /image.getHeight ()) ;
      }
    }
    break ;

  case 24:
    {
      for (int ii = image.getHeight () - 1 ; ii >= 0 ; -- ii)
      {
        // For 24-bit images we store the rows in reverse order
        // in order to be compatible with the LCD in viewers.
        const BitmapImage::Pixel *pixel = &image [ii * image.getWidth ()] ;
        for (unsigned int jj = 0 ; jj < image.getWidth() ; ++ jj, ++ pixel)
        {
          RGBTRIPLE buffer ;
          buffer.rgbtRed = pixel->red ;
          buffer.rgbtGreen = pixel->green ;
          buffer.rgbtBlue = pixel->blue ;
          strm.write (reinterpret_cast<const char*>(&buffer), sizeof (buffer)) ;
        }
        if (padsize != 0)
          strm.write (pad, padsize) ;
        callProgressFunction (ii * 100 /image.getHeight ()) ;
      }
    }
    break ;

  case 32:
    strm.write (reinterpret_cast<const char*>(&image [0]),
                sizeof (BitmapImage::Pixel)
                 * image.getHeight ()
                 * image.getWidth ()) ;
    break ;
  }

  callProgressFunction (100) ;
  return ;
}

void BmpEncoder::writeImageFile (const std::string &filename, const BitmapImage &image)
{
  ofstream strm (filename.c_str (), ios::binary|ios::trunc) ;
  if (! strm)
  {
    string msg = "Cannot Open Input File " ;
    msg += filename ;
    throw BmpError (msg) ;
  }

  writeImage (strm, image) ;
  return ;
}

void BmpEncoder::setCreateAlphaChannel (bool value)
{
  create_alpha_channel = value ;
  return ;
}

bool BmpEncoder::getCreateAlphaChannel () const
{
  return create_alpha_channel ;
}

void BmpEncoder::setBitsPerPixel (unsigned int value)
{
  switch (value)
  {
  case 16: case 24: case 32: break ;
  default: throw BmpError ("Invalid Bit Depth") ;
  }
  bits_per_pixel = value ;
  return ;
}

unsigned int BmpEncoder::getBitsPerPixel () const
{
  return bits_per_pixel ;
}

//
//  Description:
//
//    This function calls the progress function if it has been defined.
//
//  Parameters:
//    percent: The percent completed (0..100)
//    
void BmpEncoder::callProgressFunction (unsigned int percent)
{
  if (progress_function == 0)
    return ;
  bool cancel = false ;
  progress_function (*this, progress_data, 1, 1, percent, cancel) ;
  if (cancel)
    throw GraphicsAbort () ;
  return ;
}



} // End Namespace
