/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

ON_VIRTUAL_OBJECT_IMPLEMENT( ON_Bitmap, ON_Object, "390465E9-3721-11d4-800B-0010830122F0");

ON_OBJECT_IMPLEMENT( ON_WindowsBitmap, ON_Bitmap,  "390465EB-3721-11d4-800B-0010830122F0");

ON_OBJECT_IMPLEMENT( ON_EmbeddedBitmap, ON_Bitmap, "772E6FC1-B17B-4fc4-8F54-5FDA511D76D2");

ON_OBJECT_IMPLEMENT( ON_WindowsBitmapEx, ON_WindowsBitmap,  "203AFC17-BCC9-44fb-A07B-7F5C31BD5ED9");

const ON_Bitmap* ON_Bitmap::FromModelComponentRef(
  const class ON_ModelComponentReference& model_component_reference,
  const ON_Bitmap* none_return_value
  )
{
  const ON_Bitmap* p = ON_Bitmap::Cast(model_component_reference.ModelComponent());
  return (nullptr != p) ? p : none_return_value;
}

ON_Bitmap::ON_Bitmap() ON_NOEXCEPT
  : ON_ModelComponent(ON_ModelComponent::Type::Image)
{}

ON_Bitmap::ON_Bitmap( const ON_Bitmap& src)
  : ON_ModelComponent(ON_ModelComponent::Type::Image,src)
  , m_file_reference(src.m_file_reference)
{}

void ON_Bitmap::Dump( ON_TextLog& dump ) const
{
  ON_ModelComponent::Dump(dump);
  m_file_reference.Dump(dump);
  dump.Print("width = %d pixels\n",Width());
  dump.Print("height = %d pixels\n",Height());
  dump.Print("bits per pixel = %d\n",BitsPerPixel());
  dump.Print("size of image = %zu bytes\n",SizeofImage());
}

int ON_Bitmap::Width() const
{
  return 0;
}

int ON_Bitmap::Height() const
{
  return 0;
}

int ON_Bitmap::BitsPerPixel() const
{
  return 0;
}

size_t ON_Bitmap::SizeofScan() const
{
  return 0;
}

size_t ON_Bitmap::SizeofImage() const
{
  return 0;
}

unsigned char* ON_Bitmap::Bits(
  int scan_line_index
  )
{
  return nullptr;
}

const unsigned char* ON_Bitmap::Bits(
  int scan_line_index
  ) const
{
  return nullptr;
}

bool ON_Bitmap::Write(ON_BinaryArchive& archive) const
{
  const int major_version = 1;
  const int minor_version = 0;
  if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, major_version, minor_version))
    return false;

  bool rc = false;
  for (;;)
  {
    const unsigned int attributes_filter
      = ON_ModelComponent::Attributes::IdAttribute
      | ON_ModelComponent::Attributes::IndexAttribute
      | ON_ModelComponent::Attributes::NameAttribute
      ;
    ON_ModelComponent::WriteModelComponentAttributes(archive, attributes_filter);
    if (!m_file_reference.Write(true, archive))
      break;

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;

  return rc;
}

bool ON_Bitmap::Read(ON_BinaryArchive& archive)
{
  int major_version = 0;
  int minor_version = 0;
  if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version))
    return false;

  bool rc = false;
  for (;;)
  {
    if (1 != major_version)
      break;
    ON_ModelComponent::ReadModelComponentAttributes(archive);
    if (!m_file_reference.Read(archive))
      break;

    rc = true;
    break;
  }

  if (!archive.EndRead3dmChunk())
    rc = false;

  return rc;
}


const ON_FileReference& ON_Bitmap::FileReference() const
{
  return m_file_reference;
}


void ON_Bitmap::SetFileReference(
  const ON_FileReference& file_reference
  )
{
  if ( 0 != ON_FileReference::Compare(m_file_reference,file_reference) )
  {
    IncrementContentVersionNumber();
    m_file_reference = file_reference;
  }
}


void ON_Bitmap::SetFileFullPath(
  const wchar_t* file_full_path,
  bool bSetContentHash
  )
{
  ON_FileReference file_reference;
  file_reference.SetFullPath(file_full_path,bSetContentHash);
  SetFileReference(file_reference);
}


static
int ON_WindowsBitmapHelper_PaletteColorCount( int bmiHeader_biClrUsed, int bmiHeader_biBitCount )
{
  int color_count = 0;
  
  if ( bmiHeader_biClrUsed )
  {
    color_count = bmiHeader_biClrUsed;
  }
  else 
  {  
    switch( bmiHeader_biBitCount )
    {
    case 1:
      color_count = 2;
      break;
    case 4:
      color_count = 16;
      break;
    case 8:
      color_count = 256;
      break;
    default:
      color_count = 0;
    }
  }  
  return color_count;
}

static
size_t ON_WindowsBitmapHelper_SizeofPalette( int bmiHeader_biClrUsed, int bmiHeader_biBitCount )
{
#if defined(ON_OS_WINDOWS_GDI)
  return ( ON_WindowsBitmapHelper_PaletteColorCount(bmiHeader_biClrUsed,bmiHeader_biBitCount) * sizeof(RGBQUAD) );
#else
  return ( ON_WindowsBitmapHelper_PaletteColorCount(bmiHeader_biClrUsed,bmiHeader_biBitCount) * sizeof(ON_WindowsRGBQUAD) );
#endif
}

