/** \file
 * \brief Windows DIB
 *
 * See Copyright Notice in im_lib.h
 */


#include <windows.h>
#include <assert.h>
#include <stdlib.h>

#include "im_dib.h"

/*****************
  Private Funtions
*****************/

/* Long returned in getpixel is an array of 4 bytes, not actually a DWORD */
/* 32 bpp max */
/* Windows use Little Endian always, this means LSB first: 0xF3F2F1F0 = "F0F1F2F3" */

#define iSETDWORD(_vLong, _Line, _Nb)                    \
        {                                                  \
          unsigned char* _pLong = (unsigned char*)&_vLong; \
          int b = _Nb;                                     \
          while(b--)                                       \
            *_pLong++ = *_Line++;                          \
        }

#define iGETDWORD(_vLong, _Line, _Nb)                    \
        {                                                  \
          unsigned char* _pLong = (unsigned char*)&_vLong; \
          int b = _Nb;                                     \
          while(b--)                                       \
            *_Line++ = *_pLong++;                          \
        }

#define iGETDWORDMASK(_vLong, _vMask, _Line, _Nb)        \
        {                                                  \
          unsigned char* _pLong = (unsigned char*)&_vLong; \
          unsigned char* _pMask = (unsigned char*)&_vMask; \
          int b = _Nb;                                     \
          while(b--)                                       \
            *_Line++ = *_pLong++ | (~*_pMask++ & *_Line);  \
        }

static unsigned int iMakeBitMask(int bpp)
{
  unsigned int mask = 1;

  while (bpp > 1)
  {
    mask = (mask << 1) + 1;
    bpp--;
  }

  return mask;
}

static unsigned int iLineGetPixel1(unsigned char* line, int col)
{
  return (line[col / 8] >> (7 - col % 8)) & 0x01;        /* LSB is filled */
}

static void iLineSetPixel1(unsigned char* line, int col, unsigned int pixel)
{
  if (pixel)                                             /* only test 1/0 */
    line[col / 8] |= (0x01 << (7 - (col % 8)));
  else
    line[col / 8] &= (0xFE << (7 - (col % 8)));
}

static unsigned int iLineGetPixel4(unsigned char* line, int col)
{
  return (line[col / 2] >> ((1 - col % 2) * 4)) & 0x0F;  /* LSB is filled */
}

static void iLineSetPixel4(unsigned char* line, int col, unsigned int pixel)
{
  unsigned char mask = (col % 2)? 0xF0: 0x0F;            /* LSB is used */
  line[col/2] = (unsigned char)((mask & (((unsigned char)pixel) << ((1 - col % 2) * 4))) | (~mask & line[col/2]));
}

static unsigned int iLineGetPixel8(unsigned char* line, int col)
{
  return line[col];                                      /* LSB is filled */
}

static void iLineSetPixel8(unsigned char* line, int col, unsigned int pixel)
{
  line[col] = (unsigned char)pixel;                      /* LSB is used */
}

static unsigned int iLineGetPixel16(unsigned char* line, int col)
{
  return ((unsigned short*)line)[col];                   /* 0xF1F0 => "F0F10000" */
}

static void iLineSetPixel16(unsigned char* line, int col, unsigned int pixel)
{
  ((unsigned short*)line)[col] = (unsigned short)pixel;  /* inverse of above */
}

static unsigned int iLineGetPixel24(unsigned char* line, int col)
{
  unsigned int pixel = 0;
  line += col*3;
  iSETDWORD(pixel, line, 3);
  return pixel;
}

static void iLineSetPixel24(unsigned char* line, int col, unsigned int pixel)
{
  line += col*3;
  iGETDWORD(pixel, line, 3);
}

static unsigned int iLineGetPixel32(unsigned char* line, int col)
{
  return ((unsigned int*)line)[col];                    /* direct mapping */
}

static void iLineSetPixel32(unsigned char* line, int col, unsigned int pixel)
{
  ((unsigned int*)line)[col] = pixel;                   /* direct mapping */
}

static int iGetPixelAnyBpp = 0;
static unsigned int iGetPixelAnyMask = 0;

