//-----------------------------------------------------------------------------
// File: Cbitmap.cpp
//
// Desc: CBitmap class is an object that wraps around a Windows bitmap.
//
// Copyright (C) 1999-2001 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------

#include "common.hpp"
#include "id3dsurf.h"


BOOL DI_AlphaBlend(
  HDC hdcDest,                 // handle to destination DC
  int nXOriginDest,            // x-coord of upper-left corner
  int nYOriginDest,            // y-coord of upper-left corner
  int nWidthDest,              // destination width
  int nHeightDest,             // destination height
  HDC hdcSrc,                  // handle to source DC
  int nXOriginSrc,             // x-coord of upper-left corner
  int nYOriginSrc,             // y-coord of upper-left corner
  int nWidthSrc,               // source width
  int nHeightSrc              // source height
)
{
    LPBYTE pbDestBits = NULL;
    HBITMAP hTempDestDib = NULL;
    int nXOriginDestLogical = nXOriginDest, nYOriginDestLogical = nYOriginDest;

    // Convert nXOriginDest and nYOriginDest from logical coord to device coord
    POINT pt = {nXOriginDest, nYOriginDest};
    LPtoDP(hdcDest, &pt, 1);
    nXOriginDest = pt.x;
    nYOriginDest = pt.y;
    // Convert nXOriginSrc and nYOriginSrc from logical coord to device coord
    pt.x = nXOriginSrc;
    pt.y = nYOriginSrc;
    LPtoDP(hdcSrc, &pt, 1);
    nXOriginSrc = pt.x;
    nYOriginSrc = pt.y;

    // Get the bits for both source and destination first
    // Every BITMAP used in the UI is created with CreateDIBSection, so we know we can get the bits.
    HBITMAP hSrcBmp, hDestBmp;
    DIBSECTION SrcDibSec, DestDibSec;
    hSrcBmp = (HBITMAP)GetCurrentObject(hdcSrc, OBJ_BITMAP);
    GetObject(hSrcBmp, sizeof(DIBSECTION), &SrcDibSec);
    hDestBmp = (HBITMAP)GetCurrentObject(hdcDest, OBJ_BITMAP);
    GetObject(hDestBmp, sizeof(DIBSECTION), &DestDibSec);
    if (!SrcDibSec.dsBm.bmBits) return FALSE;  // Not necessary, but to be absolutely safe.

    // Calculate the rectangle to perform the operation.
    if (nXOriginSrc + nWidthSrc > SrcDibSec.dsBm.bmWidth) nWidthSrc = SrcDibSec.dsBm.bmWidth - nXOriginSrc;
    if (nYOriginSrc + nHeightSrc > SrcDibSec.dsBm.bmHeight) nHeightSrc = SrcDibSec.dsBm.bmHeight - nYOriginSrc;
    if (nXOriginDest + nWidthDest > DestDibSec.dsBm.bmWidth) nWidthDest = DestDibSec.dsBm.bmWidth - nXOriginDest;
    if (nYOriginDest + nHeightDest > DestDibSec.dsBm.bmHeight) nHeightDest = DestDibSec.dsBm.bmHeight - nYOriginDest;

    if (nWidthDest > nWidthSrc) nWidthDest = nWidthSrc;
    if (nHeightDest > nHeightSrc) nHeightDest = nHeightSrc;
    if (nWidthSrc > nWidthDest) nWidthSrc = nWidthDest;
    if (nHeightSrc > nHeightDest) nHeightSrc = nHeightDest;

    BITMAPINFO bmi;
    ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = nWidthDest;
    bmi.bmiHeader.biHeight = nHeightDest;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;

    // Bitmap will have the same width as the dest, but only lines covered in the subrect.
    hTempDestDib = CreateDIBSection(hdcDest, &bmi, DIB_RGB_COLORS, (LPVOID*)&pbDestBits, NULL, NULL);
    if (!hTempDestDib)
        return FALSE;

    HDC hTempDC = CreateCompatibleDC(hdcDest);
    if (!hTempDC)
    {
        DeleteObject(hTempDestDib);
        return FALSE;
    }
    HBITMAP hOldTempBmp = (HBITMAP)SelectObject(hTempDC, hTempDestDib);
    BOOL res = BitBlt(hTempDC, 0, 0, nWidthDest, nHeightDest, hdcDest, nXOriginDestLogical, nYOriginDestLogical, SRCCOPY);
    SelectObject(hTempDC, hOldTempBmp);
    DeleteDC(hTempDC);
    if (!res)
    {
        DeleteObject(hTempDestDib);
        return FALSE;
    }

    // We have the bits. Now do the blend.
    for (int j = 0; j < nHeightSrc; ++j)
    {
        assert(j >= 0 &&
               j < nHeightDest);
        LPBYTE pbDestRGB = (LPBYTE)&((DWORD*)pbDestBits)[j * nWidthDest];

        assert(nYOriginSrc+SrcDibSec.dsBm.bmHeight-nHeightSrc >= 0 &&
               nYOriginSrc+SrcDibSec.dsBm.bmHeight-nHeightSrc < SrcDibSec.dsBm.bmHeight);
        LPBYTE pbSrcRGBA = (LPBYTE)&((DWORD*)SrcDibSec.dsBm.bmBits)[(j+nYOriginSrc+SrcDibSec.dsBm.bmHeight-nHeightSrc)
                                                                    * SrcDibSec.dsBm.bmWidth + nXOriginSrc];

        for (int i = 0; i < nWidthSrc; ++i)
        {
            // Blend
            if (pbSrcRGBA[3] == 255)
            {
                // Alpha is 255. straight copy.
                *(LPDWORD)pbDestRGB = *(LPDWORD)pbSrcRGBA;
            } else
            if (pbSrcRGBA[3])
            {
                // Alpha is non-zero
                pbDestRGB[0] = pbSrcRGBA[0] + (((255-pbSrcRGBA[3]) * pbDestRGB[0]) >> 8);
                pbDestRGB[1] = pbSrcRGBA[1] + (((255-pbSrcRGBA[3]) * pbDestRGB[1]) >> 8);
                pbDestRGB[2] = pbSrcRGBA[2] + (((255-pbSrcRGBA[3]) * pbDestRGB[2]) >> 8);
            }
            pbDestRGB += sizeof(DWORD);
            pbSrcRGBA += sizeof(DWORD);
        }  // for
    } // for

    HDC hdcTempDest = CreateCompatibleDC(hdcDest);
    if (hdcTempDest)
    {
        HBITMAP hOldTempBmp = (HBITMAP)SelectObject(hdcTempDest, hTempDestDib);  // Select the temp dib for blitting
        // Get logical coord for device coord of dest origin
        POINT pt = {nXOriginDest, nYOriginDest};
        DPtoLP(hdcDest, &pt, 1);
        BitBlt(hdcDest, pt.x, pt.y, nWidthDest, nHeightDest,
               hdcTempDest, 0, 0, SRCCOPY);
        SelectObject(hdcTempDest, hOldTempBmp);
        DeleteDC(hdcTempDest);
    }

    DeleteObject(hTempDestDib);
    return TRUE;
}

