/**************************************************************************\
* 
* Copyright (c) 2000  Microsoft Corporation
*
* Module Name:
*
*    Compatible DIBSections
*
* Abstract:
*
*    Create a DIB section with an optimal format w.r.t. the specified hdc.
*    If the hdc format is <8bpp, returns an 8bpp DIBSection.
*    If the hdc format is not recognized, returns a 32bpp DIBSection.
*
* Notes:
*
* History:
*
*  01/23/1996 gilmanw
*     Created it.
*  01/21/2000 agodfrey
*     Added it to GDI+ (from Gilman's 'fastdib.c'), and morphed it into 
*     'CreateSemiCompatibleDIB'.
*  08/10/2000 agodfrey
*     Hacked it further so that if we don't understand the format, we make
*     a 32bpp section. Bug #96879. 
*
\**************************************************************************/

#include "precomp.hpp"
#include "compatibleDIB.hpp"


const DWORD AlphaMaskFromPixelFormatIndex[PIXFMT_MAX] = {
    0x00000000, // PixelFormatUndefined
    0x00000000, // PixelFormat1bppIndexed
    0x00000000, // PixelFormat4bppIndexed
    0x00000000, // PixelFormat8bppIndexed
    0x00000000, // PixelFormat16bppGrayScale
    0x00000000, // PixelFormat16bppRGB555
    0x00000000, // PixelFormat16bppRGB565
    0x00008000, // PixelFormat16bppARGB1555
    0x00000000, // PixelFormat24bppRGB
    0x00000000, // PixelFormat32bppRGB
    0xff000000, // PixelFormat32bppARGB
    0xff000000, // PixelFormat32bppPARGB
    0x00000000, // PixelFormat48bppRGB
    0x00000000, // PixelFormat64bppARGB
    0x00000000, // PixelFormat64bppPARGB
    0x00000000  // PixelFormat24bppBGR
};

const DWORD RedMaskFromPixelFormatIndex[PIXFMT_MAX] = {
    0x00000000, // PixelFormatUndefined
    0x00000000, // PixelFormat1bppIndexed
    0x00000000, // PixelFormat4bppIndexed
    0x00000000, // PixelFormat8bppIndexed
    0x00000000, // PixelFormat16bppGrayScale
    0x00007c00, // PixelFormat16bppRGB555
    0x0000f800, // PixelFormat16bppRGB565
    0x00007c00, // PixelFormat16bppARGB1555
    0x00ff0000, // PixelFormat24bppRGB
    0x00ff0000, // PixelFormat32bppRGB
    0x00ff0000, // PixelFormat32bppARGB
    0x00ff0000, // PixelFormat32bppPARGB
    0x00000000, // PixelFormat48bppRGB
    0x00000000, // PixelFormat64bppARGB
    0x00000000, // PixelFormat64bppPARGB
    0x000000ff  // PixelFormat24bppBGR
};

const DWORD GreenMaskFromPixelFormatIndex[PIXFMT_MAX] = {
    0x00000000, // PixelFormatUndefined
    0x00000000, // PixelFormat1bppIndexed
    0x00000000, // PixelFormat4bppIndexed
    0x00000000, // PixelFormat8bppIndexed
    0x00000000, // PixelFormat16bppGrayScale
    0x000003e0, // PixelFormat16bppRGB555
    0x000007e0, // PixelFormat16bppRGB565
    0x000003e0, // PixelFormat16bppARGB1555
    0x0000ff00, // PixelFormat24bppRGB
    0x0000ff00, // PixelFormat32bppRGB
    0x0000ff00, // PixelFormat32bppARGB
    0x0000ff00, // PixelFormat32bppPARGB
    0x00000000, // PixelFormat48bppRGB
    0x00000000, // PixelFormat64bppARGB
    0x00000000, // PixelFormat64bppPARGB
    0x0000ff00  // PixelFormat24bppBGR
};

