/*****************************************************************************
* Copyright (c) 1990-99 Digi International Inc., All Rights Reserved
* 
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
* 
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/

// NOTE: The original source code for the CDib class was horked from
//       MSJ June, 1997.
//
//       The transparent background code was horked from an MSDN article
//       entitled "MFC Tips for Copying, Pasting, Blting, and Creating
//       Owner-Drawn List Boxes" by Nigel Thompson.

////////////////////////////////////////////////////////////////
// Copyright 1996 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// CDib - Device Independent Bitmap.
// This implementation draws bitmaps using normal Win32 API functions.
// CDib is derived from CBitmap, so you can use it with
// any other MFC functions that use bitmaps.
//

#include "StdAfx.h"
#include "Dib.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define MAXPALCOLORS 256

IMPLEMENT_DYNAMIC(CDib, CObject)

CDib::CDib()
{
   memset(&m_bm, 0, sizeof(m_bm));
   m_fTransBackground = FALSE;
}

CDib::~CDib()
{
   DeleteObject();
}

//////////////////
// Delete Object. Delete DIB and palette.
//
BOOL CDib::DeleteObject()
{
   m_pal.DeleteObject();

   memset(&m_bm, 0, sizeof(m_bm));

   return CBitmap::DeleteObject();
}

//////////////////
// Read DIB from file.
//
BOOL CDib::Load(LPCTSTR lpszPathName, BOOL fTransBackground /* = FALSE */)
{
   m_fTransBackground = fTransBackground;
   return Attach(::LoadImage(NULL, lpszPathName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION | LR_DEFAULTSIZE));
}

//////////////////
// Load bitmap resource. Never tested.
//
BOOL CDib::Load(HINSTANCE hInst, LPCTSTR lpResourceName, BOOL fTransBackground /* = FALSE */)
{
   m_fTransBackground = fTransBackground;
   return Attach(::LoadImage(hInst, lpResourceName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_DEFAULTSIZE));
}

//////////////////
// Attach is just like the CGdiObject version,
// except it also creates the palette
//
BOOL CDib::Attach(HGDIOBJ hbm)
{
   BOOL fSuccess = FALSE;

   try
   {
      AffirmThrow(CBitmap::Attach(hbm));
      AffirmThrow(GetBitmap(&m_bm));         // load BITMAP for speed

      m_pal.DeleteObject();                  // in case one is already there
      AffirmThrow(CreatePalette(m_pal));     // create palette

      fSuccess = TRUE;
   }
   catch (CAffirmException ae)
   {
   }

   return fSuccess;   
}

//////////////////
// Get size (width, height) of bitmap.
// extern fn works for ordinary CBitmap objects.
//
CSize FW_EXPORTED_API GetBitmapSize(CBitmap * pBitmap)
{
   BITMAP bm;
   return pBitmap->GetBitmap(&bm) ?
      CSize(bm.bmWidth, bm.bmHeight) : CSize(0,0);
}

//////////////////
// You can use this static function to draw ordinary
// CBitmaps as well as CDibs
//

BOOL  FW_EXPORTED_API DrawBitmap(CDC& dc, CBitmap* pBitmap,
                                 const CRect* rcDst /* = NULL */, const CRect* rcSrc /* = NULL */,
                                 CPalette * pPal /* = NULL */, BOOL fForeground /* = FALSE */)
{
   ASSERT(pBitmap != NULL);

   // Compute rectangles where NULL specified
   CRect rc;
   if (!rcSrc)
   {
      // if no source rect, use whole bitmap
      rc = CRect(CPoint(0,0), GetBitmapSize(pBitmap));
      rcSrc = &rc;
   }

   if (!rcDst)
   {
      // if no destination rect, use source
      rcDst=rcSrc;
   }

   // Create memory DC
   CDC memdc;
   memdc.CreateCompatibleDC(&dc);

   // Take care of palette
   CPalette* pOldPal = NULL;
   if (pPal != NULL)
   {
      pOldPal = dc.SelectPalette(pPal, !fForeground);
      dc.RealizePalette();
   }

   CBitmap* pOldMemBmp = memdc.SelectObject(pBitmap);

   // Blast bits from memory DC to target DC.
   // Use StretchBlt if size is different.
   //
   BOOL fSuccess = FALSE;

   if (rcDst->Size()==rcSrc->Size())
   {
      fSuccess = dc.BitBlt(rcDst->left, rcDst->top, 
         rcDst->Width(), rcDst->Height(),
         &memdc, rcSrc->left, rcSrc->top, SRCCOPY);
   }
   else
   {
      dc.SetStretchBltMode(COLORONCOLOR);
      fSuccess = dc.StretchBlt(rcDst->left, rcDst->top, rcDst->Width(),
         rcDst->Height(), &memdc, rcSrc->left, rcSrc->top, rcSrc->Width(),
         rcSrc->Height(), SRCCOPY);
   }

   if (pOldPal != NULL)
      dc.SelectPalette(pOldPal, TRUE);

   memdc.SelectObject(pOldMemBmp);

   return fSuccess;
}