ON_WindowsBitmap::ON_WindowsBitmap( const ON_WindowsBitmap& src ) 
 : ON_Bitmap(src)
{
  Internal_Copy(src);
}

ON_WindowsBitmap::~ON_WindowsBitmap()
{
  Internal_Destroy();
}

ON_WindowsBitmap& ON_WindowsBitmap::operator=( const ON_WindowsBitmap& src )
{
  if ( this != &src ) 
  {
    Internal_Destroy();
    ON_Bitmap::operator=(src);
    Internal_Copy(src);
  }
  return *this;
}

#if defined(ON_OS_WINDOWS_GDI)

static BITMAPINFO* 
ON_WindowsBitmapHelper_AllocBMI(size_t sizeof_palette, size_t sizeof_image)
{
  // In theory,
  //    sz = sizeof(BITMAPINFOHEADER) + sizeof_palette + sizeof_image;
  // should work, but BITMAPINFO is only 4 bytes bigger than BITMAPINFOHEADER
  // and the allocation below will certainly work.
  size_t sz = sizeof(BITMAPINFO) + sizeof_palette + sizeof_image;
  BITMAPINFO* bmi = (BITMAPINFO*)onmalloc(sz);
  if ( bmi )
  {
    memset(bmi,0,sizeof(*bmi));
    bmi->bmiHeader.biSize = sizeof(bmi->bmiHeader);
  }
  return bmi;
}

#else

static 
ON_WindowsBITMAPINFO* ON_WindowsBitmapHelper_AllocBMI(size_t sizeof_palette, size_t sizeof_image)
{
  size_t sz = sizeof(ON_WindowsBITMAPINFO) + sizeof_palette + sizeof_image;
  ON_WindowsBITMAPINFO* bmi = (ON_WindowsBITMAPINFO*)onmalloc(sz);
  if ( bmi )
  {
    memset(bmi,0,sizeof(*bmi));
    bmi->bmiHeader.biSize = sizeof(bmi->bmiHeader);
  }
  return bmi;
}

#endif

void ON_WindowsBitmap::Internal_Copy( const ON_WindowsBitmap& src )
{
  if ( nullptr != src.m_bmi ) 
  {
    const int sizeof_palette = src.SizeofPalette();
    const size_t sizeof_image = src.SizeofImage();
    m_bmi = ON_WindowsBitmapHelper_AllocBMI( sizeof_palette, sizeof_image );
    if ( m_bmi ) 
    {
      m_bFreeBMI = 1;
      m_bmi->bmiHeader = src.m_bmi->bmiHeader;
      if( sizeof_palette > 0 )
      {
        memcpy(&m_bmi->bmiColors[0],&src.m_bmi->bmiColors[0],sizeof_palette);
      }
      if ( sizeof_image > 0 ) 
      {
        m_bits = (unsigned char*)&m_bmi->bmiColors[PaletteColorCount()];
        if ( src.m_bits )
          memcpy( m_bits, src.m_bits, sizeof_image );
        else
          memset( m_bits, 0, sizeof_image );
      }
      else
        m_bits = nullptr;
    }
  }
}


void ON_WindowsBitmap::Internal_Destroy()
{
  if (nullptr != m_bmi) 
  {
    if ( 1 == m_bFreeBMI || 3 == m_bFreeBMI )
      onfree(m_bmi);
    m_bmi = nullptr;
  }

  if ( nullptr != m_bits )
  {
    if ( 2 == m_bFreeBMI || 3 == m_bFreeBMI )
      onfree(m_bits);
    m_bits = nullptr;
  }

  m_bFreeBMI = 0;
}

bool ON_WindowsBitmap::IsEmpty() const
{
  return ( nullptr == m_bmi || nullptr == m_bits || 0 == Width() || 0 == Height() );
}

bool ON_WindowsBitmap::IsValid( ON_TextLog* text_log ) const
{
  bool rc 
    = ( m_bmi != nullptr && m_bits != nullptr && Width() > 0 && Height() > 0) 
    ? true
    : false;

  if ( !rc && 0 != text_log )
  {
    // TODO:  add a detailed diagnostic message
    text_log->Print("ON_WindowsBitmap is not valid\n");
  }
  return rc;
}