const DWORD BlueMaskFromPixelFormatIndex[PIXFMT_MAX] = {
    0x00000000, // PixelFormatUndefined
    0x00000000, // PixelFormat1bppIndexed
    0x00000000, // PixelFormat4bppIndexed
    0x00000000, // PixelFormat8bppIndexed
    0x00000000, // PixelFormat16bppGrayScale
    0x0000001f, // PixelFormat16bppRGB555
    0x0000001f, // PixelFormat16bppRGB565
    0x0000001f, // PixelFormat16bppARGB1555
    0x000000ff, // PixelFormat24bppRGB
    0x000000ff, // PixelFormat32bppRGB
    0x000000ff, // PixelFormat32bppARGB
    0x000000ff, // PixelFormat32bppPARGB
    0x00000000, // PixelFormat48bppRGB
    0x00000000, // PixelFormat64bppARGB
    0x00000000, // PixelFormat64bppPARGB
    0x00ff0000  // PixelFormat24bppBGR
};


/**************************************************************************\
* CreatePBMIFromPixelFormat
*
* Fills in the fields of a BITMAPINFO so that we can create a bitmap
* that matches the format of the display.
*
* This is done by analyzing the pixelFormat
*
* Arguments:
*    OUT pbmi : this must point to a valid BITMAPINFO structure
*               which has enough space for the palette (RGBQUAD array)
*               and MUST be zero initialized.
*    palette  : Input palette that will be copied into the BITMAPINFO
*               if it's a palettized mode.
*    pixelFormat : Input pixel format.
*  
*
* History:
*  06/07/1995 gilmanw
*     Created it.
*  01/21/2000 agodfrey
*     Munged it for GDI+'s needs.
*  08/11/2000 asecchia
*     Extracted the pixel format detection into a separate routine.
*     It now analyzes the pixelformat for all its data.
*
\**************************************************************************/

static VOID
CreatePBMIFromPixelFormat(
    OUT BITMAPINFO *pbmi,
    IN ColorPalette *palette, 
    IN PixelFormatID pixelFormat
    )
{
    // NOTE: Contents of pbmi should be zero initialized by the caller.
    
    ASSERT(pbmi != NULL);    
    if(pixelFormat == PixelFormatUndefined) { return; }
    
    // GDI can't handle the following formats: 
    
    ASSERT(
        pixelFormat != PixelFormatUndefined &&
        pixelFormat != PixelFormat16bppGrayScale &&
        pixelFormat != PixelFormat16bppARGB1555 &&
        pixelFormat != PixelFormat48bppRGB &&
        pixelFormat != PixelFormat64bppARGB &&
        pixelFormat != PixelFormat64bppPARGB
    );
 
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = 0;
    pbmi->bmiHeader.biHeight = 0;
    pbmi->bmiHeader.biPlanes = 1;    
    pbmi->bmiHeader.biBitCount = (WORD)GetPixelFormatSize(pixelFormat);
        
    pbmi->bmiHeader.biCompression = BI_RGB;
    
    if (IsIndexedPixelFormat(pixelFormat))
    {        
        // Fill the color table

        // If there's no palette, assume the caller is going to 
        // set it up.
                
        if(palette)
        {
            RGBQUAD *rgb = pbmi->bmiColors;
            UINT         i;
            
            for (i=0; i<palette->Count; i++, rgb++)
            {
               GpColor color(palette->Entries[i]);
            
               rgb->rgbRed    = color.GetRed();
               rgb->rgbGreen  = color.GetGreen();
               rgb->rgbBlue   = color.GetBlue();
            }
        }
    }
    else
    {
        INT pfSize = GetPixelFormatSize(pixelFormat);
        
        if( (pfSize==16) || (pfSize==32) )
        {
            // BI_BITFIELDS is only valid on 16- and 32-bpp formats.
            
            pbmi->bmiHeader.biCompression = BI_BITFIELDS;
        }
        
        // Get the masks from the 16bpp, 24bpp and 32bpp formats.
        DWORD* masks = reinterpret_cast<DWORD*>(&pbmi->bmiColors[0]);
        INT formatIndex = GetPixelFormatIndex(pixelFormat);
        
        masks[0] = RedMaskFromPixelFormatIndex[formatIndex];
        masks[1] = GreenMaskFromPixelFormatIndex[formatIndex];
        masks[2] = BlueMaskFromPixelFormatIndex[formatIndex];
    }
}