// A memory DC is created, and the bitmap selected into it. Note that m_hObject
// is a member of the CBitmap class and holds the handle to the GDI bitmap object.
// Once the bitmap is selected into the memory DC, we can copy the bitmap image by
// calling BitBlt. The memory DC is then tidied up and deleted.

// OK so far. Now for the interesting bit drawing the bitmap with transparent areas.
// We do this by creating a mask that determines which bits to copy and which bits
// to leave out. The mask will be made from a monochrome bitmap, using another helper
// function.

static BOOL CreateMask(CDC & dc, CBitmap * pImage, CBitmap * pMask)
{
   BOOL fSuccess = FALSE;
    
   ASSERT(pImage != NULL);
   ASSERT(pMask != NULL);

   // Nuke any existing mask.
   pMask->DeleteObject();

   // Create memory DCs to work with.
   CDC dcMask;
   dcMask.CreateCompatibleDC(&dc);

   CDC dcImage;
   dcImage.CreateCompatibleDC(&dc);
   
   // Create a monochrome bitmap for the mask.
   CSize sz = GetBitmapSize(pImage);
   pMask->CreateBitmap(sz.cx, sz.cy, 1, 1, NULL);
   
   // Select the mono bitmap into its DC.
   CBitmap * pOldMaskBmp = dcMask.SelectObject(pMask);
   
   // Select the image bitmap into its DC.
   CBitmap * pOldImageBmp = dcImage.SelectObject(pImage);
   
   // Set the transparency color to be the top-left pixel.
   dcImage.SetBkColor(dcImage.GetPixel(0, 0));
   
   // Make the mask.
   dcMask.BitBlt(0, 0, sz.cx, sz.cy, &dcImage, 0, 0, SRCCOPY);
   
   // Tidy up.
   dcMask.SelectObject(pOldMaskBmp);
   dcImage.SelectObject(pOldImageBmp);

   fSuccess = TRUE;

   return fSuccess;
}

// Memory DCs are created for both the image and the mask. A monochrome bitmap for
// the mask is created the same size as the image bitmap. The image and mask bitmaps
// are selected into their respective DCs. Now comes the clever bit. The background
// color of the image DC is set to be the pixel color of the top-left pixel of the
// image, and BitBlt is called. The current background color controls what happens
// when a color DC is bltted to a monochrome DC. The bits from the color DC that
// match the background color are copied to the monochrome DC as white pixels (1).
// The color DC pixels that don't match the background color are copied to the
// monochrome DC as black pixels (0). So now we have a monochrome mask that defines
// the transparent areas of the image.

// Let's see now how the image is drawn transparently using the mask:

BOOL FW_EXPORTED_API DrawTransBitmap(CDC& dc, CBitmap* pBitmap,
                                             const CRect* rcDst /* = NULL */, const CRect* rcSrc /* = NULL */,
                                             CPalette * pPal /* = NULL */, BOOL fForeground /* = FALSE */)
{
   BOOL fSuccess = FALSE;

   ASSERT(pBitmap != NULL);

   // Compute rectangles where NULL specified
   CRect rc;
   if (!rcSrc)
   {
      // if no source rect, use whole bitmap
      rc = CRect(CPoint(0,0), GetBitmapSize(pBitmap));
      rcSrc = &rc;
   }

   if (!rcDst)
   {
      // if no destination rect, use source
      rcDst=rcSrc;
   }

   // StretchBlt() currently not supported for DrawTransBitmap() yet
   ASSERT(rcDst->Size() == rcSrc->Size());

   CBitmap bmMask;
   CreateMask(dc, pBitmap, &bmMask);

   // Create a memory DC to which to draw.
   CDC dcOffScr;
   dcOffScr.CreateCompatibleDC(&dc);

   // Take care of the palette
   CPalette * pOldPal = NULL;
   CPalette * pOldOffScrPal = NULL;
   if (pPal != NULL)
   {
      pOldPal = dc.SelectPalette(pPal, !fForeground);
      dc.RealizePalette();

      pOldOffScrPal = dcOffScr.SelectPalette(pPal, TRUE);
   }

   // Create a bitmap for the off-screen DC that is really
   // color-compatible with the destination DC.
   CBitmap bmOffScr;
   bmOffScr.CreateBitmap(rcDst->Width(), rcDst->Height(), (BYTE)dc.GetDeviceCaps(PLANES), (BYTE)dc.GetDeviceCaps(BITSPIXEL), NULL);
   
   // Select the buffer bitmap into the off-screen DC.
   CBitmap * pOldOffScrBmp = dcOffScr.SelectObject(&bmOffScr);

   // Copy the image of the destination rectangle to the
   // off-screen buffer DC, so we can play with it.
   dcOffScr.BitBlt(0, 0, rcDst->Width(), rcDst->Height(), &dc, rcDst->left, rcDst->top, SRCCOPY);

   // Create a memory DC for the source image.
   CDC dcImage;
   dcImage.CreateCompatibleDC(&dc);

   CBitmap * pOldImageBmp = dcImage.SelectObject(pBitmap);

   // Create a memory DC for the mask.
   CDC dcMask;
   dcMask.CreateCompatibleDC(&dc);

   CBitmap * pOldMaskBmp = dcMask.SelectObject(&bmMask);

   dcOffScr.SetBkColor(RGB(255, 255, 255));

   // XOR the image with the destination.
   dcOffScr.BitBlt(0, 0, rcDst->Width(), rcDst->Height(), &dcImage, rcSrc->left, rcSrc->top, SRCINVERT);

   // AND the destination with the mask.
   dcOffScr.BitBlt(0, 0, rcDst->Width(), rcDst->Height(), &dcMask, rcSrc->left, rcSrc->top, SRCAND);

   // XOR the destination with the image again.
   dcOffScr.BitBlt(0, 0, rcDst->Width(), rcDst->Height(), &dcImage, rcSrc->left, rcSrc->top, SRCINVERT);

   // Copy the resultant image back to the screen DC.
   dc.BitBlt(rcDst->left, rcDst->top, rcDst->Width(), rcDst->Height(), &dcOffScr, 0, 0, SRCCOPY);

   // Tidy up.
   if (pOldPal != NULL)
      dc.SelectPalette(pOldPal, TRUE);

   if (pOldOffScrPal != NULL)
      dcOffScr.SelectPalette(pOldOffScrPal, TRUE);

   dcOffScr.SelectObject(pOldOffScrBmp);
   dcImage.SelectObject(pOldImageBmp);
   dcMask.SelectObject(pOldMaskBmp);

   fSuccess = TRUE;

   return fSuccess;
}