bool ON_WindowsBitmap::Create( 
       int width,
       int height,
       int bits_per_pixel // 1, 2, 4, 8, 16, 24, or 32
       )
{
  Internal_Destroy();

  if ( width < 1 || height < 1 )
  {
    return false;
  }
  if (   bits_per_pixel != 1 && bits_per_pixel !=  2 && bits_per_pixel !=  4 
      && bits_per_pixel != 8 && bits_per_pixel != 16 && bits_per_pixel != 24 
      && bits_per_pixel != 32 )
  {
    return false;
  }

#if defined(ON_OS_WINDOWS_GDI)
  BITMAPINFOHEADER bh;
  const DWORD sizeof_RGBQUAD    = sizeof(RGBQUAD);
#else
  ON_WindowsBITMAPINFOHEADER bh;
  const unsigned int sizeof_RGBQUAD    = sizeof(ON_WindowsRGBQUAD);
#endif
  memset( &bh, 0, sizeof(bh) );
  bh.biSize = sizeof(bh);
  bh.biWidth = width;
  bh.biHeight = height;
  bh.biPlanes = 1;
  bh.biBitCount = (unsigned short)bits_per_pixel; // cast is safe
  bh.biCompression = 0; // BI_RGB
  const int sizeof_scan = (((bits_per_pixel*width) + 31) / 32) * 4;
  bh.biSizeImage = height*sizeof_scan;
  bh.biXPelsPerMeter = 0;
  bh.biYPelsPerMeter = 0;
  bh.biClrUsed = 0;
  bh.biClrImportant = 0;

  int palette_color_count = 0;
  switch ( bits_per_pixel ) {
  case 1:
    palette_color_count = 2;
    break;
  case 4:
    palette_color_count = 16;
    break;
  case 8:
    palette_color_count = 256;
    break;
  }
  const int sizeof_palette = palette_color_count*sizeof_RGBQUAD;

  m_bmi = ON_WindowsBitmapHelper_AllocBMI( sizeof_palette, bh.biSizeImage );

  bool rc = false;

  if ( m_bmi /*&& palette_color_count > 0*/) 
  {
    m_bmi->bmiHeader = bh;
    m_bits = (unsigned char*)&m_bmi->bmiColors[palette_color_count];

    // default palette is gray scale
    if ( palette_color_count > 0 )
    {
      const int rgb_delta = 256/palette_color_count;
      int i, rgb;
      for ( i = 0, rgb = 0; i < palette_color_count; i++, rgb += rgb_delta ) 
      {
        if ( rgb >= 256 ) rgb = 255;
        m_bmi->bmiColors[i].rgbBlue  = (unsigned char)rgb;
        m_bmi->bmiColors[i].rgbGreen = (unsigned char)rgb;
        m_bmi->bmiColors[i].rgbRed   = (unsigned char)rgb;
        m_bmi->bmiColors[i].rgbReserved = 0;
      }
    }
    rc = true;
  }

  return rc;
}



int ON_WindowsBitmap::Width() const
{
  return (m_bmi) ? m_bmi->bmiHeader.biWidth : 0;
}

int ON_WindowsBitmap::Height() const
{
  return (m_bmi) ? m_bmi->bmiHeader.biHeight : 0;
}

int ON_WindowsBitmap::PaletteColorCount() const
{
  return m_bmi ? ON_WindowsBitmapHelper_PaletteColorCount(m_bmi->bmiHeader.biClrUsed, m_bmi->bmiHeader.biBitCount ) : 0;
}

int ON_WindowsBitmap::SizeofPalette() const
{
  return m_bmi ? ((int)ON_WindowsBitmapHelper_SizeofPalette(m_bmi->bmiHeader.biClrUsed, m_bmi->bmiHeader.biBitCount) ) : 0;
}

size_t ON_WindowsBitmap::SizeofScan() const
{
  int scan_width = 0;
  if ( m_bmi ) {
    int bitcount = m_bmi->bmiHeader.biBitCount;
    int width = Width();
    scan_width = (((bitcount*width) + 31) / 32) * 4;
  }
  return scan_width;
}

int ON_WindowsBitmap::BitsPerPixel() const
{
  return m_bmi ? m_bmi->bmiHeader.biBitCount : 0;
}

size_t ON_WindowsBitmap::SizeofImage() const
{
  size_t sizeImage = 0;
  if ( nullptr != m_bmi ) 
  {
    sizeImage = m_bmi->bmiHeader.biSizeImage;
    if (0 == sizeImage)
    {
      const size_t w = ((((m_bmi->bmiHeader.biWidth * m_bmi->bmiHeader.biBitCount) + 31) & ~31) >> 3);
      const size_t h = m_bmi->bmiHeader.biHeight;
      sizeImage = w*h;
    }
  }
  return sizeImage;
}

unsigned char* ON_WindowsBitmap::Bits( int scan_index )
{
  const size_t sizeof_scan = SizeofScan();
  unsigned char* bits = m_bmi ? (unsigned char*)&m_bmi->bmiColors[PaletteColorCount()] : 0;
  if ( bits && sizeof_scan && scan_index >= 0 && scan_index < Height() ) {
    bits += ( sizeof_scan*scan_index );
  }
  else {
    bits = 0;
  }
  return bits;
}

const unsigned char* ON_WindowsBitmap::Bits(int scan_index) const
{
  const size_t sizeof_scan = SizeofScan();
  const unsigned char* bits = m_bmi ? (const unsigned char*)&m_bmi->bmiColors[PaletteColorCount()] : 0;
  if ( bits && sizeof_scan && scan_index >= 0 && scan_index < Height() ) {
    bits += (sizeof_scan*scan_index);
  }
  else {
    bits = 0;
  }
  return bits;
}

ON_Color ON_WindowsBitmap::Pixel( int column_index, int row_index ) const
{
  return Pixel( column_index, Bits(row_index) );
}