/**************************************************************************\
* CreateSemiCompatibleDIB
*
* Create a DIB section with an optimal format w.r.t. the specified hdc.
*
* If DC format <= 8bpp, creates an 8bpp section using the specified palette.
* If the palette handle is NULL, then the system palette is used.
*
* Otherwise, if the DC format is not natively supported, creates a 32bpp
* section.
*
* Note: The hdc must be a direct DC (not an info or memory DC).
*
* Arguments:
*
*   hdc                 - The reference hdc
*   ulWidth             - The width of the desired DIBSection
*   ulHeight            - The height of the desired DIBSection
*   palette             - The palette for <=8bpp modes
*   [OUT] ppvBits       - A pointer to the DIBSection's bits
*   [OUT] pixelFormat   - The pixel format of the returned DIBSection
*
* Returns:
*   Valid bitmap handle if successful, NULL if error.
*
* History:
*  01/23/1996 gilmanw
*     Created it.
*  01/21/2000 agodfrey
*     Munged it for GDI+'s needs.
*  08/11/2000 asecchia
*     Call more general pixel format determination code.
\**************************************************************************/

HBITMAP 
CreateSemiCompatibleDIB(
    HDC hdc,
    ULONG ulWidth, 
    ULONG ulHeight,
    ColorPalette *palette,
    PVOID *ppvBits,
    PixelFormatID *pixelFormat
    )
{
    HBITMAP hbmRet = (HBITMAP) NULL;
    BYTE aj[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) * 255)];
    BITMAPINFO *pbmi = (BITMAPINFO *) aj;

    ASSERT(GetDCType(hdc) == OBJ_DC);
    ASSERT(pixelFormat && ppvBits);

    // Zero initialize the pbmi. This is a requirement for 
    // CreatePBMIFromPixelFormat()
    
    GpMemset(aj, 0, sizeof(aj));

    *pixelFormat = ExtractPixelFormatFromHDC(hdc);
    
    if(IsIndexedPixelFormat(*pixelFormat))
    {
        // For indexed modes, we only support 8bpp. Lower bit-depths
        // are supported via 8bpp mode, if at all.
        
        *pixelFormat = PixelFormat8bppIndexed;
    }

    // Not all printer HDC's have queriable palettes, the GpDevice()
    // constructor doesn't support it.  Fake 32bpp in this case.
    
    // Also, if the format is undefined, use 32bpp, and the caller will use
    // GDI to do the conversion.
    
    if (   (   (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASPRINTER)
            && (IsIndexedPixelFormat(*pixelFormat))
           )
        || (*pixelFormat == PixelFormatUndefined)
       )
    {
        *pixelFormat = PixelFormat32bppRGB;
    }
    
    CreatePBMIFromPixelFormat(pbmi, palette, *pixelFormat);
    
    // Change bitmap size to match specified dimensions.

    pbmi->bmiHeader.biWidth = ulWidth;
    pbmi->bmiHeader.biHeight = ulHeight;
    if (pbmi->bmiHeader.biCompression == BI_RGB)
    {
        pbmi->bmiHeader.biSizeImage = 0;
    }
    else
    {
        if ( pbmi->bmiHeader.biBitCount == 16 )
            pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 2;
        else if ( pbmi->bmiHeader.biBitCount == 32 )
            pbmi->bmiHeader.biSizeImage = ulWidth * ulHeight * 4;
        else
            pbmi->bmiHeader.biSizeImage = 0;
    }
    pbmi->bmiHeader.biClrUsed = 0;
    pbmi->bmiHeader.biClrImportant = 0;

    // Create the DIB section.  Let Win32 allocate the memory and return
    // a pointer to the bitmap surface.

    hbmRet = CreateDIBSection(hdc, pbmi, DIB_RGB_COLORS, ppvBits, NULL, 0);

    if ( !hbmRet )
    {
        ONCE(WARNING(("CreateSemiCompatibleDIB: CreateDIBSection failed")));
    }

    return hbmRet;
}

/**************************************************************************\
* ExtractPixelFormatFromHDC
* 
* Returns:
*   PixelFormatID if successful, PixelFormatUndefined if not.
*
* History:
*  08/11/2000 asecchia
*     Created it.
\**************************************************************************/

