//+----------------------------------------------------------------------------
//
// File:     image.cpp
//
// Module:   CMDIAL and CMAK
//
// Synopsis: CMDIAL/CMAK specific imaging support routines
//
// Copyright (c) 1998-1999 Microsoft Corporation
//
// Author:   nickball   Created Header          03/30/98
//           quintinb   moved to common\source  08/06/98
//
//+----------------------------------------------------------------------------


//+---------------------------------------------------------------------------
//
//  Function:   CmGetBitmapInfo
//
//  Synopsis:   Helper function to retrieve the contents of a bitmap from an HBITMAP 
//                          
//  Arguments:  hbm - Hanhdle of the target bitmap
//
//  Returns:    A pointer to a LPBITMAPINFO that contains the INFOHEADER, 
//              ColorTable and bits for the bitmap.
//
//  Note:       When accessing this value, or passing it on to other BITMAP APIs
//              it is recommended that the value be cast as an (LPBYTE). 
//
//  History:    a-nichb - Cleaned-up and commented  - 3/21/97
//
//----------------------------------------------------------------------------

LPBITMAPINFO CmGetBitmapInfo(HBITMAP hbm) 
{
    LPBITMAPINFO pbmi = NULL;
    HDC hDC = NULL;
    int nNumColors = 0;
    int iRes;
    LPBITMAPINFO lpbmih = NULL;
    DWORD dwInfoSize = 0;
    WORD wbiBits = 0;

    if (!hbm) 
    {
        return NULL;
    }
    
    // Get the basic bmp object info 
    
    BITMAP BitMap;
    
    if (!GetObjectA(hbm, sizeof(BITMAP), &BitMap))
    {
        goto Cleanup;
    }

    // Calc the color bits and num colors
    
    wbiBits = BitMap.bmPlanes * BitMap.bmBitsPixel;

    if (wbiBits <= 8) 
    {
        nNumColors = 1 << wbiBits;
    }
        
    // Allocate a BITMAPINFO structure large enough to hold header + color palette
        
    dwInfoSize = sizeof(BITMAPINFOHEADER) + (nNumColors * sizeof(RGBQUAD));
     
    lpbmih = (LPBITMAPINFO) CmMalloc(dwInfoSize); 

    if (!lpbmih)
    {
        goto Cleanup;
    }
    
    // Pre-fill the info that we have about the bmp

    lpbmih->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    lpbmih->bmiHeader.biWidth = BitMap.bmWidth;
    lpbmih->bmiHeader.biHeight = BitMap.bmHeight;
    lpbmih->bmiHeader.biPlanes = 1; 
    lpbmih->bmiHeader.biBitCount = wbiBits;
        
    // Call GetDiBits() w/ 5th Param to NULL, this is treated by the system as
    // a query in which case it validates the lpbmih contents and fills in the
    // biSizeImage member of the structure
    
    hDC = GetDC(NULL);
    if (!hDC)
    {
        goto Cleanup;
    }

    iRes = GetDIBits(hDC,hbm,0,BitMap.bmHeight,NULL,(LPBITMAPINFO) lpbmih,DIB_RGB_COLORS);

#ifdef DEBUG
    if (!iRes)
    {
        CMTRACE(TEXT("CmGetBitmapInfo() GetDIBits() failed."));
    }
#endif

    if (iRes)
    {
        DWORD dwFullSize = dwInfoSize;
        
        // Create a complete DIB structure with room for bits and fill it

        if (lpbmih->bmiHeader.biSizeImage) 
        {
            dwFullSize += lpbmih->bmiHeader.biSizeImage;
        } 
        else 
        {
            dwFullSize += (((WORD) (lpbmih->bmiHeader.biWidth * lpbmih->bmiHeader.biBitCount) / 8) * (WORD) BitMap.bmHeight); 
        }
    
        pbmi = (LPBITMAPINFO) CmMalloc(dwFullSize + sizeof(DWORD));

#ifdef DEBUG
        *((DWORD *) (((PBYTE) pbmi)+dwFullSize)) = 0x12345678;
        *((DWORD *) (((PBYTE) pbmi)+dwFullSize-sizeof(DWORD))) = 0x23456789;
#endif

        if (pbmi)
        {
            // Load the new larger LPBITMAPINFO struct with existing info, 
            // and get the data bits. Release the existing LPBITMAPINFO.
            
            CopyMemory(pbmi, lpbmih, dwInfoSize);
             
            //
            // We have a handle, we want the exact bits.
            // 

            iRes = GetDIBits(hDC,
                             hbm,
                             0,
                             BitMap.bmHeight,
                             ((LPBYTE) pbmi) + dwInfoSize,
                             pbmi,
                             DIB_RGB_COLORS);

#ifdef DEBUG
            if (*((DWORD *) (((PBYTE) pbmi) + dwFullSize)) != 0x12345678)
            {
                CMTRACE(TEXT("CmGetBitmapInfo() GetDIBits() copied too much."));
            }

            if (*((DWORD *) (((PBYTE) pbmi) + dwFullSize - sizeof(DWORD))) == 0x23456789)
            {
                CMTRACE(TEXT("CmGetBitmapInfo() GetDIBits() didn't copy enough."));
            }
#endif    
            // If GetDiBits() failed, free the BITMAPINFO buffer
            
            if (!iRes) 
            {
                CmFree(pbmi);
                pbmi = NULL;
            }
        }
    }
          
    // Cleanup

Cleanup:
    if (lpbmih)
    {
        CmFree(lpbmih);
    }
    if (hDC)
    {
        ReleaseDC(NULL, hDC);       
    }
    
    return pbmi;
}