ON_Color ON_WindowsBitmap::Pixel( int column_index, const unsigned char* scanbits ) const
{
  int r=0,g=0,b=0,a=0;

  unsigned int palindex;
  
  if ( m_bmi && column_index >= 0 && column_index <= Width() && scanbits && !m_bmi->bmiHeader.biCompression ) {
    switch( m_bmi->bmiHeader.biBitCount ) {
    case 32:
      scanbits += (column_index*4);
      b = *scanbits++;
      g = *scanbits++;
      r = *scanbits++;
      a = *scanbits;
      break;
    
    case 24:
      scanbits += (column_index*3);
      b = *scanbits++;
      g = *scanbits++;
      r = *scanbits;
      break;
    
    case 8:
      // 256 color bitmap uses palette
      palindex = scanbits[column_index];    
      b = m_bmi->bmiColors[palindex].rgbBlue;
      g = m_bmi->bmiColors[palindex].rgbGreen;
      r = m_bmi->bmiColors[palindex].rgbRed;
      a = m_bmi->bmiColors[palindex].rgbReserved;
      break;
    
    case 4:
      {
        // 16 color bitmap uses palette
        palindex = scanbits[column_index/2];
        if ( !(column_index%2) )
          palindex >>= 4;
        palindex &= 0x0F;
        b = m_bmi->bmiColors[palindex].rgbBlue;
        g = m_bmi->bmiColors[palindex].rgbGreen;
        r = m_bmi->bmiColors[palindex].rgbRed;
        a = m_bmi->bmiColors[palindex].rgbReserved;
      }
      break;
    
    case 1:
      // monochrome bitmap has 2 color palette 
      palindex = ( scanbits[column_index/8] >> (7-(column_index%8)) ) & 0x01;
      b = m_bmi->bmiColors[palindex].rgbBlue;
      g = m_bmi->bmiColors[palindex].rgbGreen;
      r = m_bmi->bmiColors[palindex].rgbRed;
      a = m_bmi->bmiColors[palindex].rgbReserved;
      break;		
    }
  }
  
  return ON_Color(r,g,b,a);
}

bool ON_WindowsBitmap::WriteUncompressed( ON_BinaryArchive& file ) const
{
#if defined(ON_OS_WINDOWS_GDI)
  BITMAPINFOHEADER bmiHeader;
#else
  ON_WindowsBITMAPINFOHEADER bmiHeader;
#endif

  if ( m_bmi )
  {
    bmiHeader = m_bmi->bmiHeader;
    bmiHeader.biSize = sizeof(bmiHeader);
  }
  else 
  {
    memset(&bmiHeader,0,sizeof(bmiHeader));
  }
  int i;
  short s;
  i = bmiHeader.biSize;
  bool    rc = file.WriteInt(i);
  i = bmiHeader.biWidth;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biHeight;
  if (rc) rc = file.WriteInt(i);
  s = bmiHeader.biPlanes;
  if (rc) rc = file.WriteShort(s);
  s = bmiHeader.biBitCount;
  if (rc) rc = file.WriteShort(s);
  i = bmiHeader.biCompression;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biSizeImage;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biXPelsPerMeter;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biYPelsPerMeter;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biClrUsed;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biClrImportant;
  if (rc) rc = file.WriteInt(i);

  if ( rc ) 
  {
    const int color_count = PaletteColorCount();
    for (i = 0; i < color_count && rc; i++ )
    {
      if (rc) rc = file.WriteChar( m_bmi->bmiColors[i].rgbBlue );
      if (rc) rc = file.WriteChar( m_bmi->bmiColors[i].rgbGreen );
      if (rc) rc = file.WriteChar( m_bmi->bmiColors[i].rgbRed );
      if (rc) rc = file.WriteChar( m_bmi->bmiColors[i].rgbReserved );
    }
    const size_t sizeof_image = SizeofImage();
    if ( sizeof_image > 0 && rc ) 
    {
      if (rc) rc = file.WriteByte( sizeof_image, &m_bmi->bmiColors[color_count] );
    }
  }

  return rc;
}

bool ON_WindowsBitmap::ReadUncompressed( ON_BinaryArchive& file )
{
#if defined(ON_OS_WINDOWS_GDI)
  BITMAPINFOHEADER bmiHeader;
#else
  ON_WindowsBITMAPINFOHEADER bmiHeader;
#endif
  memset(&bmiHeader,0,sizeof(bmiHeader));

  Internal_Destroy();

  int i;
  short s;
  bool rc;

  for(;;)
  {
    i = 0;
    s = 0;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biSize = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biWidth = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biHeight = i;
    rc = file.ReadShort(&s); if (!rc) break; bmiHeader.biPlanes = s;
    rc = file.ReadShort(&s); if (!rc) break; bmiHeader.biBitCount = s;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biCompression = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biSizeImage = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biXPelsPerMeter = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biYPelsPerMeter = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biClrUsed = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biClrImportant = i;
    break;
  }

  if ( rc ) 
  {
    bmiHeader.biSize = sizeof(bmiHeader);
    const size_t sizeof_palette = ON_WindowsBitmapHelper_SizeofPalette(bmiHeader.biClrUsed, bmiHeader.biBitCount );
    const size_t sizeof_image   = bmiHeader.biSizeImage;
    
    m_bmi = ON_WindowsBitmapHelper_AllocBMI( sizeof_palette, sizeof_image );

    if ( !m_bmi )
    {
      rc = false;
    }
    else 
    {
      m_bFreeBMI = 1;
      m_bmi->bmiHeader = bmiHeader;
      const int color_count = ON_WindowsBitmapHelper_PaletteColorCount(bmiHeader.biClrUsed, bmiHeader.biBitCount );
      for (int i_for_loop = 0; i_for_loop < color_count && rc; i_for_loop++ ) 
      {
        if (rc) rc = file.ReadChar( &m_bmi->bmiColors[i_for_loop].rgbBlue );
        if (rc) rc = file.ReadChar( &m_bmi->bmiColors[i_for_loop].rgbGreen );
        if (rc) rc = file.ReadChar( &m_bmi->bmiColors[i_for_loop].rgbRed );
        if (rc) rc = file.ReadChar( &m_bmi->bmiColors[i_for_loop].rgbReserved );
      }
      if ( sizeof_image > 0 && rc ) 
      {
        m_bits = (unsigned char*)&m_bmi->bmiColors[color_count];
        if (rc) rc = file.ReadByte( sizeof_image, m_bits );
      }
    }
  }
  return rc;
}

