/* 
 *  $Id: DibSect.cpp,v 1.1.1.1 2007/10/05 08:30:20 dk Exp $
 *
 *  Adapted from code submitted by Jarek Jurasz
 * <jurasz@imb.uni-karlsruhe.de>. Thanks!
 *  
 *  This file is part of the Win32::GuiTest Perl module.
 * 
 *  You may distribute under the terms of either the GNU General Public
 *  License or the Artistic License.
 *
 */


#include "dibsect.h"
#include <stdio.h>
#include <stdlib.h>

/* JJ Some environment */
#define unless(x) if(!(x))
#define SelectBitmap (HBITMAP)SelectObject

DibSect::DibSect()
{
  ZeroMemory(this, sizeof(*this));

  biSize        = sizeof(BITMAPINFOHEADER);
  biPlanes      = 1; // Must always be 1 according to docs
  biBitCount    = 8; // by default
  biCompression = BI_RGB;
}//DibSect::DibSect()-----------------------------------------------------------


bool DibSect::Destroy()
{
  if (hBitmap)
  {
    DeleteObject(hBitmap);
    hBitmap = NULL;
    pBits = NULL;
  }
  return true;
}//DibSect::Destroy()----------------------------------------------------------

/*
  The hDC seems to be needed for DIB_PAL_COLORS only.
*/
HBITMAP DibSect::Create
(
  HDC hDC       // a DC with the right palette
)
{
  Destroy();

  // rounded up to full DWORDs
  cWidthBytes = (((biWidth * biBitCount) >> 3) + 3) & (~3);
  cySize      = abs(biHeight);


  PBITMAPINFO pbmi;
  BOOL fDelete;
  UINT uUsage;

  if (biCompression == BI_RGB && biBitCount >= 16)
  {
    /*
      - for now works only for 24 bit case
      - in 16 and 32 bit case 3 DWORD masks are needed!
      - no color table needed
    */
    pbmi = (PBITMAPINFO) this;
    fDelete = FALSE;
    uUsage = DIB_RGB_COLORS;
  }
  else
  {
    int nColors = 1 << biBitCount;
    uUsage = DIB_PAL_COLORS;
    // for DIB_PAL_COLORS only
    pbmi = (PBITMAPINFO) new char [GetBmiSize()];
    fDelete = TRUE;

    // copy out the Bmih
    CopyMemory(pbmi, this, biSize);

    // put palette indices
    // use DIB_PAL_COLORS, 1:1 for speed
    USHORT *pPal = (USHORT *) pbmi->bmiColors;
    for (int i = 0; i < nColors; )
      *pPal++ = (USHORT) i++;

    // with DIB_RGB_COLORS could use GetSystemPaletteEntries() or
    // GetDIBColorTable()
  }
  biSizeImage = cySize * cWidthBytes;
 
  // no file mapping given
  hBitmap = CreateDIBSection(hDC, pbmi, uUsage, (void **) &pBits, NULL, 0);

  if (fDelete)
    delete [] pbmi;

  return hBitmap;
}//DibSect::Create()------------------------------------------------------------

// or better just a number of colors?
UINT DibSect::GetBmiSize() const
{
  switch(biBitCount)
  {
    case 16:
    case 32:
      // include the definition of bit distribution
      return sizeof(BITMAPINFOHEADER) + 3 * sizeof(DWORD);

    case 24:
      return sizeof(BITMAPINFOHEADER);

    default:
      return sizeof(BITMAPINFOHEADER) + (1 << biBitCount) * sizeof(RGBQUAD);
  }
}//DibSect::GetBmiSize()--------------------------------------------------------

/*
  Copies a rect from window's client area into a current (not yet created) DIB
  - depends on visibility
*/
HBITMAP DibSect::CopyWndClient
(
  HWND hWnd,
  RECT * pr // = NULL
)
{
  RECT r;
  if (pr && !IsRectEmpty(pr))
    r = *pr;
  else
    GetClientRect(hWnd, &r);

  HDC hdcWnd = GetDC(hWnd);
  HDC hdcMem = CreateCompatibleDC(hdcWnd);

  HBITMAP hbmOld;

  biWidth = r.right - r.left;
  biHeight = r.bottom - r.top;
  // always true color - could use palette for gray
  biBitCount = 24;
  HBITMAP hbm;
  unless (hbm = Create(hdcWnd))
    return hbm;

  hbmOld = SelectBitmap(hdcMem, hbm);
  BitBlt(hdcMem, 0, 0, biWidth, biHeight, hdcWnd, r.left, r.top, SRCCOPY);

  SelectObject(hdcMem, hbmOld);
  DeleteObject(hdcMem);
  ReleaseDC(hWnd, hdcWnd);

  return hbm;
}//DibSect::CopyWndClient()----------------------------------------------------