static HPALETTE CmCreateDIBPalette(LPBITMAPINFO pbmi) 
{
    WORD wNumColors = 0;
    HPALETTE hRes = NULL;

    if (!pbmi) 
    {
        return (NULL);
    }
    
    // Get num colors according to color depth
    // Note: 24-bit bitmaps have no color table

    if (pbmi->bmiHeader.biBitCount <= 8) 
    {
        wNumColors = 1 << pbmi->bmiHeader.biBitCount;
    } 

    // Fill logical palette based upon color table

    if (wNumColors) 
    {
        LPLOGPALETTE pLogPal;
        int idx;

        pLogPal = (LPLOGPALETTE) CmMalloc(sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*wNumColors);
        if (pLogPal)
        {
            pLogPal->palVersion = 0x300;
            pLogPal->palNumEntries = wNumColors;
            for (idx=0;idx<wNumColors;idx++) 
            {
                pLogPal->palPalEntry[idx].peRed = pbmi->bmiColors[idx].rgbRed;
                pLogPal->palPalEntry[idx].peGreen = pbmi->bmiColors[idx].rgbGreen;
                pLogPal->palPalEntry[idx].peBlue = pbmi->bmiColors[idx].rgbBlue;
                pLogPal->palPalEntry[idx].peFlags = 0;
            }
        
            // Create a new palette

            hRes = CreatePalette(pLogPal);

#ifdef DEBUG
            if (!hRes)
            {
                CMTRACE1(TEXT("CmCreateDIBPalette() CreatePalette() failed, GLE=%u."), GetLastError());
            }
#endif

            CmFree(pLogPal);
        }
    }
    return hRes;
}

HBITMAP CmLoadBitmap(HINSTANCE hInst, LPCTSTR pszSpec) 
{
    return ((HBITMAP) CmLoadImage(hInst, pszSpec, IMAGE_BITMAP, 0, 0));
}