bool ON_WindowsBitmap::Write( ON_BinaryArchive& archive ) const
{
  return Internal_WriteV5(archive);
}

bool ON_WindowsBitmap::Internal_WriteV5( ON_BinaryArchive& archive ) const
{
  return WriteCompressed(archive);
}

bool ON_WindowsBitmap::Read( ON_BinaryArchive& archive )
{
  return Internal_ReadV5(archive);
}

bool ON_WindowsBitmap::Internal_ReadV5( ON_BinaryArchive& file )
{
  bool rc = false;
  if ( file.Archive3dmVersion() == 1 )
    rc = ReadUncompressed(file);
  else
    rc = ReadCompressed(file);
  return rc;
}

bool ON_WindowsBitmapEx::Write(ON_BinaryArchive& archive) const
{
  return Internal_WriteV5(archive);
}

bool ON_WindowsBitmapEx::Read(ON_BinaryArchive& archive)
{
  return Internal_ReadV5(archive);
}

bool ON_WindowsBitmapEx::Internal_WriteV5( ON_BinaryArchive& file ) const
{
  bool rc = file.Write3dmChunkVersion(1,0);
  if (rc)
    rc = file.WriteString(FileReference().FullPath());
  if (rc)
    rc = ON_WindowsBitmap::WriteCompressed(file);
  return rc;
}

bool ON_WindowsBitmapEx::Internal_ReadV5( ON_BinaryArchive& file )
{
  Internal_Destroy();
  SetFileReference(ON_FileReference::Unset);

  int major_version = 0;
  int minor_version = 0;
  bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
  if (rc && 1 == major_version )
  {
    // Calling ON_WindowsBitmap::ReadCompressed() destroys
    // m_bitmap_filename, so we have to read it into a local
    // string and make the assigment after calling 
    // ON_WindowsBitmap::ReadCompressed().
    ON_wString bitmap_filename;
    if (rc)
      rc = file.ReadString(bitmap_filename);
    if ( !rc)
      bitmap_filename.Destroy();

    if (rc)
      rc = ON_WindowsBitmap::ReadCompressed(file);

    bitmap_filename.TrimLeftAndRight();
    if (bitmap_filename.IsNotEmpty())
    {
      ON_FileReference file_reference;
      if (ON_FileSystemPath::IsRelativePath(bitmap_filename))
        file_reference.SetRelativePath(bitmap_filename);
      else
        file_reference.SetFullPath(bitmap_filename,false);
      SetFileReference(file_reference);
    }
  }
  else
    rc = false;
  return rc;
}

bool ON_WindowsBitmap::WriteCompressed( ON_BinaryArchive& file ) const
{
  int color_count = 0;
  int sizeof_palette = 0;
  size_t sizeof_image = 0;
  bool bContiguousBitmap = IsContiguous();
#if defined(ON_OS_WINDOWS_GDI)
  BITMAPINFOHEADER bmiHeader;
#else
  ON_WindowsBITMAPINFOHEADER bmiHeader;
#endif
  if ( m_bmi )
  {
    bmiHeader = m_bmi->bmiHeader;
    color_count = PaletteColorCount();
    sizeof_palette = color_count*sizeof(*m_bmi->bmiColors);
    sizeof_image = SizeofImage();
    if ( 0 == sizeof_image )
      bContiguousBitmap = true;
  }
  else 
  {
    bContiguousBitmap = true;
    color_count = 0;
    sizeof_palette = 0;
    sizeof_image = 0;
    memset(&bmiHeader,0,sizeof(bmiHeader));
  }
  int i;
  short s;
  i = bmiHeader.biSize;
  bool    rc = file.WriteInt(i);
  i = bmiHeader.biWidth;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biHeight;
  if (rc) rc = file.WriteInt(i);
  s = bmiHeader.biPlanes;
  if (rc) rc = file.WriteShort(s);
  s = bmiHeader.biBitCount;
  if (rc) rc = file.WriteShort(s);
  i = bmiHeader.biCompression;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biSizeImage;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biXPelsPerMeter;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biYPelsPerMeter;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biClrUsed;
  if (rc) rc = file.WriteInt(i);
  i = bmiHeader.biClrImportant;
  if (rc) rc = file.WriteInt(i);

  if ( rc ) 
  {
    if ( bContiguousBitmap )
    {
      const size_t sizeof_buffer = sizeof_palette + sizeof_image;
      // palette and bits are compressed in a single chunk
      rc = file.WriteCompressedBuffer( sizeof_buffer, (0 != m_bmi) ? m_bmi->bmiColors : 0);
    }
    else
    {
      // 28 July 2003
      //     Added support for writing non-contiguous bitmaps
      // palette 
      rc = file.WriteCompressedBuffer( sizeof_palette, m_bmi->bmiColors );
      if (rc)
      {
        // image bits
        rc = file.WriteCompressedBuffer( sizeof_image, m_bits );
      }
    }
  }

  return rc;
}