static unsigned int iAnyGet(unsigned char* line, int col, int bpp)
{
  int s_byte = (col*bpp) >> 3;
  int s_bit = (col*bpp) & 0x7;
  unsigned int pixel = 0;
  unsigned int mask = (~0) >> (32-bpp);
  int n_bytes = (bpp + s_bit + 7) >> 3;
  int shift = (n_bytes << 3) - bpp - s_bit;
  line += s_byte;
  while (n_bytes)
  {
    pixel |= *line++;
    if (--n_bytes > 0) pixel <<= 8;
    else break;
  }
  pixel >>= shift;
  return pixel & mask;
}

static void iAnySet(unsigned char* line, int col, int bpp, unsigned int pixel)
{
  int s_byte = (col*bpp) >> 3;
  int s_bit = (col*bpp) & 0x7;
  unsigned int mask = (~0) >> (32-bpp);
  int n_bytes = (bpp + s_bit + 7) >> 3;
  int shift = (n_bytes << 3) - bpp - s_bit;
  unsigned char* p_pixel = (unsigned char*) &pixel, *p_mask = (unsigned char*) &mask;
  line += s_byte + n_bytes - 1;
  pixel <<= shift;
  mask <<= shift;
  while (n_bytes--) {
    *line = (*line & ~(*p_mask)) | (*p_pixel & *p_mask);
    p_mask++; p_pixel++; line--;
  }
}

static unsigned int iLineGetPixelAny(unsigned char* line, int col)
{
  return iAnyGet(line, col, iGetPixelAnyBpp);
#if 0
  unsigned int pixel = 0;
  int rbits  = (col * iGetPixelAnyBpp) % 8;       /* calc remaining bits */
  line      += (col * iGetPixelAnyBpp) / 8;       /* position pointer */

  /* transfer from pixel line to a DWORD in little endian, so it can be shifted */
  {
    int nbytes = (iGetPixelAnyBpp + rbits + 7) / 8; /* bytes used */
    iSETDWORD(pixel, line, nbytes);
  }

  /* shift down pixel remaining bits and mask extra non pixel bits */
  return (pixel >> rbits) & iGetPixelAnyMask;
#endif
}

static int iSetPixelAnyBpp = 0;
static unsigned int iSetPixelAnyMask = 0;

static void iLineSetPixelAny(unsigned char* line, int col, unsigned int pixel)
{
  iAnySet(line, col, iSetPixelAnyBpp, pixel);
#if 0
  int rbits  = (col * iSetPixelAnyBpp) % 8;       /* calc remaining bits */
  line      += (col * iSetPixelAnyBpp) / 8;       /* position pointer */

  pixel = pixel << rbits; /* position bits */

  {
    unsigned int mask = iSetPixelAnyMask << rbits; /* position mask */
    int nbytes = (iGetPixelAnyBpp + rbits + 7) / 8; /* bytes used */
    iGETDWORDMASK(pixel, mask, line, nbytes);
  }                      
#endif
}

static long iQuad2Long(RGBQUAD* quad_color)
{
  return (((unsigned long)quad_color->rgbRed) << 16) |
         (((unsigned long)quad_color->rgbGreen) <<  8) |
         (((unsigned long)quad_color->rgbBlue) <<  0);
}

static RGBQUAD iLong2Quad(long long_color)
{
  RGBQUAD quad_color;
  
  quad_color.rgbRed = (unsigned char)(((long_color) >> 16) & 0xFF);
  quad_color.rgbGreen = (unsigned char)(((long_color) >>  8) & 0xFF);
  quad_color.rgbBlue = (unsigned char)(((long_color) >>  0) & 0xFF);
 
  return quad_color;
}

static int iImageLineSize(int width, int bpp)
{
  return (width * bpp + 7) / 8;            /* 1 byte boundary */
}

static int iLineSize(int width, int bpp)
{
  return ((width * bpp + 31) / 32) * 4;   /* 4 bytes boundary */
}

static void iInitHeadersReference(imDib* dib)
{
  dib->bmi = (BITMAPINFO*)dib->buffer;
  dib->bmih = (BITMAPINFOHEADER*)dib->buffer;
  dib->bmic = (RGBQUAD*)(dib->buffer + sizeof(BITMAPINFOHEADER));
}

static void iInitSizes(imDib* dib, int width, int height, int bpp)
{
  dib->line_size = iLineSize(width, bpp);
  dib->pad_size = dib->line_size - iImageLineSize(width, bpp);
  dib->bits_size = dib->line_size * height;
  dib->size = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * dib->palette_count + dib->bits_size;
}