PixelFormatID
ExtractPixelFormatFromHDC(
    HDC hdc
    )
{
    HBITMAP hbm;
    BOOL    bRet = FALSE;
    PixelFormatID pixelFormat = PixelFormatUndefined;
    
    BYTE bmi_buf[sizeof(BITMAPINFO) + (sizeof(RGBQUAD) * 255)];
    BITMAPINFO *pbmi = (BITMAPINFO *) bmi_buf;
    
    GpMemset(bmi_buf, 0, sizeof(bmi_buf));
    
    // Create a dummy bitmap from which we can query color format info
    // about the device surface.

    if ( (hbm = CreateCompatibleBitmap(hdc, 1, 1)) != NULL )
    {
        pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

        // Call first time to fill in BITMAPINFO header.

        GetDIBits(hdc, hbm, 0, 0, NULL, pbmi, DIB_RGB_COLORS);

        // First handle the 'simple' case of indexed formats.
        
        if ( pbmi->bmiHeader.biBitCount <= 8 )
        {
            switch(pbmi->bmiHeader.biBitCount)
            {
            case 1: pixelFormat = PixelFormat1bppIndexed; break;
            case 4: pixelFormat = PixelFormat4bppIndexed; break;
            case 8: pixelFormat = PixelFormat8bppIndexed; break;
            
            // Fallthrough on default - the pixelFormat is already
            // initialized to PixelFormatUndefined.
            default: 
                WARNING((
                    "BitDepth %d from GetDIBits is not supported.", 
                    pbmi->bmiHeader.biBitCount
                ));                
            }
        }
        else
        {
            DWORD redMask = 0;
            DWORD greenMask = 0;
            DWORD blueMask = 0;
            
            if ( pbmi->bmiHeader.biCompression == BI_BITFIELDS )
            {
                // Call a second time to get the color masks.
                // It's a GetDIBits Win32 "feature".

                GetDIBits(
                    hdc, 
                    hbm, 
                    0, 
                    pbmi->bmiHeader.biHeight, 
                    NULL, 
                    pbmi,
                    DIB_RGB_COLORS
                );
                          
                DWORD* masks = reinterpret_cast<DWORD*>(&pbmi->bmiColors[0]);

                redMask = masks[0];
                greenMask = masks[1];
                blueMask = masks[2];          
            }
            else if (pbmi->bmiHeader.biCompression == BI_RGB)
            {
               redMask   = 0x00ff0000;
               greenMask = 0x0000ff00;
               blueMask  = 0x000000ff;
            }
            
            if ((redMask   == 0x00ff0000) &&
                (greenMask == 0x0000ff00) &&
                (blueMask  == 0x000000ff))
            {
                if (pbmi->bmiHeader.biBitCount == 24)
                {
                    pixelFormat = PixelFormat24bppRGB;
                }
                else if (pbmi->bmiHeader.biBitCount == 32)
                {
                    pixelFormat = PixelFormat32bppRGB;
                }
            }
            else if ((redMask   == 0x000000ff) &&
                     (greenMask == 0x0000ff00) &&
                     (blueMask  == 0x00ff0000) &&
                     (pbmi->bmiHeader.biBitCount == 24))
            {
                pixelFormat = PIXFMT_24BPP_BGR;
            }            
            else if ((redMask   == 0x00007c00) &&
                     (greenMask == 0x000003e0) &&
                     (blueMask  == 0x0000001f) &&
                     (pbmi->bmiHeader.biBitCount == 16))
            {
                pixelFormat = PixelFormat16bppRGB555;
            }
            else if ((redMask   == 0x0000f800) &&
                     (greenMask == 0x000007e0) &&
                     (blueMask  == 0x0000001f) &&
                     (pbmi->bmiHeader.biBitCount == 16))
            {
                pixelFormat = PixelFormat16bppRGB565;
            }
        }

        if (pixelFormat == PixelFormatUndefined)
        {
            ONCE(WARNING(("(once) ExtractPixelFormatFromHDC: Unrecognized pixel format")));
        }

        DeleteObject(hbm);
    }
    else
    {
        WARNING(("ExtractPixelFormatFromHDC: CreateCompatibleBitmap failed"));
    }

    return pixelFormat;
}