bool ON_WindowsBitmap::ReadCompressed( ON_BinaryArchive& file )
{
  Internal_Destroy();

  bool bFailedCRC = false;
#if defined(ON_OS_WINDOWS_GDI)
  BITMAPINFOHEADER bmiHeader;
#else
  ON_WindowsBITMAPINFOHEADER bmiHeader;
#endif
  memset(&bmiHeader,0,sizeof(bmiHeader));
  int i;
  short s;
  bool rc;

  for(;;)
  {
    i = 0;
    s = 0;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biSize = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biWidth = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biHeight = i;
    rc = file.ReadShort(&s); if (!rc) break; bmiHeader.biPlanes = s;
    rc = file.ReadShort(&s); if (!rc) break; bmiHeader.biBitCount = s;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biCompression = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biSizeImage = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biXPelsPerMeter = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biYPelsPerMeter = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biClrUsed = i;
    rc = file.ReadInt(&i);   if (!rc) break; bmiHeader.biClrImportant = i;
    break;
  }


  if ( rc ) 
  {
    bmiHeader.biSize = sizeof(bmiHeader);
    const size_t sizeof_palette = ON_WindowsBitmapHelper_SizeofPalette(bmiHeader.biClrUsed, bmiHeader.biBitCount );
    const size_t sizeof_image   = bmiHeader.biSizeImage;
    m_bmi = ON_WindowsBitmapHelper_AllocBMI(sizeof_palette,sizeof_image);
    if ( !m_bmi )
    {
      rc = false;
    }
    else 
    {
      m_bFreeBMI = 1;
      m_bmi->bmiHeader = bmiHeader;
      m_bmi->bmiHeader.biSize = sizeof(m_bmi->bmiHeader);
      const int color_count = ON_WindowsBitmapHelper_PaletteColorCount(bmiHeader.biClrUsed, bmiHeader.biBitCount );
      if ( sizeof_image > 0 )
        m_bits = (unsigned char*)&m_bmi->bmiColors[color_count];
      size_t sizeof_buffer = 0;
      rc = file.ReadCompressedBufferSize( &sizeof_buffer );
      if (rc)
      {
        const size_t sizeof_colors = color_count*sizeof(*m_bmi->bmiColors);
        if (    sizeof_buffer == sizeof_colors
             || sizeof_buffer == sizeof_colors + sizeof_image
           ) 
        {
          // palette and image bits are compressed into one or two chunks
          rc = file.ReadCompressedBuffer( sizeof_buffer, m_bmi->bmiColors, &bFailedCRC );
          if ( rc && sizeof_image > 0 && sizeof_buffer == sizeof_colors )
          {
            // 28 July 2003
            //     Added support for reading non-contiguous bitmaps
            sizeof_buffer = 0;
            rc = file.ReadCompressedBufferSize( &sizeof_buffer );
            if (rc)
            {
              if ( sizeof_buffer == sizeof_image )
              {
                // image bits are compressed into a separatechunk
                rc = file.ReadCompressedBuffer( sizeof_buffer, &m_bmi->bmiColors[color_count], &bFailedCRC );
              }
              else
              {
                ON_ERROR("ON_WindowsBitmap::ReadCompressed() image bits buffer size mismatch\n");
                rc = false;
              }
            }
          }
        }
        else
        {
          ON_ERROR("ON_WindowsBitmap::ReadCompressed() buffer size mismatch\n");
          rc = false;
        }
      }
    }
  }
  return rc;
}

bool ON_WindowsBitmap::IsContiguous() const
{
  bool rc = false;
  if ( 0 != m_bmi && 0 != m_bits && m_bmi->bmiHeader.biSizeImage > 0 )
  {
    // p1 points to the first byte after the color palette.
    unsigned char* p1 = (unsigned char*)&m_bmi->bmiColors[PaletteColorCount()];
    rc = ( m_bits == p1 );
  }
  return rc;
}

#if defined(ON_OS_WINDOWS_GDI)

#pragma message( " --- OpenNURBS including Windows BITMAPINFO support in ON_WindowsBitmap" )

ON_WindowsBitmap::ON_WindowsBitmap( const BITMAPINFO& src )
                 : m_bmi(0), m_bits(0), m_bFreeBMI(0)
{
  *this = src;
}

ON_WindowsBitmap& ON_WindowsBitmap::operator=( const BITMAPINFO& src )
{
  Internal_Destroy();
  ON_Bitmap::operator=(ON_Bitmap::Unset);

  int color_count = ON_WindowsBitmapHelper_PaletteColorCount(src.bmiHeader.biClrUsed, src.bmiHeader.biBitCount );
  Create(&src,(const unsigned char*)(&src.bmiColors[color_count]),true);
  return *this;
}