//+----------------------------------------------------------------------------
//
// Function:  ReleaseBitmapData
//
// Synopsis:  Releases resources and memory acquired during CreateBitmapData.  Note
//            that if you are using this with the BmpWndProc function below, that you
//            should call an STM_SETIMAGE with a NULL image pointer param in order to
//            clear out the window procedures window long.  Otherwise, it could get
//            a WM_PAINT message and try to use the freed memory before you can
//            clear it out or have the window destroyed by the dialog manager.
//
// Arguments: LPBMPDATA pBmpData - Ptr to the BmpData to be released
//
// Returns:   Nothing
//
// History:   nickball    Created    3/27/98
//
//+----------------------------------------------------------------------------
void ReleaseBitmapData(LPBMPDATA pBmpData)
{  
    MYDBGASSERT(pBmpData);

    if (NULL == pBmpData)
    {
        return;
    }

    if (pBmpData->hDIBitmap) 
    {
        DeleteObject(pBmpData->hDIBitmap);
        pBmpData->hDIBitmap = NULL;
    }
    
    if (pBmpData->hDDBitmap) 
    {
        DeleteObject(pBmpData->hDDBitmap);
        pBmpData->hDDBitmap = NULL;
    }

    if (pBmpData->pBmi)
    {
        CmFree(pBmpData->pBmi);
        pBmpData->pBmi = NULL;
    }
}