static void iInitInfoHeader(BITMAPINFOHEADER* bmih, int width, int height, int bpp, int palette_count)
{
  bmih->biSize = sizeof(BITMAPINFOHEADER);
  bmih->biWidth = width;
  bmih->biHeight = height;
  bmih->biPlanes = 1;
  bmih->biBitCount = (WORD)bpp;
  bmih->biCompression = 0;
  bmih->biSizeImage = 0;
  bmih->biClrUsed = palette_count;
  bmih->biClrImportant = 0;

  {
    HDC ScreenDC = GetDC(NULL);

    bmih->biXPelsPerMeter = (unsigned int)(GetDeviceCaps(ScreenDC, LOGPIXELSX) / 0.0254);
    bmih->biYPelsPerMeter = (unsigned int)(GetDeviceCaps(ScreenDC, LOGPIXELSY) / 0.0254);

    ReleaseDC(NULL, ScreenDC);
  }
}

static void iInitBits(imDib* dib, BYTE* bits)
{
  if (bits == NULL)
    dib->bits = dib->buffer + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * dib->palette_count;
  else
    dib->bits = bits;
}

static int iGetValidBpp(int bpp)
{
  if (bpp == 1) 
    bpp = 1; 
  else if (bpp <= 4) 
    bpp = 4; 
  else if (bpp <= 8) 
    bpp = 8; 
  else if (bpp <= 16) 
    bpp = 16; 
  else if (bpp <= 24) 
    bpp = 24; 
  else if (bpp <= 32)
    bpp = 32; 
  else
    bpp = 0;

  return bpp;
}

static int iCheckHeader(BITMAPINFOHEADER *bmih)
{
  if (bmih->biSize != sizeof(BITMAPINFOHEADER))
    return 0;

  if (bmih->biWidth <= 0)
    return 0;

  if (bmih->biHeight == 0)
    return 0;

  {
    int bpp = iGetValidBpp(bmih->biBitCount);
    if (!bpp)
      return 0;

    if (bmih->biCompression == BI_RLE8 && bpp != 8)
      return 0;

    if (bmih->biCompression == BI_RLE4 && bpp != 4)
      return 0;

    if (bmih->biCompression == BI_BITFIELDS && (bpp != 16 || bpp != 32))
      return 0;

    if (bmih->biHeight < 0 && (bmih->biCompression == BI_RLE8 || bmih->biCompression == BI_RLE4))
      return 0;

/*    if (bmih->biCompression == BI_JPEG || bmih->biCompression == BI_PNG)
      return 0; */
  }

  return 1;
}

/*****************
  Creation
*****************/

static void AllocHandle(imDib* dib) 
{
  dib->buffer = NULL;
  dib->handle = GlobalAlloc(GMEM_MOVEABLE, dib->size); 
  if (!dib->handle) return;
  dib->buffer = (BYTE*)GlobalLock(dib->handle); 
}

imDib* imDibCreate(int width, int height, int bpp)
{
  imDib* dib;
  int obpp = bpp;

  bpp = iGetValidBpp(abs(bpp));
  
  assert(width > 0 && height > 0);
  assert(bpp);

  dib = (imDib*)malloc(sizeof(imDib));
  memset(dib, 0, sizeof(imDib));

  if (bpp > 8)
  {
    if ((bpp == 16 || bpp == 32) && obpp < 0)
      dib->palette_count = 3;
    else
      dib->palette_count = 0;
  }
  else
    dib->palette_count = 1 << bpp;
  
  iInitSizes(dib, width, height, bpp);
                         
  AllocHandle(dib);
  if (dib->buffer == NULL)
  {
    free(dib);
    return NULL;
  }

  iInitHeadersReference(dib);

  iInitInfoHeader(dib->bmih, width, height, bpp, dib->palette_count);

  iInitBits(dib, NULL);

  
  return dib;
}