bool ON_WindowsBitmap::Create( const BITMAPINFO* bmi, const unsigned char* bits, bool bCopy )
{
  Internal_Destroy();

  bool rc = false;

  m_bFreeBMI = 0;
  m_bmi = 0;
  m_bits = 0;

  if ( 0 != bmi )
  {
    if ( bCopy ) 
    {
      // allocate a contiguous Windows device independent bitmap
      const size_t sizeof_palette = ON_WindowsBitmapHelper_SizeofPalette(bmi->bmiHeader.biClrUsed, bmi->bmiHeader.biBitCount );
      const int sizeof_image   = bmi->bmiHeader.biSizeImage;
      m_bmi = ON_WindowsBitmapHelper_AllocBMI( sizeof_palette, (bCopy?sizeof_image:0) );
      if ( 0 != m_bmi ) 
      {
        rc = true;
        m_bFreeBMI = 1; // ~ON_WindowsBitmap will free the m_bmi pointer
        m_bmi->bmiHeader = bmi->bmiHeader;
        m_bmi->bmiHeader.biSize = sizeof(m_bmi->bmiHeader);
        int color_count = ON_WindowsBitmapHelper_PaletteColorCount(bmi->bmiHeader.biClrUsed, bmi->bmiHeader.biBitCount );
        if ( color_count > 0 )
        {
          memcpy( &m_bmi->bmiColors[0], &bmi->bmiColors[0], color_count*sizeof(m_bmi->bmiColors[0]) );
        }
        if ( bCopy && sizeof_image > 0 )
        {
          m_bits = (unsigned char*)(&m_bmi->bmiColors[color_count]);
          if ( 0 != bits )
            memcpy( m_bits, bits, sizeof_image );
          else
            memset( m_bits, 0, sizeof_image );
        }
      }
    }
    else
    {
      // share BITMAPINFO memory
      rc = true;
      m_bmi = const_cast<BITMAPINFO*>(bmi);
      m_bits = const_cast<unsigned char*>(bits);
    }
  }

  return rc;
}

ON_WindowsBitmap::ON_WindowsBitmap( const BITMAPINFO* src )
{
  if ( 0 != src )
  {
    int color_count = ON_WindowsBitmapHelper_PaletteColorCount(src->bmiHeader.biClrUsed, src->bmiHeader.biBitCount );
    Create(src,(const unsigned char*)(&src->bmiColors[color_count]),false);
  }
}

#endif

#if !defined(ON_OS_WINDOWS_GDI)
bool ON_WindowsBitmap::Create(const struct ON_WindowsBITMAPINFO* src)
{
  bool rc = false;
  *this = ON_WindowsBitmap::Unset;
  m_bFreeBMI = 0;
  m_bmi = 0;
  m_bits = 0;
  if ( 0 != src )
  {
    rc = true;
    int color_count = ON_WindowsBitmapHelper_PaletteColorCount(src->bmiHeader.biClrUsed, src->bmiHeader.biBitCount );
    m_bmi = (struct ON_WindowsBITMAPINFO*)src;
    m_bits = (unsigned char*)(&src->bmiColors[color_count]);
  }
  return rc;
}
#endif

////////////////////////////////////////////////////////////////////////////////
//
// ON_EmbeddedBitmap - used to embed bitmaps in 3dm archives
//

ON_EmbeddedBitmap::~ON_EmbeddedBitmap()
{
  Internal_Destroy();
}

ON_EmbeddedBitmap::ON_EmbeddedBitmap(const ON_EmbeddedBitmap& src)
  : ON_Bitmap(src)
{
  Internal_Copy(src);
}

ON_EmbeddedBitmap& ON_EmbeddedBitmap::operator=(const ON_EmbeddedBitmap& src)
{
  if (this != &src)
  {
    Internal_Destroy();
    ON_Bitmap::operator=(src);
    Internal_Copy(src);
  }
  return *this;
}

void ON_EmbeddedBitmap::Internal_Copy(
  const ON_EmbeddedBitmap& src
  )
{
  if (nullptr != src.m_buffer && src.m_sizeof_buffer > 0)
  {
    Create(src.m_sizeof_buffer);
    if (nullptr != m_buffer && m_sizeof_buffer == src.m_sizeof_buffer)
    {
      memcpy(const_cast<void*>(m_buffer), src.m_buffer, m_sizeof_buffer);
      m_buffer_crc32 = src.m_buffer_crc32;
    }
  }
}

void ON_EmbeddedBitmap::Internal_Destroy()
{
  void* p
    = (m_managed_buffer && m_sizeof_buffer > 0)
    ? const_cast<void*>(m_buffer)
    : nullptr;
  m_buffer = nullptr;
  m_sizeof_buffer = 0;
  m_managed_buffer = false;
  m_buffer_crc32 = 0;
  if (nullptr != p)
    onfree(p);
}


void ON_EmbeddedBitmap::Create( size_t sizeof_buffer )
{
  Internal_Destroy();
  if ( sizeof_buffer > 0 )
  {
    m_buffer = onmalloc(sizeof_buffer);
    if ( 0 != m_buffer )
    {
      m_sizeof_buffer = sizeof_buffer;
      m_managed_buffer = true;
    }
  }
}