//+----------------------------------------------------------------------------
//
// Function:  CreateBitmapData
//
// Synopsis:  Fills a BMPDATA struct with all data necessary to display a bitmap. 
//
// Arguments: HBITMAP hBmp - Handle of the source bitmap
//            LPBMPDATA lpBmpData - Ptr to the BmpData struct to be filled
//            HWND hwnd - The hwnd that the bitmap will be displayed in.
//            BOOL fCustomPalette - Indicates that the DDB should be created with a palette specific to the bitmap.
//
// Returns:   BOOL - TRUE on succes 
//
// History:   nickball    Created    3/27/98
//
//+----------------------------------------------------------------------------
BOOL CreateBitmapData(HBITMAP hDIBmp,
    LPBMPDATA lpBmpData,
    HWND hwnd,
    BOOL fCustomPalette)
{   
    MYDBGASSERT(hDIBmp);
    MYDBGASSERT(lpBmpData);
    MYDBGASSERT(lpBmpData->phMasterPalette);

    if (NULL == hDIBmp || NULL == lpBmpData)
    {
        return NULL;
    }

    //
    // Params look good, get busy
    //

    HPALETTE hPaletteNew = NULL;
    LPBITMAPINFO pBmi = NULL;
    HBITMAP hDDBmp = NULL;
    HDC hDC;
    int iRes = 0;

    //
    // If we already have a pBmi value, we will assume it is up to date, as 
    // both it and the DIB do not change throughout the life of the BMP.
    // Note: If BmpData is not zero initialized, you will have problems.
    //

    if (lpBmpData->pBmi)
    {
        pBmi = lpBmpData->pBmi;
    }
    else
    {   
        //
        // Use the bitmap handle to retrieve a BITMAPINFO ptr complete w/ data
        //
        
        pBmi = CmGetBitmapInfo(lpBmpData->hDIBitmap);
        
        if (NULL == pBmi) 
        {
            return FALSE;
        }
    }
    
    //
    // we need a DC
    //
    
    hDC = GetDC(hwnd);

    if (!hDC)
    {
        CMTRACE(TEXT("MyCreateDDBitmap() GetDC() failed."));
        return FALSE;
    }

    //
    //  If CM is localized so that it is RTL (Right to Left => arabic and Hebrew),
    //  then we need to call SetLayout on the hDC from above.  If we don't
    //  set the layout back to LTR, the bitmap will show up as all black instead of as
    //  an image.
    //
    HMODULE hLib = LoadLibrary(TEXT("gdi32.dll"));
    
    if (hLib)
    {
        #ifndef LAYOUT_RTL
        #define LAYOUT_RTL                         0x00000001 // Right to left
        #endif

        DWORD dwLayout;
        typedef DWORD (WINAPI* pfnSetLayoutType)(HDC, DWORD);
        typedef DWORD (WINAPI* pfnGetLayoutType)(HDC);

        pfnSetLayoutType pfnSetLayout = (pfnSetLayoutType)GetProcAddress(hLib, "SetLayout");
        pfnGetLayoutType pfnGetLayout = (pfnGetLayoutType)GetProcAddress(hLib, "GetLayout");

        if (pfnSetLayout && pfnGetLayout)
        {
            DWORD dwLayout = pfnGetLayout(hDC);
    
            if (LAYOUT_RTL & dwLayout)
            {
                dwLayout ^= LAYOUT_RTL; // toggle LAYOUT_RTL off
                pfnSetLayout(hDC, dwLayout);
                CMTRACE(TEXT("CreateBitmapData -- Toggling off LAYOUT_RTL on the device context"));
            }
        }

        FreeLibrary(hLib);
    }

    //
    // If fCustomPalette is set then create a palette based on our bits
    // and realize it in the current DC.
    //

    if (fCustomPalette) 
    {
        hPaletteNew = CmCreateDIBPalette(pBmi);
        
        if (hPaletteNew) 
        {                           
            //
            // Select and realize the new palette so that the DDB is created with it below
            //

            HPALETTE hPalettePrev = SelectPalette(hDC, 
                hPaletteNew, lpBmpData->bForceBackground); // FALSE == Foreground app behavior);
                                                               // TRUE == Background app behavior);

            if (hPalettePrev) 
            {
                iRes = RealizePalette(hDC);
#ifdef DEBUG
                if (GDI_ERROR == iRes)
                {
                    CMTRACE1(TEXT("MyCreateDDBitmap() RealizePalette() failed, GLE=%u."), GetLastError());                    
                }
            }
            else
            {
                CMTRACE1(TEXT("MyCreateDDBitmap() SelectPalette() failed, GLE=%u."), GetLastError());
#endif
            }

        }
    }

    //
    // Determine number of color entries based upon color depth
    //

    int nNumColors = 0;
    
    if (pBmi->bmiHeader.biBitCount <= 8)
    {
        nNumColors = (1 << pBmi->bmiHeader.biBitCount);
    }

    //
    // Create the DDB from the bits 
    //

    hDDBmp = CreateDIBitmap(hDC,
                          &pBmi->bmiHeader,                        
                          CBM_INIT,
                          ((LPBYTE) pBmi) + sizeof(BITMAPINFOHEADER) + (nNumColors * sizeof(RGBQUAD)), //dib.dsBm.bmBits,
                          pBmi,
                          DIB_RGB_COLORS);

#ifdef DEBUG
    if (!hDDBmp)
    {
        CMTRACE(TEXT("MyCreateDDBitmap() CreateDIBitmap() failed."));
    }
#endif

    ReleaseDC(NULL, hDC);

    //
    // Fill in the bitmap data
    //

    if (hDDBmp)
    {
        lpBmpData->hDIBitmap = hDIBmp;       
        lpBmpData->pBmi = pBmi;

        //
        // Delete existing DDB, if any
        //

        if (lpBmpData->hDDBitmap) 
        {
            DeleteObject(lpBmpData->hDDBitmap);
        } 

        lpBmpData->hDDBitmap = hDDBmp;

        if (hPaletteNew)
        {
            //
            // Delete existing Palette, if any
            //

            if (*lpBmpData->phMasterPalette)
            {
                DeleteObject(*lpBmpData->phMasterPalette);
            }

            *lpBmpData->phMasterPalette = hPaletteNew;
        }

        return TRUE;
    }

    //
    // Something went wrong, cleanup
    //

    CmFree(pBmi);

    return FALSE;
}

//
// Bitmap window procedure
//