imDib* imDibCreateSection(HDC hDC, HBITMAP *bitmap, int width, int height, int bpp)
{
  BITMAPINFO* bmi;
  BYTE* bits;
  int palette_count;
  int obpp = bpp;

  bpp = iGetValidBpp(abs(bpp));

  assert(hDC);
  assert(width > 0 && height > 0);
  assert(bpp);

  if (bpp > 8)
  {
    if ((bpp == 16 || bpp == 32) && obpp < 0)
      palette_count = 3;  
    else
      palette_count = 0;
  }
  else
    palette_count = 1 << bpp;

  bmi = (BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * palette_count);

  iInitInfoHeader(&bmi->bmiHeader, width, height, bpp, palette_count);

  if (bpp > 8 && palette_count == 3)
  {
    DWORD *masks = (DWORD*)(bmi + sizeof(BITMAPINFOHEADER));
    masks[0] = 0x001F;
    masks[1] = 0x03E0;
    masks[2] = 0x7C00;
  }

  /* ATTENTION: You cannot paste a DIB section from one application into another application. */
  *bitmap = CreateDIBSection(hDC, bmi, DIB_RGB_COLORS, (void**)&bits, NULL, 0);

  imDib* dib = imDibCreateReference((BYTE*)bmi, bits);
  dib->free_buffer = 1;
  return dib;
}


imDib* imDibCreateCopy(const imDib* src_dib)
{
  imDib* dib;

  assert(src_dib);

  dib = (imDib*)malloc(sizeof(imDib));
  memcpy(dib, src_dib, sizeof(imDib));

  AllocHandle(dib);
  if (dib->buffer == NULL)
  {
    free(dib);
    return NULL;
  }

  iInitHeadersReference(dib);

  memcpy(dib->buffer, src_dib->buffer, dib->size - dib->bits_size);

  iInitBits(dib, NULL);

  memcpy(dib->bits, src_dib->bits, dib->bits_size);

  return dib;
}

imDib* imDibCreateReference(BYTE* bmi, BYTE* bits)
{
  imDib* dib;

  assert(bmi);

  dib = (imDib*)malloc(sizeof(imDib));
  memset(dib, 0, sizeof(imDib));

  dib->buffer = bmi;
  dib->handle = NULL; /* dib->handle is NOT allocated */

  iInitHeadersReference(dib);
  
  if (dib->bmih->biBitCount > 8)
  {
    dib->palette_count = 0;
    
    if (dib->bmih->biCompression == BI_BITFIELDS)
      dib->palette_count = 3;
  }
  else
  {
    if (dib->bmih->biClrUsed != 0)
      dib->palette_count = dib->bmih->biClrUsed;
    else
      dib->palette_count = 1 << dib->bmih->biBitCount;
  }
  
  iInitBits(dib, bits);

  iInitSizes(dib, dib->bmih->biWidth, abs(dib->bmih->biHeight), dib->bmih->biBitCount);

  return dib;
}

void imDibDestroy(imDib* dib)
{
  assert(dib);

  if (dib->handle) 
  {
    GlobalUnlock(dib->handle);
    GlobalFree(dib->handle);
  }

  if (dib->free_buffer)
    free(dib->buffer);

  free(dib);
}

/*****************
  Line Access
*****************/

imDibLineGetPixel imDibLineGetPixelFunc(int bpp)
{
  switch(bpp)
  {
  case 1:
    return &iLineGetPixel1;
  case 4:
    return &iLineGetPixel4;
  case 8:
    return &iLineGetPixel8;
  case 16:
    return &iLineGetPixel16;
  case 24:
    return &iLineGetPixel24;
  case 32:
    return &iLineGetPixel32;
  default:
    if (bpp > 32) return NULL;
    iGetPixelAnyBpp = bpp;
    iGetPixelAnyMask = iMakeBitMask(bpp);
    return &iLineGetPixelAny;
  }
}

imDibLineSetPixel imDibLineSetPixelFunc(int bpp)
{
  switch(bpp)
  {
  case 1:
    return &iLineSetPixel1;
  case 4:
    return &iLineSetPixel4;
  case 8:
    return &iLineSetPixel8;
  case 16:
    return &iLineSetPixel16;
  case 24:
    return &iLineSetPixel24;
  case 32:
    return &iLineSetPixel32;
  default:
    if (bpp > 32) return NULL;
    iSetPixelAnyBpp = bpp;
    iSetPixelAnyMask = iMakeBitMask(bpp);
    return &iLineSetPixelAny;
  }
}

/*****************
  DIB <-> Bitmap
*****************/