bool ON_EmbeddedBitmap::IsValid( ON_TextLog* text_log ) const
{
  if ( 0 == m_buffer )
  {
    if ( 0 != text_log )
      text_log->Print("ON_EmbeddedBitmap m_buffer = 0\n");
    return false;
  }
  return true;
}

bool ON_EmbeddedBitmap::Write(ON_BinaryArchive& archive) const
{
  return Internal_WriteV5(archive);
}

bool ON_EmbeddedBitmap::Internal_WriteV5( ON_BinaryArchive& file ) const 
{
  bool rc = false;
  for (;;)
  {
    if (!file.Write3dmChunkVersion(1, 1))
      break;

    if (!file.WriteString( FileReference().FullPath() ) )
      break;

    if (!file.WriteInt(m_buffer_crc32))
      break;

    const int i = 1; // 1 = compressed
    if (!file.WriteInt(i))
      break;

    if (!file.WriteCompressedBuffer(m_sizeof_buffer, m_buffer))
      break;

    // version 1.1 added id and name
    if (!file.WriteUuid(Id()))
      break;
    if (!file.WriteString(Name()))
      break;

    rc = true;
    break;
  }

  return rc;
}

bool ON_EmbeddedBitmap::Read(ON_BinaryArchive& archive)
{
  return Internal_ReadV5(archive);
}

bool ON_EmbeddedBitmap::Internal_ReadV5( ON_BinaryArchive& file ) 
{
  Internal_Destroy();

  bool rc = false;
  for (;;)
  {
    int major_version = 0;
    int minor_version = 0;
    if (!file.Read3dmChunkVersion(&major_version, &minor_version))
      break;

    if (1 != major_version)
      break;

    ON_wString file_name;
    if (!file.ReadString(file_name))
      break;
    ON_FileReference file_reference;
    if (ON_FileSystemPath::IsRelativePath(file_name))
      file_reference.SetRelativePath(file_name);
    else
      file_reference.SetFullPath(file_name, false);
    SetFileReference(file_reference);

    if (!file.ReadInt(&m_buffer_crc32))
      break;

    int i = -1;
    if (!file.ReadInt(&i))
      break;
    if (0 == i)
    {
      // uncompressed
      if (!file.ReadSize(&m_sizeof_buffer))
        break;
      void* buffer = nullptr;
      if (m_sizeof_buffer > 0)
      {
        buffer = onmalloc(m_sizeof_buffer);
        m_managed_buffer = true;
      }
      if (!file.ReadByte(m_sizeof_buffer, buffer))
      {
        if (nullptr != buffer)
          onfree(buffer);
        break;
      }
      m_buffer = buffer;
    }
    else if (1 == i)
    {
      // compressed
      if (!file.ReadCompressedBufferSize(&m_sizeof_buffer))
        break;
      void* buffer = nullptr;
      if (m_sizeof_buffer > 0)
      {
        buffer = onmalloc(m_sizeof_buffer);
      }
      bool bFailedCRC = false;
      if (!file.ReadCompressedBuffer(m_sizeof_buffer, buffer, &bFailedCRC))
      {
        if (nullptr != buffer)
          onfree(buffer);
        break;
      }
      m_buffer = buffer;
      m_managed_buffer = true;
    }
    else
    {
      // invalid 
      break;
    }

    if (minor_version < 1)
    {
      rc = true;
      break;
    }    

    // version 1.1 added id and name
    ON_UUID bitmap_id = ON_nil_uuid;
    if (!file.ReadUuid(bitmap_id))
      break;
    SetId(bitmap_id);

    ON_wString bitmap_name;
    if (!file.ReadString(bitmap_name))
      break;
    SetName(bitmap_name);

    rc = true;
    break;
  }

  if (rc && IdIsNil())
    SetId(); // older formats had nil ids - modern times require unique component ids.

  return rc;
}

size_t ON_EmbeddedBitmap::SizeofImage() const
{
  return m_sizeof_buffer;
}

unsigned char* ON_EmbeddedBitmap::Bits(int scan_line_index) 
{
  return const_cast<unsigned char*>((0 == scan_line_index) ? static_cast<const unsigned char*>(m_buffer) : nullptr);
}

const unsigned char* ON_EmbeddedBitmap::Bits(int scan_line_index) const
{
  return (0 == scan_line_index) ? static_cast<const unsigned char*>(m_buffer) : nullptr;
}

unsigned int ON_Bitmap::SizeOf() const
{
  const size_t sizeof_baseclass = ON_ModelComponent::SizeOf();
  const size_t sizeof_class = sizeof(*this) - sizeof(ON_ModelComponent);
  return (unsigned int)(sizeof_baseclass + sizeof_class);
}

unsigned int ON_EmbeddedBitmap::SizeOf() const
{
  const size_t sizeof_baseclass = ON_Bitmap::SizeOf();
  const size_t sizeof_class = sizeof(*this) - sizeof(ON_Bitmap);
  return (unsigned int)(sizeof_baseclass + sizeof_class + m_sizeof_buffer);
}

unsigned int ON_WindowsBitmap::SizeOf() const
{
  const size_t sizeof_baseclass = ON_Bitmap::SizeOf();
  const size_t sizeof_class = sizeof(*this) - sizeof(ON_Bitmap);
  return (unsigned int)(sizeof_baseclass + sizeof_class + SizeofImage());
}