LRESULT CALLBACK BmpWndProc(HWND hwndBmp, 
                            UINT uMsg, 
                            WPARAM wParam, 
                            LPARAM lParam) 
{
    LPBMPDATA pBmpData = (LPBMPDATA) GetWindowLongU(hwndBmp,0);   
    BOOL bRes;

    switch (uMsg) 
    {
        case WM_CREATE:
        {
            return FALSE;
        }

        case WM_DESTROY:
            SetWindowLongU(hwndBmp,sizeof(LPBMPDATA),(LONG_PTR) NULL);      
            break;

        case WM_PAINT:
            if (pBmpData && pBmpData->pBmi) 
            {
                LPBITMAPINFO pBmi = pBmpData->pBmi;

                RECT rWnd;
                RECT rSrc = {0,0,(int)pBmpData->pBmi->bmiHeader.biWidth,
                                 (int)pBmpData->pBmi->bmiHeader.biHeight};
                PAINTSTRUCT ps;
                HDC hdcBmp;
                HBITMAP hbmpPrev;
                int iPrevStretchMode;
                
                //
                // Start  painting
                //

                HDC hdc = BeginPaint(hwndBmp,&ps);

                if (hdc)
                {
                    //
                    // Select and realize our current palette in the current DC
                    //

                    //UnrealizeObject(*pBmpData->phMasterPalette);
                    SelectPalette(hdc, *pBmpData->phMasterPalette, pBmpData->bForceBackground);
                    RealizePalette(hdc);

                    //
                    // Create a compatible DC, we'll create the BMP here then BLT it to the real DC
                    //

                    hdcBmp = CreateCompatibleDC(hdc);

                    if (hdcBmp)
                    {
                        //
                        // Select and realize our current palette in the compatible DC
                        //

                        SelectPalette(hdcBmp, *pBmpData->phMasterPalette, pBmpData->bForceBackground);
                        RealizePalette(hdcBmp);

                        if (!hdcBmp)
                        {
                            CMTRACE(TEXT("BmpWndProc() CreateCompatibleDC() failed."));
                        }

                        if (!pBmpData->hDDBitmap)
                        {
                            CMTRACE(TEXT("BmpWndProc() - WM_PAINT - hDDBitmap is NULL."));
                        }

                        //
                        // Select the bitmap into the compatible DC
                        //

                        hbmpPrev = (HBITMAP) SelectObject(hdcBmp,pBmpData->hDDBitmap);
                        bRes = GetWindowRect(hwndBmp,&rWnd);

                        if (!bRes)
                        {
                            CMTRACE1(TEXT("BmpWndProc() GetWindowRect() failed, GLE=%u."), GetLastError());
                        }       

                        //
                        // Now set the mode, and StretchBlt the bitmap from the compatible DC to the active DC
                        //

                        CMTRACE(TEXT("BmpWndProc() : Changing stretch mode"));
                        iPrevStretchMode = SetStretchBltMode(hdc, STRETCH_DELETESCANS);

                        bRes = StretchBlt(hdc,
                                          rWnd.left-rWnd.left,
                                          rWnd.top-rWnd.top,
                                          rWnd.right-rWnd.left,
                                          rWnd.bottom-rWnd.top,
                                          hdcBmp,
                                          rSrc.left-rSrc.left,
                                          rSrc.top-rSrc.top,
                                          rSrc.right-rSrc.left,
                                          rSrc.bottom-rSrc.top,
                                          SRCCOPY);
                        if (!bRes)
                        {
                            CMTRACE1(TEXT("BmpWndProc() StretchBlt() failed, GLE=%u."), GetLastError());
                        }

                        //
                        // Restore the mode in the active DC
                        //
                        CMTRACE(TEXT("BmpWndProc() Restoring stretch mode"));
                        iPrevStretchMode = SetStretchBltMode(hdc, iPrevStretchMode);

                        //
                        // Restore the compatible DC and release it
                        //

                        SelectObject(hdcBmp,hbmpPrev);          
                        DeleteDC(hdcBmp);

                    }
                    else
                    {
                        CMTRACE1(TEXT("BmpWndProc() CreateCompatibleDC() failed, GLE=%u."), GetLastError());
                    }


                    bRes = EndPaint(hwndBmp,&ps);

                    if (!bRes)
                    {
                        CMTRACE(TEXT("BmpWndProc() EndPaint() failed."));
                    }
                }
                else
                {
                    CMTRACE1(TEXT("BmpWndProc() BeginPaint() failed, GLE=%u."), GetLastError());
                }

            }
            break;

        case STM_SETIMAGE:
            if (wParam == IMAGE_BITMAP) 
            {
                CMTRACE2(TEXT("STM_SETIMAGE: wParam=%u, lParam=%u"), wParam, lParam);

                //
                // lParam contains a handle to the bitmap data, store it in extra bytes
                // 

                SetWindowLongU(hwndBmp,0, lParam); // pBmpData

                CMTRACE2(TEXT("SetWindowLongU called with hwndBmp = %u, lParam=%u"), hwndBmp, lParam);

                //
                // Force a repaint
                //

                bRes = InvalidateRect(hwndBmp,NULL,TRUE);

                CMTRACE2(TEXT("InvalidateRect called with hwndBmp = %u, lParam=%u"), hwndBmp, lParam);

#ifdef DEBUG
                if (!bRes)
                {
                    CMTRACE(TEXT("BmpWndProc() InvalidateRect() failed."));
                }
#endif              
                if (pBmpData && pBmpData->hDDBitmap) 
                {
                    return ((LRESULT) pBmpData->hDDBitmap);
                }
                else
                {
                    return NULL;
                }
            }
            break;
    }
    return (DefWindowProcU(hwndBmp,uMsg,wParam,lParam));
}