imDib* imDibFromHBitmap(const HBITMAP bitmap, const HPALETTE hPalette)
{
  imDib* dib;
  BITMAP bmp;

  assert(bitmap);
 
  if (!GetObject(bitmap, sizeof(BITMAP), (LPSTR)&bmp)) 
    return NULL;
 
  dib = imDibCreate(bmp.bmWidth, bmp.bmHeight, bmp.bmPlanes * bmp.bmBitsPixel);

  if (!dib)
    return NULL;
  
  {
    HDC ScreenDC = GetDC(NULL);
    HPALETTE hOldPalette = NULL;
    if (hPalette) hOldPalette = SelectPalette(ScreenDC, hPalette, FALSE);
    RealizePalette(ScreenDC);

    GetDIBits(ScreenDC, bitmap, 0, dib->bmih->biHeight, dib->bits, dib->bmi, DIB_RGB_COLORS);	

    if (hOldPalette) SelectPalette(ScreenDC, hOldPalette, FALSE);
    ReleaseDC(NULL, ScreenDC);
  }
  
  return dib;
}

HBITMAP imDibToHBitmap(const imDib* dib)
{
  HBITMAP bitmap;

  assert(dib);

  {
    HDC ScreenDC = GetDC(NULL);
    bitmap = CreateDIBitmap(ScreenDC, dib->bmih, CBM_INIT, dib->bits, dib->bmi, DIB_RGB_COLORS);
    ReleaseDC(NULL, ScreenDC);
  }

/* 
  Another Way
  bitmap = CreateCompatibleBitmap(ScreenDC, dib->bmih->biWidth, dib->bmih->biHeight);
  SetDIBits(ScreenDC, bitmap, 0, dib->bmih->biHeight, dib->bits, dib->bmi, DIB_RGB_COLORS);	
*/

  return bitmap;
}

/*******************
  DIB <-> Clipboard
*******************/

int imDibIsClipboardAvailable(void)
{
  if (IsClipboardFormatAvailable(CF_DIB) ||
      IsClipboardFormatAvailable(CF_BITMAP))
    return 1;

  return 0;
}

imDib* imDibPasteClipboard(void)
{
  int clip_type = 0;
  if (IsClipboardFormatAvailable(CF_DIB)) 
    clip_type = CF_DIB;
  else if (IsClipboardFormatAvailable(CF_BITMAP)) 
    clip_type = CF_BITMAP;

  if (!clip_type)
    return NULL;

  OpenClipboard(NULL);
  HANDLE Handle = GetClipboardData(clip_type);
  if (Handle == NULL)
  {
    CloseClipboard();
    return NULL;
  }
  
  imDib *dib;
  if (clip_type == CF_DIB)
  {
    BYTE* bmi = (BYTE*)GlobalLock(Handle);
    if (!bmi || !iCheckHeader((BITMAPINFOHEADER*)bmi))
    {
      CloseClipboard();
      return NULL;
    }

    {
      imDib* clip_dib = imDibCreateReference(bmi, NULL);
      dib = imDibCreateCopy(clip_dib);
      imDibDestroy(clip_dib);
      GlobalUnlock(Handle);
    }
  }
  else
  {
    HPALETTE hpal = (HPALETTE)GetClipboardData(CF_PALETTE);

    /* If there is a CF_PALETTE object in the clipboard, this is the palette to assume */
    /* the bitmap is realized against.                                                 */
    if (!hpal)
      hpal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);

    dib = imDibFromHBitmap((HBITMAP)Handle, hpal);
  }
  
  CloseClipboard();
  
  return dib;
}  

void imDibCopyClipboard(imDib* dib)
{
  assert(dib);

  if (!dib->handle)
    return;

  if (!OpenClipboard(NULL))
    return;
  EmptyClipboard();
  GlobalUnlock(dib->handle);
  SetClipboardData(CF_DIB, dib->handle);
  CloseClipboard();

  dib->buffer = NULL;
  dib->handle = NULL;
  imDibDestroy(dib);
}

/*******************
  DIB -> Palette
*******************/