////////////////////////////////////////////////////////////////
// Draw DIB on caller's DC. Does stretching from source to destination
// rectangles. Generally, you can let the following default to zero/NULL:
//
//      pPal        = palette, default=NULL, (use DIB's palette)
//      fForeground = realize in foreground default=FALSE
//
// If you are handling palette messages, you should use fForeground=FALSE,
// since you will realize the foreground palette in WM_QUERYNEWPALETTE.
//
BOOL CDib::Draw(CDC& dc, const CRect* rcDst /* = NULL */, const CRect* rcSrc /* = NULL */,
                CPalette* pPal /* = NULL */, BOOL fForeground /* = FALSE */)
{
   if (!m_hObject)
      return FALSE;

   // Select, realize palette
   if (pPal==NULL)            // no palette specified:
      pPal = GetPalette();      // use default

   BOOL fSuccess = FALSE;

   if (m_fTransBackground)
   {
      fSuccess = DrawTransBitmap(dc, this, rcDst, rcSrc, pPal, fForeground);
   }
   else
   {
      // use normal draw function
      fSuccess = DrawBitmap(dc, this, rcDst, rcSrc, pPal, fForeground);
   }

   return fSuccess;
}

#define PALVERSION 0x300   // magic number for LOGPALETTE

//////////////////
// Create the palette. Use halftone palette for hi-color bitmaps.
//
BOOL CDib::CreatePalette(CPalette& pal)
{ 
   // should not already have palette
   ASSERT(pal.m_hObject==NULL);

   BOOL fSuccess = FALSE;
   RGBQUAD* colors = new RGBQUAD[MAXPALCOLORS];
   UINT nColors = GetColorTable(colors, MAXPALCOLORS);
   if (nColors > 0)
   {
      // Allocate memory for logical palette 
      int len = sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * nColors;
      LOGPALETTE* pLogPal = (LOGPALETTE*)new char[len];
      if (!pLogPal)
         return NULL;

      // set version and number of palette entries
      pLogPal->palVersion = PALVERSION;
      pLogPal->palNumEntries = nColors;

      // copy color entries 
      for (UINT i = 0; i < nColors; i++)
      {
         pLogPal->palPalEntry[i].peRed   = colors[i].rgbRed;
         pLogPal->palPalEntry[i].peGreen = colors[i].rgbGreen;
         pLogPal->palPalEntry[i].peBlue  = colors[i].rgbBlue;
         pLogPal->palPalEntry[i].peFlags = 0;
      }

      // create the palette and destroy LOGPAL
      fSuccess = pal.CreatePalette(pLogPal);
      delete [] (char*)pLogPal;
   }
   else
   {
      CWindowDC dcScreen(NULL);
      fSuccess = pal.CreateHalftonePalette(&dcScreen);
   }

   delete colors;

   return fSuccess;
}

//////////////////
// Helper to get color table. Does all the mem DC voodoo.
//
UINT CDib::GetColorTable(RGBQUAD* colorTab, UINT nColors)
{
   CWindowDC dcScreen(NULL);

   CDC memdc;
   memdc.CreateCompatibleDC(&dcScreen);

   CBitmap* pOldMemBmp = memdc.SelectObject(this);
   nColors = GetDIBColorTable(memdc, 0, nColors, colorTab);

   memdc.SelectObject(pOldMemBmp);
   return nColors;
}