CBitmap::~CBitmap()
{
    if (m_hbm != NULL)
        DeleteObject(m_hbm);
    m_hbm = NULL;
    m_bSizeKnown = FALSE;
}

HDC CreateAppropDC(HDC hDC)
{
    return CreateCompatibleDC(hDC);
}

HBITMAP CreateAppropBitmap(HDC hDC, int cx, int cy)
{
    if (hDC != NULL)
        return CreateCompatibleBitmap(hDC, cx, cy);
    
    HWND hWnd = GetDesktopWindow();
    HDC hWDC = GetWindowDC(hWnd);
    HBITMAP hbm = NULL;
    if (hWDC != NULL)
    {
        hbm = CreateCompatibleBitmap(hWDC, cx, cy);
        ReleaseDC(hWnd, hWDC);
    }

    return hbm;
}

CBitmap *CBitmap::CreateFromResource(HINSTANCE hInst, LPCTSTR tszName)
{
    return CreateViaLoadImage(hInst, tszName, IMAGE_BITMAP, 0, 0,
        LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
}

CBitmap *CBitmap::CreateFromFile(LPCTSTR tszFileName)
{
    return CreateViaD3DX(tszFileName);
}

// Use D3DX API to load our surface with image content.
CBitmap *CBitmap::CreateViaD3DX(LPCTSTR tszFileName, LPDIRECT3DSURFACE9 pUISurf)
{
    HRESULT hr;
    LPDIRECT3DDEVICE9 pD3DDev = NULL;
    LPDIRECT3DTEXTURE9 pTex = NULL;
    LPDIRECT3DSURFACE9 pSurf = NULL;
    HBITMAP hDIB = NULL;
    CBitmap *pbm = NULL;

    // If the UI surface is NULL, create a new device.  Otherwise, use existing device.
    if (!pUISurf)
    {
        LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);

		if (!pD3D)
        {
            OutputDebugString(_T("Failed to create D3D\n"));
            hr = E_FAIL;
        }
        else
        {
            D3DDISPLAYMODE Mode;
            pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &Mode);
            D3DPRESENT_PARAMETERS d3dpp;
            d3dpp.BackBufferWidth = 1;
            d3dpp.BackBufferHeight = 1;
            d3dpp.BackBufferFormat = Mode.Format;
            d3dpp.BackBufferCount = 1;
            d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
            d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
            d3dpp.hDeviceWindow = NULL;
            d3dpp.Windowed = TRUE;
            d3dpp.EnableAutoDepthStencil = FALSE;
            d3dpp.FullScreen_RefreshRateInHz = 0;
            d3dpp.PresentationInterval = 0;
            d3dpp.Flags = 0;
            hr = pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, GetActiveWindow(), D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDev);
            if (FAILED(hr))
            {
                TCHAR tszMsg[MAX_PATH];

                _stprintf(tszMsg, _T("CreateDevice returned 0x%X\n"), hr);
                OutputDebugString(tszMsg);
            }
            
            pD3D->Release();
        }
    } 
    else
    {
        hr = pUISurf->GetDevice(&pD3DDev);
    }


    if( FAILED( hr ) )
    {
        OutputDebugString(_T("Failed to create D3D device\n"));
    }
    else if( FAILED( hr = pD3DDev->CreateTexture(512, 512, 0, 0, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pTex, NULL) ) ) 
    {
        OutputDebugString(_T("Failed to create D3D texture\n"));
    }
    else if( FAILED( hr = pTex->GetSurfaceLevel(0, &pSurf) ) )
    {
        OutputDebugString(_T("Failed to obtain surface interface\n"));
    }
    else
    {
        D3DXIMAGE_INFO d3dii;

        if( SUCCEEDED( D3DXLoadSurfaceFromFile(pSurf, NULL, NULL, tszFileName, NULL, D3DX_FILTER_NONE, 0, &d3dii) ) )
        {
            // Create a bitmap and copy the texture content onto it.
            int iDibWidth = d3dii.Width, iDibHeight = d3dii.Height;
            if (iDibWidth > 430) iDibWidth = 430;
            if (iDibHeight > 310) iDibHeight = 310;
            LPBYTE pDIBBits;
            BITMAPINFO bmi;
            bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
            bmi.bmiHeader.biWidth = iDibWidth;
            bmi.bmiHeader.biHeight = iDibHeight;
            bmi.bmiHeader.biPlanes = 1;
            bmi.bmiHeader.biBitCount = 32;
            bmi.bmiHeader.biCompression = BI_RGB;
            bmi.bmiHeader.biSizeImage = 0;
            bmi.bmiHeader.biXPelsPerMeter = 0;
            bmi.bmiHeader.biYPelsPerMeter = 0;
            bmi.bmiHeader.biClrUsed = 0;
            bmi.bmiHeader.biClrImportant = 0;
            hDIB = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (LPVOID*)&pDIBBits, NULL, 0);
            if (hDIB)
            {
                // Pre-process the pixel data based on alpha for AlphaBlend()
                D3DLOCKED_RECT lrc;
                pSurf->LockRect(&lrc, NULL, NULL);
                BYTE *pbData = (LPBYTE)lrc.pBits;
                {
                    for (DWORD i = 0; i < 512 * 512; ++i)
                    {
                        BYTE bAlpha = pbData[i * 4 + 3];
                        pbData[i * 4] = pbData[i * 4] * bAlpha / 255;
                        pbData[i * 4 + 1] = pbData[i * 4 + 1] * bAlpha / 255;
                        pbData[i * 4 + 2] = pbData[i * 4 + 2] * bAlpha / 255;
                    }
                }
                pSurf->UnlockRect();

                // Lock the surface
                D3DLOCKED_RECT D3DRect;
                hr = pSurf->LockRect(&D3DRect, NULL, 0);
                if (SUCCEEDED(hr))
                {
                    // Copy the bits
                    // Note that the image is reversed in Y direction, so we need to re-reverse it.
                    for (int y = 0; y < iDibHeight; ++y)
                        CopyMemory(pDIBBits + ((iDibHeight-1-y) * iDibWidth * 4), (LPBYTE)D3DRect.pBits + (y * D3DRect.Pitch), iDibWidth * 4);

                    // Unlock
                    pSurf->UnlockRect();

                    pbm = new CBitmap;

                    if (pbm) 
                    {
                        pbm->m_hbm = hDIB;
                        hDIB = NULL;
                        pbm->FigureSize();
                    }
                }
            }
        }
    }

	if (hDIB) DeleteObject(hDIB);
    if (pSurf) pSurf->Release();
    if (pTex) pTex->Release();
    if (pD3DDev) pD3DDev->Release();

    return pbm;
}