//+---------------------------------------------------------------------------
//
//  Function:   QueryNewPalette
//
//  Synopsis:   Helper function to encapsulate handling of WM_QUERYNEWPALETTE
//                          
//  Arguments:  hwndDlg     - Handle of the dialog receiving the message
//              lpBmpData   - Struct containing handles for bmp to display
//              iBmpCtrl    - Bitmap control ID
//
//  Returns:    Nothing
//
//  History:    a-nichb - Created - 7/14/97
//
//----------------------------------------------------------------------------
void QueryNewPalette(LPBMPDATA lpBmpData, HWND hwndDlg, int iBmpCtrl)
{
    MYDBGASSERT(lpBmpData);

    if (lpBmpData)
    {
        //
        // We just handle this as a standard palette change because we
        // want to ensure that we create a new DDB using a palette based
        // upon our bitmap.
        //
                
        PaletteChanged(lpBmpData, hwndDlg, iBmpCtrl);
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   PaletteChanged
//
//  Synopsis:   Helper function to encapsulate handling of WM_PALETTECHANGED
//                          
//  Arguments:  hwndDlg     - Handle of the dialog receiving the message
//              lpBmpData   - Struct containing handles for bmp to display
//              iBmpCtrl    - Bitmap control ID
//
//  Returns:    Nothing
//
//  History:    a-nichb - Created - 7/14/97
//
//----------------------------------------------------------------------------
void PaletteChanged(LPBMPDATA lpBmpData, HWND hwndDlg, int iBmpCtrl)
{   
    MYDBGASSERT(lpBmpData);

    if (NULL == lpBmpData || NULL == lpBmpData->phMasterPalette)
    {
        return;
    }

    //
    // Unrealize the master palette if it exists
    //
       
    if (*lpBmpData->phMasterPalette)
    {
        UnrealizeObject(*lpBmpData->phMasterPalette);
    }

    //
    // Create a device dependent bitmap and appropriate palette
    //

    if (CreateBitmapData(lpBmpData->hDIBitmap, lpBmpData, hwndDlg, TRUE))
    {        
        //
        // SetImage to update handles for painting and force draw
        //

        HBITMAP hbmpTmp = (HBITMAP) SendDlgItemMessageA(hwndDlg, iBmpCtrl, STM_SETIMAGE,
                                               IMAGE_BITMAP,(LPARAM) lpBmpData);
#ifdef DEBUUG
                if (!hbmpTmp)
                {
                    CMTRACE(TEXT("PaletteChanged().WM_PALETTECHANGED - STM_SETIMAGE returned NULL."));
                }
#endif

    }
}