/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Mike Pinkerton <pinkerton@netscape.com>
 *   Gus Verdun <gustavoverdun@aol.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
 
 
#include "nsImageClipboard.h"
#include "nsGfxCIID.h"


/* Things To Do 11/8/00

Check image metrics, can we support them? Do we need to?
Any other render format? HTML?

*/


//
// nsImageToClipboard ctor
//
// Given an nsIImage, convert it to a DIB that is ready to go on the win32 clipboard
//
nsImageToClipboard :: nsImageToClipboard ( nsIImage* inImage )
  : mImage(inImage)
{
  // nothing to do here
}


//
// nsImageToClipboard dtor
//
// Clean up after ourselves. We know that we have created the bitmap
// successfully if we still have a pointer to the header.
//
nsImageToClipboard::~nsImageToClipboard()
{
}


//
// GetPicture
//
// Call to get the actual bits that go on the clipboard. If an error 
// ocurred during conversion, |outBits| will be null.
//
// NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
//
nsresult
nsImageToClipboard :: GetPicture ( HANDLE* outBits )
{
  NS_ASSERTION ( outBits, "Bad parameter" );

  return CreateFromImage ( mImage, outBits );

} // GetPicture


//
// CalcSize
//
// Computes # of bytes needed by a bitmap with the specified attributes.
//
PRInt32 
nsImageToClipboard :: CalcSize ( PRInt32 aHeight, PRInt32 aColors, WORD aBitsPerPixel, PRInt32 aSpanBytes )
{
  PRInt32 HeaderMem = sizeof(BITMAPINFOHEADER);

  // add size of pallette to header size
  if (aBitsPerPixel < 16)
    HeaderMem += aColors * sizeof(RGBQUAD);

  if (aHeight < 0)
    aHeight = -aHeight;

  return (HeaderMem + (aHeight * aSpanBytes));
}


//
// CalcSpanLength
//
// Computes the span bytes for determining the overall size of the image
//
PRInt32 
nsImageToClipboard::CalcSpanLength(PRUint32 aWidth, PRUint32 aBitCount)
{
  PRInt32 spanBytes = (aWidth * aBitCount) >> 5;
  
  if ((aWidth * aBitCount) & 0x1F)
    spanBytes++;
  spanBytes <<= 2;

  return spanBytes;
}