CBitmap *CBitmap::CreateViaLoadImage(HINSTANCE hInst, LPCTSTR tszName, UINT uType, int cx, int cy, UINT fuLoad)
{
    if (fuLoad & LR_SHARED)
    {
        assert(0);
        return NULL;
    }

    CBitmap *pbm = new CBitmap;
    if (pbm == NULL)
        return NULL;
    
    HANDLE handle = ::LoadImage(hInst, tszName, uType, cx, cy, fuLoad);
    
    if (handle == NULL)
    {
        delete pbm;
        return NULL;
    }

    pbm->m_hbm = (HBITMAP)handle;

    pbm->FigureSize();

    return pbm;
}

BOOL CBitmap::FigureSize()
{
    BITMAP bm;

    if (0 == GetObject((HGDIOBJ)m_hbm, sizeof(BITMAP), (LPVOID)&bm))
        return FALSE;

    m_size.cx = abs(bm.bmWidth);
    m_size.cy = abs(bm.bmHeight);

    return m_bSizeKnown = TRUE;
}

CBitmap *CBitmap::StealToCreate(HBITMAP &refbm)
{
    if (refbm == NULL)
        return NULL;

    CBitmap *pbm = new CBitmap;
    if (pbm == NULL)
        return NULL;
    
    pbm->m_hbm = refbm;
    refbm = NULL;

    pbm->FigureSize();

    return pbm;
}