HPALETTE imDibLogicalPalette(const imDib* dib)
{
  LOGPALETTE* pLogPal;      
  PALETTEENTRY* pPalEntry;
  HPALETTE hPal;
  RGBQUAD* bmic;
  int c;
  
  assert(dib);
  assert(dib->bmih->biBitCount <= 8);
  
  pLogPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + dib->palette_count * sizeof(PALETTEENTRY));
  pLogPal->palVersion    = 0x300; 
  pLogPal->palNumEntries = (WORD)dib->palette_count;
  
  bmic = dib->bmic;
  pPalEntry = pLogPal->palPalEntry;
  
  for (c = 0; c < dib->palette_count; c++) 
  {
    pPalEntry->peRed   = bmic->rgbRed;
    pPalEntry->peGreen = bmic->rgbGreen;
    pPalEntry->peBlue  = bmic->rgbBlue;
    pPalEntry->peFlags = PC_NOCOLLAPSE;
    
    pPalEntry++;
    bmic++;
  }
  
  hPal = CreatePalette(pLogPal);
  free(pLogPal);
  
  return hPal;
}

/*******************
  DIB <-> RGB Image
*******************/

void imDibEncodeFromRGBA(imDib* dib, const unsigned char* red, const unsigned char* green, const unsigned char* blue, const unsigned char* alpha)
{
  int x, y;
  BYTE* bits;
  
  if (dib->bmih->biHeight < 0)
    bits = dib->bits + (dib->bits_size - dib->line_size); /* start of last line */
  else
    bits = dib->bits;
  
  assert(dib->bmih->biBitCount > 16);
  
  for (y = 0; y < abs(dib->bmih->biHeight); y++)
  {
    for (x = 0; x < dib->bmih->biWidth; x++)
    {
      *bits++ = *blue++;
      *bits++ = *green++;
      *bits++ = *red++;

      if (dib->bmih->biBitCount == 32)
      {
        if (alpha)
          *bits++ = *alpha++;
        else
          *bits++ = 0xFF; /* opaque */
      }
    }
    
    bits += dib->pad_size;

    if (dib->bmih->biHeight < 0)
      bits -= 2*dib->line_size;
  }
}

void imDibDecodeToRGBA(const imDib* dib, unsigned char* red, unsigned char* green, unsigned char* blue, unsigned char* alpha)
{
  int x, y, offset;
  unsigned short color;
  BYTE* bits;
  unsigned int rmask = 0, gmask = 0, bmask = 0, 
                roff = 0, goff = 0, boff = 0; /* pixel bit mask control when reading 16 and 32 bpp images */
  
  assert(dib);
  assert(dib->bmih->biBitCount > 8);
  assert(red && green && blue);

  if (dib->bmih->biHeight < 0)
    bits = dib->bits + (dib->bits_size - dib->line_size); /* start of last line */
  else
    bits = dib->bits;
  
  if (dib->bmih->biBitCount == 16)
    offset = dib->line_size;  /* do not increment for each pixel, jump line */
  else
    offset = dib->pad_size;   /* increment for each pixel, jump pad */
  
  if (dib->bmih->biCompression == BI_BITFIELDS)
  {
    unsigned int Mask;
    unsigned int* palette = (unsigned int*)dib->bmic;
    
    rmask = Mask = palette[0];
    while (!(Mask & 0x01))
    {Mask >>= 1; roff++;}
    
    gmask = Mask = palette[1];
    while (!(Mask & 0x01))
    {Mask >>= 1; goff++;}
    
    bmask = Mask = palette[2];
    while (!(Mask & 0x01))
    {Mask >>= 1; boff++;}
  }
  else if (dib->bmih->biBitCount == 16)
  {
    /* default mask if BI_BITFIELDS is not used */
    bmask = 0x001F;
    gmask = 0x03E0;
    rmask = 0x7C00;
    boff = 0;
    goff = 5;
    roff = 10;
  }
  
  for (y = 0; y < abs(dib->bmih->biHeight); y++)
  {
    for (x = 0; x < dib->bmih->biWidth; x++)
    {
      if (dib->bmih->biBitCount == 16)
      {
        color = ((unsigned short*)bits)[x];
        *red++ = (unsigned char)((((rmask & color) >> roff) * 255) / (rmask >> roff));
        *green++ = (unsigned char)((((gmask & color) >> goff) * 255) / (gmask >> goff));
        *blue++ = (unsigned char)((((bmask & color) >> boff) * 255) / (bmask >> boff));
      }
      else
      {
        *blue++ = *bits++;
        *green++ = *bits++;
        *red++ = *bits++;
        
        if (dib->bmih->biBitCount == 32)
        {
          if (alpha)
            *alpha++ = *bits++;
          else
            bits++;
        }
      }
    }
    
    bits += offset;

    if (dib->bmih->biHeight < 0)
      bits -= 2*dib->line_size;
  }
}