//
// CreateFromImage
//
// Do the work to setup the bitmap header and copy the bits out of the
// image. 
//
nsresult
nsImageToClipboard::CreateFromImage ( nsIImage* inImage, HANDLE* outBitmap )
{
  nsresult result = NS_OK;
  *outBitmap = nsnull;
  
/*
  //pHead = (BITMAPINFOHEADER*)pImage->GetBitInfo();
  //pImage->GetNativeData((void**)&hBitMap);
 if (hBitMap)
  {
         BITMAPINFO * pBMI;
         pBMI                    = (BITMAPINFO *)new BYTE[(sizeof(BITMAPINFOHEADER)+256*4)];
         BITMAPINFOHEADER * pBIH = (BITMAPINFOHEADER *)pBMI;
         pBIH->biSize            = sizeof(BITMAPINFOHEADER);
         pBIH->biBitCount        = 0;
         pBIH->biPlanes          = 1;
         pBIH->biSizeImage       = 0;
         pBIH->biXPelsPerMeter   = 0;
         pBIH->biYPelsPerMeter   = 0;
         pBIH->biClrUsed         = 0;                            // Always use the whole palette.

         pBIH->biClrImportant    = 0;

         // Get bitmap format.

         HDC hdc  =  ::GetDC(NULL);
         int rc   =  ::GetDIBits(hdc, hBitMap, 0, 0, NULL, pBMI, DIB_RGB_COLORS);
         NS_ASSERTION(rc, "rc returned false");

         nLength = CalcSize(pBIH->biHeight, pBIH->biClrUsed, pBIH->biBitCount, CalcSpanLength(pBIH->biWidth, pBIH->biBitCount));

         // alloc and lock

         mBitmap = (HANDLE)::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, nLength);
         if (mBitmap && (mHeader = (BITMAPINFOHEADER*)::GlobalLock((HGLOBAL) mBitmap)) )
         {
                 result = TRUE;

                 pBits = (PBYTE)&mHeader[1];
                 memcpy(mHeader, pBIH, sizeof (BITMAPINFOHEADER));
                 rc = ::GetDIBits(hdc, hBitMap, 0, mHeader->biHeight, pBits, (BITMAPINFO *)mHeader, DIB_RGB_COLORS);
                 NS_ASSERTION(rc, "rc returned false");
                 delete[] (BYTE*)pBMI;
                 ::GlobalUnlock((HGLOBAL)mBitmap); // we use mHeader to tell if we have to free it later.

         }
         ::ReleaseDC(NULL, hdc);
  }
  else
  {
*/

  inImage->LockImagePixels ( PR_FALSE );
  if ( inImage->GetBits() ) {
    BITMAPINFOHEADER* imageHeader = NS_REINTERPRET_CAST(BITMAPINFOHEADER*, inImage->GetBitInfo());
    NS_ASSERTION ( imageHeader, "Can't get header for image" );
    if ( !imageHeader )
      return NS_ERROR_FAILURE;
      
    PRInt32 imageSize = CalcSize(imageHeader->biHeight, imageHeader->biClrUsed, imageHeader->biBitCount, inImage->GetLineStride());

    // Create the buffer where we'll copy the image bits (and header) into and lock it
    BITMAPINFOHEADER*  header = nsnull;
    *outBitmap = (HANDLE)::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, imageSize);
    if (*outBitmap && (header = (BITMAPINFOHEADER*)::GlobalLock((HGLOBAL) *outBitmap)) )
    {
      RGBQUAD *pRGBQUAD = (RGBQUAD *)&header[1];
      PBYTE bits = (PBYTE)&pRGBQUAD[imageHeader->biClrUsed];

      // Fill in the header info.

      header->biSize          = sizeof(BITMAPINFOHEADER);
      header->biWidth         = imageHeader->biWidth;
      header->biHeight        = imageHeader->biHeight;

      header->biPlanes        = 1;
      header->biBitCount      = imageHeader->biBitCount;
      header->biCompression   = BI_RGB;               // No compression

      header->biSizeImage     = 0;
      header->biXPelsPerMeter = 0;
      header->biYPelsPerMeter = 0;
      header->biClrUsed       = imageHeader->biClrUsed;
      header->biClrImportant  = 0;

      // Set up the color map.
      
      nsColorMap *colorMap = inImage->GetColorMap();
      if ( colorMap ) {
        PBYTE pClr = colorMap->Index;

        NS_ASSERTION(( ((DWORD)colorMap->NumColors) == header->biClrUsed), "Color counts must match");
        for ( UINT i=0; i < header->biClrUsed; ++i ) {
          NS_WARNING ( "Cool! You found a test case for this! Let Gus or Pink know" );
          
          // now verify that the order is correct
          pRGBQUAD[i].rgbBlue  = *pClr++;
          pRGBQUAD[i].rgbGreen = *pClr++;
          pRGBQUAD[i].rgbRed   = *pClr++;
        }
      }
      else
        NS_ASSERTION(header->biClrUsed == 0, "Ok, now why are there colors and no table?");

      // Copy!!
      ::CopyMemory(bits, inImage->GetBits(), inImage->GetLineStride() * header->biHeight);
      
      ::GlobalUnlock((HGLOBAL)outBitmap);
    }
    else
      result = NS_ERROR_FAILURE;
  } // if we can get the bits from the image
  
  inImage->UnlockImagePixels ( PR_FALSE );
  return result;
}


#if XP_MAC
#pragma mark -
#endif


nsImageFromClipboard :: nsImageFromClipboard ( BITMAPV4HEADER* inHeader )
  : mHeader(inHeader)
{
  // nothing to do here
}