BOOL CBitmap::GetSize(SIZE *psize)
{
    if (m_hbm == NULL || !m_bSizeKnown || psize == NULL)
        return FALSE;

    *psize = m_size;
    return TRUE;
}

void CBitmap::AssumeSize(SIZE size)
{
    m_size = size;
    m_bSizeKnown = TRUE;  //m_hbm != NULL;
}

CBitmap *CBitmap::CreateResizedTo(SIZE size, HDC hDC, int iStretchMode, BOOL bStretch)
{
    CBitmap *pbm = new CBitmap;
    HDC hSrcDC = NULL;
    HDC hDestDC = NULL;
    HBITMAP hBitmap = NULL;
    HGDIOBJ hOldSrcBitmap = NULL, hOldDestBitmap = NULL;
    BOOL bRet = FALSE;
    int oldsm = 0;
    POINT brushorg;

    if (pbm == NULL || size.cx < 1 || size.cy < 1 || m_hbm == NULL || !m_bSizeKnown)
        goto error;

    hSrcDC = CreateAppropDC(hDC);
    hDestDC = CreateAppropDC(hDC);
    if (hSrcDC == NULL || hDestDC == NULL)
        goto error;

    hBitmap = CreateAppropBitmap(hDC, size.cx, size.cy);
    if (hBitmap == NULL)
        goto error;

    if (bStretch)
    {
        if (GetStretchBltMode(hDestDC) != iStretchMode)
        {
            if (iStretchMode == HALFTONE)
                GetBrushOrgEx(hDestDC, &brushorg);
            oldsm = SetStretchBltMode(hDestDC, iStretchMode);
            if (iStretchMode == HALFTONE)
                SetBrushOrgEx(hDestDC, brushorg.x, brushorg.y, NULL);
        }
    }

    hOldSrcBitmap = SelectObject(hSrcDC, m_hbm);
    hOldDestBitmap = SelectObject(hDestDC, hBitmap);
    if (bStretch)
        bRet = StretchBlt(hDestDC, 0, 0, size.cx, size.cy, hSrcDC, 0, 0, m_size.cx, m_size.cy, SRCCOPY);
    else
        bRet = BitBlt(hDestDC, 0, 0, size.cx, size.cy, hSrcDC, 0, 0, SRCCOPY);
    SelectObject(hDestDC, hOldDestBitmap);
    SelectObject(hSrcDC, hOldSrcBitmap);

    if (bStretch)
    {
        if (oldsm != 0)
        {
            if (oldsm == HALFTONE)
                GetBrushOrgEx(hDestDC, &brushorg);
            SetStretchBltMode(hDestDC, oldsm);
            if (oldsm == HALFTONE)
                SetBrushOrgEx(hDestDC, brushorg.x, brushorg.y, NULL);
        }
    }

    if (!bRet)
        goto error;

    pbm->m_hbm = hBitmap;
    hBitmap = NULL;
    pbm->AssumeSize(size);

    goto cleanup;
error:
    if (pbm != NULL)
        delete pbm;
    pbm = NULL;
cleanup:
    if (hBitmap != NULL)
        DeleteObject(hBitmap);
    if (hSrcDC != NULL)
        DeleteDC(hSrcDC);
    if (hDestDC != NULL)
        DeleteDC(hDestDC);

    return pbm;
}