bool DibSect::Invert()
{
  // would work also for 32 bit
  unless (pBits && biBitCount == 24 || biBitCount == 32)
    return false;

  for (int y = 0; y < biHeight; y++)
  {
    PBYTE prgb = pBits + y * cWidthBytes;
    for (int x = 0; x < biWidth; x++)
    {
      prgb[0] = 255 - prgb[0];
      prgb[1] = 255 - prgb[1];
      prgb[2] = 255 - prgb[2];
      prgb += biBitCount/8;
    }
  }
  return true;
}//DibSect::Invert()-----------------------------------------------------------


bool DibSect::ToGrayScale()
{
  // would work also for 32 bit
  unless (pBits && biBitCount == 24 || biBitCount == 32)
    return false;

  for (int y = 0; y < biHeight; y++)
  {
    PBYTE prgb = pBits + y * cWidthBytes;
    for (int x = 0; x < biWidth; x++)
    {
      BYTE r = prgb[0];
      BYTE g = prgb[1];
      BYTE b = prgb[2];
      prgb[0] = prgb[1] = prgb[2] = (r + g + b) / 3;
      prgb += biBitCount/8;
    }
  }
  return true;
}//DibSect::ToGrayScale()------------------------------------------------------


const unsigned short DS_BITMAP_FILEMARKER = 0x4d42; // 'BM'


// not tested
bool DibSect::Load(const char *szFileName)
{
  hBitmap = (HBITMAP) LoadImage(NULL, szFileName, IMAGE_BITMAP, 0, 0, 
    LR_LOADFROMFILE | LR_CREATEDIBSECTION);
  return hBitmap != NULL;
}

// currently 256 colors not supported
bool DibSect::SaveAs(const char *szFileName)
{
  unless (pBits && biBitCount == 24)
    return false;

  BITMAPFILEHEADER   hdr;

  unless (szFileName)
    return false;

  // Perl redefines fopen etc.
  FILE * pf = fopen(szFileName, "w+b");
  
  unless (pf)
    return false;

  DWORD dwBitmapInfoSize = GetBmiSize();
  DWORD dwFileHeaderSize = dwBitmapInfoSize + sizeof(hdr);

  // Fill in the fields of the file header 
  hdr.bfType       = DS_BITMAP_FILEMARKER;
  hdr.bfSize       = dwFileHeaderSize + biSizeImage;
  hdr.bfReserved1  = 0;
  hdr.bfReserved2  = 0;
  hdr.bfOffBits    = dwFileHeaderSize;

  // Write the file header 
  bool fOk = (sizeof(hdr) == fwrite(&hdr, 1, sizeof(hdr), pf));

  // Write the DIB header
  unless (dwBitmapInfoSize == fwrite(this, 1, dwBitmapInfoSize, pf))
    fOk = false;
 
  // Write DIB bits
  unless (biSizeImage == fwrite(GetBits(), 1, biSizeImage, pf))
    fOk = false;

  unless(fclose(pf))
    fOk = false;

  return fOk;
}//DibSect::SaveAs()-----------------------------------------------------------


/*
 - see old MSDN note "DIBs and Their Use"
 - ENHMETAFILE some other day
*/
HMETAFILE DibSect::AsMetafile()
{
  // not a wmf file format...
  HDC hMetaDC = CreateMetaFile((LPSTR) NULL);
  // requires bitmapinfo (header + colors), but we cheat it to live with header only
  StretchDIBits(hMetaDC, 0, 0, biWidth, biHeight, 0, 0, biWidth, 
    biHeight, GetBits(), (BITMAPINFO * )this, DIB_RGB_COLORS, SRCCOPY);
  HMETAFILE hMetafile = CloseMetaFile(hMetaDC);
  return hMetafile;
}

bool DibSect::ToClipboard()
{
  unless (hBitmap)
    return FALSE;

  BOOL fAsDIB = FALSE;
  

  // CF_DIB needs a packed DIB with header, color table and bits in one memory block
  // CF_BITMAP somehow doesn't work with DIB handles
  // -> use metafile

  HANDLE h = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(METAFILEPICT));
  METAFILEPICT * pmfp = (METAFILEPICT *) GlobalLock(h);

  pmfp->mm = MM_TEXT;
  pmfp->xExt = biWidth;
  pmfp->yExt = cySize;
  pmfp->hMF = AsMetafile();

  GlobalUnlock(h);

  bool fSuccess = 
       OpenClipboard(NULL)
    && EmptyClipboard()
    && SetClipboardData(CF_METAFILEPICT, h);

  unless (fSuccess)
    GlobalFree(h);

  // we try to close it even if opening failed
  CloseClipboard();

  return fSuccess;
}//DibSect::ToClipboard()-------------------------------------------------------