/*******************
  DIB <-> Map Image
*******************/

void imDibEncodeFromMap(imDib* dib, const unsigned char* map, const long* palette, int palette_count)
{
  assert(dib);
  assert(map && palette);
  assert(dib->bmih->biBitCount <= 8);
  assert(dib->bmih->biCompression != BI_RLE8);

  {
    int x, y;
    BYTE* bits;
    
    if (dib->bmih->biHeight < 0)
      bits = dib->bits + (dib->bits_size - dib->line_size); /* start of last line */
    else
      bits = dib->bits;

    for (y = 0; y < abs(dib->bmih->biHeight); y++)
    {
      for (x = 0; x < dib->bmih->biWidth; x++)
        bits[x] = *map++;
    
      if (dib->bmih->biHeight < 0)
        bits -= dib->line_size;
      else
        bits += dib->line_size;
    }
  }

  {
    int c;
    RGBQUAD* bmic = dib->bmic;

    for (c = 0; c < palette_count; c++)
      *bmic++ = iLong2Quad(palette[c]);
  }

  dib->bmih->biClrUsed = palette_count;
  dib->bmih->biClrImportant = 0;
  dib->palette_count = palette_count;
}

void imDibDecodeToMap(const imDib* dib, unsigned char* map, long* palette)
{
  assert(dib);
  assert(dib->bmih->biBitCount <= 8);
  assert(map && palette);

  {
    int x, y;
    BYTE* bits;
    
    if (dib->bmih->biHeight < 0)
      bits = dib->bits + (dib->bits_size - dib->line_size); /* start of last line */
    else
      bits = dib->bits;
  
    for (y = 0; y < abs(dib->bmih->biHeight); y++)
    {
      for (x = 0; x < dib->bmih->biWidth; x++)
      {
        switch (dib->bmih->biBitCount)
        {
        case 1:
          *map++ = (unsigned char)((bits[x / 8] >> (7 - x % 8)) & 0x01);
          break;
        case 4:
          *map++ = (unsigned char)((bits[x / 2] >> ((1 - x % 2) * 4)) & 0x0F);
          break;
        case 8:
          *map++ = bits[x];
          break;
        }
      }
    
      if (dib->bmih->biHeight < 0)
        bits -= dib->line_size;
      else
        bits += dib->line_size;
    }
  }
  
  {
    int c;
    RGBQUAD* bmic = dib->bmic;

    for (c = 0; c < dib->palette_count; c++)
    {
      palette[c] = iQuad2Long(bmic);
      *bmic++;
    }
  }
}

/*******************
  DIB <-> File
*******************/