nsImageFromClipboard :: ~nsImageFromClipboard ( )
{
  // since the output is a COM object, it is refcounted and such we've got
  // nothing to do.
}


//
// GetImage
//
// Take the image data in the given buffer and create an nsIImage based
// off it
//
nsresult 
nsImageFromClipboard :: GetImage ( nsIImage** outImage )
{
  NS_ASSERTION ( outImage, "Bad parameter" );
  *outImage = nsnull;
  
  static NS_DEFINE_IID(kCImageCID, NS_IMAGE_CID);
  nsresult rv = nsComponentManager::CreateInstance(kCImageCID, nsnull, 
                                                    NS_GET_IID(nsIImage), 
                                                    (void**) outImage);
  if ( NS_SUCCEEDED(rv) ) {
    // pull the size informat out of the BITMAPINFO header and
    // initialize the image
    PRInt32 width  = mHeader->bV4Width;
    PRInt32 height = mHeader->bV4Height;
    PRInt32 depth  = mHeader->bV4BitCount;
    PRUint8 * bits = GetDIBBits();
    if ( !bits )
      return NS_ERROR_FAILURE;
      
    // BUG 44369 notes problems with the GFX image code handling
    // depths other than 8 or 24 bits. Ensure that's what we have
    // before we try to work with the image. (pinkerton)
    if ( depth == 24 || depth == 8 ) {
      (*outImage)->Init(width, height, depth, nsMaskRequirements_kNoMask);

      // Now, copy the image bits from the Dib into the nsIImage's buffer
      PRUint8* imageBits = (*outImage)->GetBits();
      depth = (depth >> 3);
      PRUint32 size = width * height * depth;
      ::CopyMemory(imageBits, bits, size);
    }
  }

  return rv;

} // GetImage


//
// GetDIBBits
//
// Compute the sizes of the various headers then return a ptr into the buffer
// that is at the start of the bits.
//
PRUint8*
nsImageFromClipboard :: GetDIBBits ( )
{
  BITMAPINFO * aBitmapInfo = (BITMAPINFO*) mHeader;
  DWORD   maskSize;
  DWORD   colorSize;  

  // Get the size of the information header
  DWORD headerSize = aBitmapInfo->bmiHeader.biSize ;
  if (headerSize != sizeof (BITMAPCOREHEADER) &&
      headerSize != sizeof (BITMAPINFOHEADER) &&
      //headerSize != sizeof (BITMAPV5HEADER) &&
      headerSize != sizeof (BITMAPV4HEADER))
    return NULL ;

  // Get the size of the color masks
  if (headerSize == sizeof (BITMAPINFOHEADER) &&
      aBitmapInfo->bmiHeader.biCompression == BI_BITFIELDS)
    maskSize = 3 * sizeof (DWORD);
  else
    maskSize = 0;

  // Get the size of the color table
  if (headerSize == sizeof (BITMAPCOREHEADER)) {
    int iBitCount = ((BITMAPCOREHEADER *) aBitmapInfo)->bcBitCount;
    if (iBitCount <= 8)
      colorSize = (1 << iBitCount) * sizeof (RGBTRIPLE);
    else
      colorSize = 0 ;
  } 
  else {         // All non-OS/2 compatible DIBs
    if (aBitmapInfo->bmiHeader.biClrUsed > 0)
      colorSize = aBitmapInfo->bmiHeader.biClrUsed * sizeof (RGBQUAD);
    else if (aBitmapInfo->bmiHeader.biBitCount <= 8)
      colorSize = (1 << aBitmapInfo->bmiHeader.biBitCount) * sizeof (RGBQUAD);
    else
      colorSize = 0;
  }

  // the bits are at an offset into the buffer that is the header size plus the
  // mask size plus the color table size. 
  return (BYTE *) aBitmapInfo + (headerSize + maskSize + colorSize);
  
} // GetDIBBits