HDC CBitmap::BeginPaintInto(HDC hCDC)
{
    if (m_hDCInto != NULL)
    {
        assert(0);
        return NULL;
    }

    m_hDCInto = CreateAppropDC(hCDC);
    if (m_hDCInto == NULL)
        return NULL;

    m_hOldBitmap = SelectObject(m_hDCInto, m_hbm);

    return m_hDCInto;
}

void CBitmap::EndPaintInto(HDC &hDC)
{
    if (hDC == NULL || hDC != m_hDCInto)
    {
        assert(0);
        return;
    }

    SelectObject(m_hDCInto, m_hOldBitmap);
    DeleteDC(m_hDCInto);
    m_hDCInto = NULL;
    hDC = NULL;
}

void CBitmap::PopOut()
{
    if (m_hDCInto == NULL)
    {
        assert(0);
        return;
    }

    SelectObject(m_hDCInto, m_hOldBitmap);
}

void CBitmap::PopIn()
{
    if (m_hDCInto == NULL)
    {
        assert(0);
        return;
    }

    m_hOldBitmap = SelectObject(m_hDCInto, m_hbm);
}

BOOL CBitmap::Draw(HDC hDC, POINT origin, SIZE crop, BOOL bAll)
{
    if (hDC == NULL || m_hbm == NULL)
        return FALSE;

    if (bAll && !m_bSizeKnown)
        return FALSE;

    if (bAll)
        crop = m_size;

    HDC hDCbm = CreateAppropDC(hDC);
    if (hDCbm == NULL)
        return FALSE;

    BOOL bPop = m_hDCInto != NULL;

    if (bPop)
        PopOut();

    HGDIOBJ hOldBitmap = SelectObject(hDCbm, m_hbm);
    BOOL bRet = BitBlt(hDC, origin.x, origin.y, crop.cx, crop.cy, hDCbm, 0, 0, SRCCOPY);
    SelectObject(hDCbm, hOldBitmap);
    DeleteDC(hDCbm);

    if (bPop)
        PopIn();

    return bRet;
}

BOOL CBitmap::Blend(HDC hDC, POINT origin, SIZE crop, BOOL bAll)
{
    if (hDC == NULL || m_hbm == NULL)
        return FALSE;

    if (bAll && !m_bSizeKnown)
        return FALSE;

    if (bAll)
        crop = m_size;

    HDC hDCbm = CreateAppropDC(hDC);
    if (hDCbm == NULL)
        return FALSE;

    BOOL bPop = m_hDCInto != NULL;

    if (bPop)
        PopOut();

#ifndef AC_SRC_ALPHA
#define AC_SRC_ALPHA AC_SRC_NO_PREMULT_ALPHA
#endif

    HGDIOBJ hOldBitmap = SelectObject(hDCbm, m_hbm);
    BOOL bRet;

    bRet = DI_AlphaBlend(hDC, origin.x, origin.y, crop.cx, crop.cy, hDCbm, 0, 0, m_size.cx, m_size.cy);
    SelectObject(hDCbm, hOldBitmap);
    DeleteDC(hDCbm);

    if (bPop)
        PopIn();

    return bRet;
}