int imDibSaveFile(const imDib* dib, const char* filename)
{ 
  DWORD dwTmp; 
  HANDLE hFile;                 /* file handle */ 
  BITMAPFILEHEADER file_header; /* bitmap file-header */ 

  assert(dib);
  assert(filename);

  hFile = CreateFile(filename, GENERIC_WRITE, (DWORD) 0, 
                 (LPSECURITY_ATTRIBUTES)NULL, 
                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL); 

  if (hFile == INVALID_HANDLE_VALUE) 
    return 0;

  /* 0x42 = "B" 0x4d = "M" */ 
  file_header.bfType = 0x4d42;        

  /* Compute the size of the entire file. */ 
  file_header.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dib->palette_count*sizeof(RGBQUAD) + dib->bits_size); 

  file_header.bfReserved1 = 0; 
  file_header.bfReserved2 = 0; 

  /* Compute the offset to the bits array. */ 
  file_header.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dib->palette_count*sizeof(RGBQUAD); 

  /* Copy the BITMAPFILEHEADER into the .BMP file. */ 
  if (!WriteFile(hFile, (LPVOID)&file_header, sizeof(BITMAPFILEHEADER), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
    goto save_error;

  /* Copy the BITMAPINFOHEADER into the file. */ 
  if (!WriteFile(hFile, (LPVOID)dib->bmih, sizeof(BITMAPINFOHEADER), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
    goto save_error;

  /* Copy the RGBQUAD array into the file. */ 
  if (dib->palette_count > 0)
  {
    if (!WriteFile(hFile, (LPVOID)dib->bmic, dib->palette_count*sizeof(RGBQUAD), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
      goto save_error;
  }

  /* Copy the bits array into the .BMP file. */ 
  if (!WriteFile(hFile, dib->bits, dib->bits_size, (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
    goto save_error;

  /* Close the .BMP file. */ 
  CloseHandle(hFile);

  return 1;

save_error:
  CloseHandle(hFile);
  return 0;
} 
 
imDib* imDibLoadFile(const char* filename)
{ 
  HANDLE hFile;                 /* file handle */ 
  DWORD dwTmp; 
  imDib* dib = NULL;
  BITMAPFILEHEADER file_header; /* bitmap file-header */ 
  BITMAPINFOHEADER bmih;

  assert(filename);

  hFile = CreateFile(filename, GENERIC_READ, (DWORD) 0, 
                 (LPSECURITY_ATTRIBUTES)NULL, 
                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL); 

  if (hFile == INVALID_HANDLE_VALUE) 
    return NULL;

  /* Read the BITMAPFILEHEADER from the .BMP file. */ 
  if (!ReadFile(hFile, (LPVOID)&file_header, sizeof(BITMAPFILEHEADER), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
    goto load_error;

  if (file_header.bfType != 0x4d42)
    goto load_error;

  /* Read the BITMAPINFOHEADER from the file. */ 
  if (!ReadFile(hFile, (LPVOID)&bmih, sizeof(BITMAPINFOHEADER), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
    goto load_error;

  if(!iCheckHeader(&bmih))
    goto load_error;

  dib = imDibCreate(bmih.biWidth, abs(bmih.biHeight), bmih.biCompression==BI_BITFIELDS? -bmih.biBitCount: bmih.biBitCount);

  memcpy(dib->bmih, &bmih, bmih.biSize);

  if (bmih.biSize != sizeof(BITMAPINFOHEADER))
  {
    /* skip newer BIH definitions */
    SetFilePointer(hFile, bmih.biSize - sizeof(BITMAPINFOHEADER), NULL, FILE_CURRENT);
    dib->bmih->biSize = sizeof(BITMAPINFOHEADER);
  }

  /* Read the RGBQUAD array from the file. */ 
  if (dib->palette_count > 0)
  {
    if (!ReadFile(hFile, (LPVOID)dib->bmic, dib->palette_count*sizeof(RGBQUAD), (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
      goto load_error;
  }

  /* Read the Bits array from the .BMP file. */ 
  SetFilePointer(hFile, file_header.bfOffBits, NULL, FILE_BEGIN);

  {
    int bits_size = dib->bits_size;

    if (bmih.biBitCount < 16 && bmih.biCompression != BI_RGB)
      bits_size = GetFileSize(hFile, NULL) - file_header.bfOffBits;

    if (bits_size > dib->bits_size)
      goto load_error;

    if (!ReadFile(hFile, dib->bits, bits_size, (LPDWORD)&dwTmp, (LPOVERLAPPED)NULL)) 
      goto load_error;
  }

  /* Close the .BMP file. */ 
  CloseHandle(hFile);

  return dib;

load_error:
  if (dib) imDibDestroy(dib);
  CloseHandle(hFile);
  return NULL;
} 

/*******************
  Screen -> DIB
*******************/

imDib* imDibCaptureScreen(int x, int y, int width, int height)
{
  HBITMAP bitmap;
  HDC ScreenDC = GetDC(NULL);
  HDC hdcCompatible = CreateCompatibleDC(ScreenDC); 

  if (width == 0) width = GetDeviceCaps(ScreenDC, HORZRES);
  if (height == 0) height = GetDeviceCaps(ScreenDC, VERTRES);

  bitmap = CreateCompatibleBitmap(ScreenDC, width, height);

  if (!bitmap) 
  {
    ReleaseDC(NULL, ScreenDC);
    return NULL;
  }

  /* Select the bitmaps into the compatible DC.  */
  SelectObject(hdcCompatible, bitmap);

  /* Copy color data for the entire display into a */
  /* bitmap that is selected into a compatible DC. */
  BitBlt(hdcCompatible, 0, 0, width, height, ScreenDC, x, y, SRCCOPY);

  ReleaseDC(NULL, ScreenDC);
  DeleteDC(hdcCompatible);

  {
    imDib* dib = imDibFromHBitmap(bitmap, NULL);
    DeleteObject(bitmap);
    return dib;
  }
}