CBitmap *CBitmap::Dup()
{   
    SIZE t;
    if (!GetSize(&t))
        return NULL;
    return CreateResizedTo(t, NULL, COLORONCOLOR, FALSE);
}

CBitmap *CBitmap::Create(SIZE size, HDC hCDC)
{
    CBitmap *pbm = new CBitmap;
    if (pbm == NULL)
        return NULL;

    pbm->m_hbm = CreateAppropBitmap(hCDC, size.cx, size.cy);
    if (pbm->m_hbm == NULL)
    {
        delete pbm;
        return NULL;
    }

    pbm->AssumeSize(size);

    return pbm;
}

CBitmap *CBitmap::Create(SIZE size, COLORREF color, HDC hCDC)
{
    CBitmap *pbm = Create(size, hCDC);
    if (pbm == NULL)
        return NULL;

    HDC hDC = pbm->BeginPaintInto();
    if (hDC == NULL)
    {
        delete pbm;
        return NULL;
    }
    
    HGDIOBJ hBrush = (HGDIOBJ)CreateSolidBrush(color), hOldBrush;

    if (hBrush)
    {
        hOldBrush = SelectObject(hDC, hBrush);
        Rectangle(hDC, -1, -1, size.cx + 1, size.cy + 1);
        SelectObject(hDC, hOldBrush);
        DeleteObject(hBrush);
    }

    pbm->EndPaintInto(hDC);

    return pbm;
}

BOOL CBitmap::Get(HDC hDC, POINT point)
{
    if (!m_bSizeKnown)
        return FALSE;
    return Get(hDC, point, m_size);
}

BOOL CBitmap::Get(HDC hDC, POINT point, SIZE size)
{
    if (m_hDCInto != NULL || hDC == NULL)
        return FALSE;

    HDC hDCInto = BeginPaintInto(hDC);
    if (hDCInto == NULL)
        return FALSE;

    BOOL bRet = BitBlt(hDCInto, 0, 0, size.cx, size.cy, hDC, point.x, point.y, SRCCOPY);
    
    EndPaintInto(hDCInto);

    return bRet;
}

CBitmap *CBitmap::CreateHorzGradient(const RECT &rect, COLORREF rgbLeft, COLORREF rgbRight)
{
    SIZE size = GetRectSize(rect);
    COLORREF rgbMid = RGB(
        (int(GetRValue(rgbLeft)) + int(GetRValue(rgbRight))) / 2,
        (int(GetGValue(rgbLeft)) + int(GetGValue(rgbRight))) / 2,
        (int(GetBValue(rgbLeft)) + int(GetBValue(rgbRight))) / 2);
    return Create(size, rgbMid);
}

BOOL CBitmap::MapToDC(HDC hDCTo, HDC hDCMapFrom)
{
    if (hDCTo == NULL || !m_bSizeKnown || m_hDCInto != NULL)
        return FALSE;

    HBITMAP hbm = CreateAppropBitmap(hDCTo, m_size.cx, m_size.cy);
    if (hbm == NULL)
        return FALSE;

    HDC hDCFrom = NULL;
    HDC hDCInto = NULL;
    HGDIOBJ hOld = NULL;
    BOOL bRet = FALSE;

    hDCFrom = BeginPaintInto(hDCMapFrom);
    if (!hDCFrom)
        goto cleanup;

    hDCInto = CreateCompatibleDC(hDCTo);
    if (!hDCInto)
        goto cleanup;

    hOld = SelectObject(hDCInto, (HGDIOBJ)hbm);
    bRet = BitBlt(hDCInto, 0, 0, m_size.cx, m_size.cy, hDCFrom, 0, 0, SRCCOPY);
    SelectObject(hDCInto, hOld);

cleanup:
    if (hDCFrom)
        EndPaintInto(hDCFrom);
    if (hDCInto)
        DeleteDC(hDCInto);
    if (bRet)
    {
        if (m_hbm)
            DeleteObject((HGDIOBJ)m_hbm);
        m_hbm = hbm;
        hbm = NULL;
    }
    if (hbm)
        DeleteObject((HGDIOBJ)hbm);

    return bRet;
}
