
#include "stdafx.h"
#include "global.h"
#include "pbrush.h"
#include "pbrusdoc.h"
#include "pbrusfrm.h"
#include "pbrusvw.h"
#include "minifwnd.h"
#include "bmobject.h"
#include "imgsuprt.h"
#include "imgwnd.h"
#include "imgwell.h"
#include "imgtools.h"
#include "t_fhsel.h"
#include "toolbox.h"
#include "imgbrush.h"
#include "imgdlgs.h"

#ifdef _DEBUG
#undef THIS_FILE
static CHAR BASED_CODE THIS_FILE[] = __FILE__;
#endif // _DEBUG

#include "memtrace.h"

BOOL  g_bUseTrans = FALSE;
BOOL  g_bCustomBrush = FALSE;
BOOL  fDraggingBrush = FALSE;
IMG*  pImgCur = NULL;

#define NUM_DEF_COLORS 28
extern COLORREF  colorColorsDef[NUM_DEF_COLORS];

COLORREF  crLeft  = 0;
COLORREF  crRight = RGB( 0xff, 0xff, 0xff );
COLORREF  crTrans = TRANS_COLOR_NONE; // transparent color
int       theLeft;
int       theRight;
int       theTrans;
int       wCombineMode;

HDC       hRubberDC;
HBITMAP   hRubberBM;
int       cxRubberWidth;
int       cyRubberHeight;
IMG*      pRubberImg;

BOOL     EnsureUndoSize(IMG* pImg);

static int   cxUndoWidth, cyUndoHeight;
static BYTE  cUndoPlanes, cUndoBitCount;

HBITMAP   g_hUndoImgBitmap = NULL;
HPALETTE  g_hUndoPalette   = NULL;

COLORREF  std2Colors [] =
    {
    RGB(000, 000, 000), //  0 - black
    RGB(255, 255, 255)  //  1 - white
    };

COLORREF  std16Colors [] =
    {
    RGB(  0,   0,   0), // Black
    RGB(128,   0,   0), // Dark Red
    RGB(  0, 128,   0), // Dark Green
    RGB(128, 128,   0), // Pea Green
    RGB(  0,   0, 128), // Dark Blue
    RGB(128,   0, 128), // Lavender
    RGB(  0, 128, 128), // Slate
    RGB(192, 192, 192), // Light Gray
    RGB(128, 128, 128), // Dark Gray
    RGB(255,   0,   0), // Bright Red
    RGB(  0, 255,   0), // Bright Green
    RGB(255, 255,   0), // Yellow
    RGB(  0,   0, 255), // Bright Blue
    RGB(255,   0, 255), // Magenta
    RGB(  0, 255, 255), // Cyan
    RGB(255, 255, 255)  //  1 - white
    };

/***************************************************************************/

IMG* CreateImg(int cxWidth, int cyHeight, int cPlanes, int cBitCount, int cXPelsPerMeter, int cYPelsPerMeter, BOOL bPalette )
{
        IMG* pimg = NULL;
        CTempBitmap bmNew;
        HBITMAP hbmOld = NULL;

        CClientDC dcScreen(NULL);


        if (! cPlanes )
        {
                cPlanes = dcScreen.GetDeviceCaps( PLANES );
        }
        if (! cBitCount)
        {
                cBitCount = dcScreen.GetDeviceCaps( BITSPIXEL );
        }

        CDC cDC;
        cDC.CreateCompatibleDC( &dcScreen );
        if (!cDC.m_hDC)
        {
                return NULL;
        }

        if (cPlanes * cBitCount > 1)
        {
                cDC.SetStretchBltMode(HALFTONE);

        }

        // Set these to 0 to not create a bitmap
        if (cxWidth && cyHeight)
        {
                BOOL bMono = (cPlanes == 1 && cBitCount == 1);
                COLORREF* pcrColors = NULL;
                int nColors = 0;

                cBitCount *= cPlanes;
                if (cBitCount <= 1)
                {
                        cBitCount = 1;
                        pcrColors = std2Colors;
                        nColors = 2;
                }
                else if (cBitCount <= 4)
                {
                        cBitCount = 4;
                        pcrColors = std16Colors;
                        nColors = 16;
                }
                else if (cBitCount <= 8)
                {
                        cBitCount = 8;
                        pcrColors = colorColorsDef;
                        nColors = NUM_DEF_COLORS;
                }
                else
                {
                        // I don't want to deal with 15 or 16 bit images
                        cBitCount = 24;
                }

                HBITMAP hbmNew = NULL;

                if (cBitCount == 4)
                {
                        // Just create a DDB if in 16 colors
                        hbmNew = CreateCompatibleBitmap( dcScreen.m_hDC, cxWidth, cyHeight);
                }
                else
                {
                        struct BMHDR
                        {
                                BITMAPINFOHEADER bmInfo;
                                RGBQUAD          rgb[256];
                        } DIBHdr =
                        {
                                sizeof(BITMAPINFOHEADER),
                                cxWidth,
                                cyHeight,
                                1,
                                (WORD)cBitCount,
                                BI_RGB,
                                0,
                                0,
                                nColors,
                                nColors,
                        } ;

                        if (cBitCount <= 8)
                        {
                                RGBQUAD* prgb;
                                COLORREF* pcr;
                                int n;

                                pcr = pcrColors;
                                prgb = DIBHdr.rgb;

                                for (n=nColors; n>0; --n, ++pcr, ++prgb)
                                {
                                        prgb->rgbRed   = GetRValue(*pcr);
                                        prgb->rgbGreen = GetGValue(*pcr);
                                        prgb->rgbBlue  = GetBValue(*pcr);
                                        prgb->rgbReserved = 0;
                                }
                        }

                        LPVOID lpNewBits;
                        hbmNew = CreateDIBSection(cDC.m_hDC, (LPBITMAPINFO)&DIBHdr,
                                DIB_RGB_COLORS, &lpNewBits, NULL, 0);
                }

                if (!hbmNew)
                {
                        return NULL;
                }
                bmNew.Attach(hbmNew);
        }

        TRY
        {
                pimg = new IMG;
        }
        CATCH (CMemoryException, e)
        {
                TRACE( TEXT("CreateImg: Can't alloc an IMG\n") );
                return NULL;
        }
        END_CATCH

        pimg->cxWidth        = cxWidth;
        pimg->cyHeight       = cyHeight;
        pimg->cPlanes        = cPlanes;
        pimg->cBitCount      = cBitCount;
        pimg->cXPelsPerMeter = cXPelsPerMeter;
        pimg->cYPelsPerMeter = cYPelsPerMeter;
        pimg->hDC            = (HDC)cDC.Detach();
        pimg->hBitmap        = (HBITMAP)bmNew.Detach();
        pimg->hBitmapOld     = NULL;
        pimg->m_pFirstImgWnd = NULL;
        pimg->bDirty         = FALSE;
        pimg->m_hPalOld      = NULL;
        pimg->m_pPalette     = NULL;

        pimg->m_bTileGrid = g_bDefaultTileGrid;
        pimg->m_cxTile    = g_defaultTileGridSize.cx;
        pimg->m_cyTile    = g_defaultTileGridSize.cy;


        BYTE cRed   = GetRValue( crRight );
        BYTE cGreen = GetGValue( crRight );
        BYTE cBlue  = GetBValue( crRight );

        if (theApp.m_bPaletted)
        {
                crRight = PALETTERGB( cRed, cGreen, cBlue );
        }
        else
        {
                crRight =        RGB( cRed, cGreen, cBlue );
        }

        if (pimg->hBitmap)
        {
                pimg->hBitmapOld = (HBITMAP)SelectObject(pimg->hDC, pimg->hBitmap);
                ClearImg( pimg );
        }

        return(pimg);
}

/***************************************************************************/

BOOL ClearImg(IMG* pimg)
    {
#if 1
    HBRUSH   hNewBrush;
    HBRUSH   hOldBrush = NULL;
    HPALETTE hpalOld   = NULL;

    pimg->m_nLastChanged = -1;

    if ((hNewBrush = ::CreateSolidBrush( crRight )) == NULL)
        return FALSE;

    if (pimg->m_pPalette)
        {
        hpalOld = SelectPalette( pimg->hDC, (HPALETTE)pimg->m_pPalette->m_hObject, FALSE );
        RealizePalette( pimg->hDC );
        }

    hOldBrush = (HBRUSH)SelectObject( pimg->hDC, hNewBrush );

    PatBlt( pimg->hDC, 0, 0, pimg->cxWidth, pimg->cyHeight, PATCOPY );

    if (hOldBrush)
        SelectObject(pimg->hDC, hOldBrush);

    DeleteObject( hNewBrush );

    if (hpalOld)
        SelectPalette( pimg->hDC, hpalOld, FALSE );

    return TRUE;
#else
        BOOL    bResult = FALSE;
        HBRUSH  hNewBrush = ::CreateSolidBrush( crRight );

        if ( hNewBrush )
                {
                HBRUSH  hOldBrush = (HBRUSH)SelectObject( pimg->hDC, hNewBrush );
                if ( hOldBrush )
                        {
                        HPALETTE hpalOld = SelectPalette( pimg->hDC,
                                                                                          (HPALETTE)pimg->m_pPalette->m_hObject,
                                                                                          FALSE );
                        if ( hpalOld )
                                {
                                RealizePalette( pimg->hDC );

                            PatBlt( pimg->hDC, 0, 0, pimg->cxWidth, pimg->cyHeight, PATCOPY );
                                pimg->m_nLastChanged = -1;
                                bResult = TRUE;

                        SelectPalette( pimg->hDC, hpalOld, FALSE );
                                }
                SelectObject(pimg->hDC, hOldBrush);
                        }
                DeleteObject( hNewBrush );
                }
        return bResult;
#endif
    }

/***************************************************************************/

void FreeImg(IMG* pimg)
    {
    if (! pimg)
        return;

    if (pimg == theImgBrush.m_pImg)
        theImgBrush.m_pImg = NULL;

    if (pimg->hDC)
        {
        if (pimg->hBitmapOld)
            SelectObject( pimg->hDC, pimg->hBitmapOld );

        if (pimg->m_hPalOld)
            SelectPalette( pimg->hDC, pimg->m_hPalOld, FALSE ); // Background ??

        DeleteDC(pimg->hDC);
        }

    if (pimg->hBitmap)
        DeleteObject(pimg->hBitmap);

    if (theApp.m_pPalette == pimg->m_pPalette)
        theApp.m_pPalette = NULL;

    if (pimg->m_pPalette)
        delete pimg->m_pPalette;

    if (pimg->m_pBitmapObj->m_pImg == pimg)
        pimg->m_pBitmapObj->m_pImg = NULL;

    if (pImgCur == pimg)
        pImgCur = NULL;

    if (pRubberImg == pimg)
        pRubberImg = NULL;

    delete pimg;
    }

/***************************************************************************/

void SelectImg(IMG* pimg)
    {
    if (pimg == pImgCur)
        return;

    if (theImgBrush.m_pImg)
        HideBrush();

    pImgCur = pimg;

    SetupRubber(pimg);
    }

/***************************************************************************/

void DirtyImg( IMG* pimg )
    {
    CPBDoc* pDoc = (CPBDoc*)((CFrameWnd*)AfxGetMainWnd())->GetActiveDocument();

    if (pDoc)
        {
        pDoc->SetModifiedFlag( TRUE );

        if (theApp.m_bEmbedded)
            pDoc->NotifyChanged();
        }

    pimg->bDirty = TRUE;
    pimg->m_pBitmapObj->SetDirty( TRUE );
    }

/***************************************************************************/

void CleanupImages()
    {
    FreeImg( pImgCur );

    CleanupImgUndo();
    CleanupImgRubber();
    }

/***************************************************************************/

void InvalImgRect(IMG* pimg, CRect* prc)
    {
    CImgWnd* pImgWnd;
    CImgWnd* pNextImgWnd;

    CRect rc;

    rc.SetRect(0, 0, pimg->cxWidth, pimg->cyHeight);

    if (prc)
        rc &= *prc;

    for (pImgWnd = pimg->m_pFirstImgWnd; pImgWnd;
                                         pImgWnd = pNextImgWnd)
        {
        CRect rcWnd;

        pNextImgWnd = pImgWnd->m_pNextImgWnd;

        if (prc)
            {
            rcWnd = rc;
            pImgWnd->ImageToClient(rcWnd);

            if (pImgWnd->IsGridVisible())
                {
                rcWnd.right  += 1;
                rcWnd.bottom += 1;
                }
            }

        pImgWnd->InvalidateRect(prc == NULL ? NULL : &rcWnd, FALSE);
        }
    }

/***************************************************************************/

void CommitImgRect(IMG* pimg, CRect* prc)
    {
    ASSERT(hRubberDC);

    if (hRubberDC == NULL)
        return;

    CRect rc;

    if (prc == NULL)
        {
        SetRect( &rc, 0, 0, pimg->cxWidth, pimg->cyHeight );
        prc = &rc;
        }

    HPALETTE hpalOld = NULL;

    if (theApp.m_pPalette
    &&  theApp.m_pPalette->m_hObject)
        {
        hpalOld = SelectPalette( hRubberDC, (HPALETTE)theApp.m_pPalette->m_hObject, FALSE ); // Background ??
        RealizePalette( hRubberDC );
        }

    BitBlt(hRubberDC, prc->left, prc->top,
                      prc->Width(), prc->Height(),
           pimg->hDC, prc->left, prc->top, SRCCOPY);

    if (hpalOld)
        SelectPalette( hRubberDC, hpalOld, FALSE ); // Background ??
    }

void SelInScreenFirst(CPalette* pPal)
{
        // HACK: Select into screen DC first for GDI bug
        CWindowDC hdcScreen(NULL);
        hdcScreen.SelectPalette(pPal, TRUE);
        hdcScreen.RealizePalette();
}

BOOL CreateSafePalette(CPalette* pPal, LPLOGPALETTE lpLogPal)
{
        if (!pPal->CreatePalette(lpLogPal))
        {
                return(FALSE);
        }

        SelInScreenFirst(pPal);
        return(TRUE);
}


/***************************************************************************/

BOOL ReplaceImgPalette( IMG* pImg, LPLOGPALETTE lpLogPal )
    {
    if (pImg->m_hPalOld)
        {
        ::SelectPalette( pImg->hDC, pImg->m_hPalOld, FALSE );
        pImg->m_hPalOld = NULL;
        }

    if (pImg->m_pPalette)
        delete pImg->m_pPalette;

    pImg->m_pPalette = new CPalette;

    if (pImg->m_pPalette
    &&  CreateSafePalette(pImg->m_pPalette, lpLogPal ))
        {
        pImg->m_hPalOld = ::SelectPalette( pImg->hDC,
                                 (HPALETTE)pImg->m_pPalette->GetSafeHandle(), FALSE );
        ::RealizePalette( pImg->hDC );
        InvalImgRect( pImg, NULL );
        }
    else
        {
        if (pImg->m_pPalette)
            delete pImg->m_pPalette;

        pImg->m_pPalette = NULL;
        }

    return (pImg->m_pPalette != NULL);
    }

/***************************************************************************/

void CleanupImgRubber()
    {
    if (hRubberDC)
        {
        DeleteDC(hRubberDC);
        hRubberDC = NULL;
        }

    if (hRubberBM)
        {
        DeleteObject(hRubberBM);
        hRubberBM = NULL;
        }

    pRubberImg = NULL;

    cxRubberWidth  = 0;
    cyRubberHeight = 0;
    }

/***************************************************************************/

void IdleImage()
    {
    if (g_pMouseImgWnd)
        {
        CRect rcImage(0, 0, g_pMouseImgWnd->GetImg()->cxWidth,
                            g_pMouseImgWnd->GetImg()->cyHeight);

        g_pMouseImgWnd->ImageToClient( rcImage );

        CRect rcClient;

        g_pMouseImgWnd->GetClientRect( &rcClient );

        rcClient &= rcImage;

        CPoint pt;
        GetCursorPos( &pt );

        CPoint ptClient = pt;

        g_pMouseImgWnd->ScreenToClient( &ptClient );

        if (CWnd::WindowFromPoint( pt ) != g_pMouseImgWnd
        ||  ! rcClient.PtInRect( ptClient ))
            {
            extern MTI  mti;

            CImgTool::GetCurrent()->OnLeave( g_pMouseImgWnd, &mti );

            g_pMouseImgWnd = NULL;

            if (! CImgTool::IsDragging() &&
                ::IsWindow(((CPBFrame*)theApp.m_pMainWnd)->m_statBar.m_hWnd) )
                ((CPBFrame*)theApp.m_pMainWnd)->m_statBar.ClearPosition();
            }
        }

    if (fDraggingBrush)
        {
        CPoint   pt;
        CRect    rcClient;
        CPoint   ptClient;
        CImgWnd* pImgWnd;

        GetCursorPos(&pt);

        pImgWnd = g_pDragBrushWnd;

        if (pImgWnd == NULL)
            return;

        CRect rcImage(0, 0, pImgWnd->GetImg()->cxWidth,
                            pImgWnd->GetImg()->cyHeight);

        pImgWnd->ImageToClient( rcImage   );
        pImgWnd->GetClientRect( &rcClient );

        rcClient &= rcImage;

        ptClient = pt;
        pImgWnd->ScreenToClient( &ptClient );

        if ( CWnd::WindowFromPoint(pt) != pImgWnd
        ||  ! rcClient.PtInRect(ptClient))
            {
            if (fDraggingBrush && theImgBrush.m_pImg == NULL)
                HideBrush();

            pImgWnd->UpdPos( CPoint(-1, -1) );
            }
        else
            if (GetCapture() == NULL)
                {
                CPoint imagePt = ptClient;

                pImgWnd->ClientToImage( imagePt );

                if (! g_bBrushVisible && fDraggingBrush
                &&    CImgTool::GetCurrent()->UsesBrush())
                    {
                    pImgWnd->ShowBrush( imagePt );
                    }
                }
        }
    }

/***************************************************************************/

void HideBrush()
    {
    if (! g_bBrushVisible)
        return;

    g_bBrushVisible = FALSE;

    CImgWnd* pImgWnd = g_pDragBrushWnd;

    ASSERT(pImgWnd);

    if (pImgWnd == NULL)
        return;

    IMG* pimg = pImgWnd->GetImg();

    if (pimg == NULL)
        return;

    HPALETTE hpalOld = pImgWnd->SetImgPalette( hRubberDC, FALSE ); // Background ??

    BitBlt( pimg->hDC, rcDragBrush.left,
                       rcDragBrush.top,
                       rcDragBrush.Width(),
                       rcDragBrush.Height(),
            hRubberDC, rcDragBrush.left,
                       rcDragBrush.top, SRCCOPY );

    if (hpalOld)
        SelectPalette( hRubberDC, hpalOld, FALSE ); // Background ??

    InvalImgRect( pimg, &rcDragBrush );
    }

/***************************************************************************/

void FixRect(RECT * prc)
    {
    int t;

    if (prc->left > prc->right)
        {
        t = prc->left;
        prc->left = prc->right;
        prc->right = t;
        }

    if (prc->top > prc->bottom)
        {
        t = prc->top;
        prc->top = prc->bottom;
        prc->bottom = t;
        }
    }

/***************************************************************************/

BOOL SetupRubber(IMG* pimg)
    {
    if (cxRubberWidth  < pimg->cxWidth
    ||  cyRubberHeight < pimg->cyHeight)
        {
        HBITMAP hOldBitmap, hNewBitmap;

        HideBrush();

        if (hRubberDC == NULL
        && (hRubberDC = CreateCompatibleDC( pimg->hDC )) == NULL)
            return FALSE;

        hNewBitmap = CreateCompatibleBitmap( pimg->hDC, pimg->cxWidth, pimg->cyHeight );

        if (hNewBitmap == NULL)
            {
            return FALSE;
            }

        hRubberBM = hNewBitmap;

        hOldBitmap = (HBITMAP) SelectObject( hRubberDC, hRubberBM );

        if (hOldBitmap)
            {
            DeleteObject( hOldBitmap );
            }

        cxRubberWidth  = pimg->cxWidth;
        cyRubberHeight = pimg->cyHeight;
        }

    if (pRubberImg != pimg)
        {
        HideBrush();

        HPALETTE hpalOld = NULL;

        if (theApp.m_pPalette
        &&  theApp.m_pPalette->m_hObject)
            {
            hpalOld = SelectPalette( hRubberDC, (HPALETTE)theApp.m_pPalette->m_hObject, FALSE );
            RealizePalette( hRubberDC );
            }

        BitBlt( hRubberDC, 0, 0, pimg->cxWidth,
                                 pimg->cyHeight, pimg->hDC, 0, 0, SRCCOPY );

        if (hpalOld)
            SelectPalette( hRubberDC, hpalOld, FALSE ); // Background ??
        }

    pRubberImg = pimg;

    return TRUE;
    }

/***************************************************************************/

CPalette* CreatePalette( const COLORREF* colors, int nColors )
    {
    CPalette* pPal = new CPalette;

    if (pPal)
        {
        LPLOGPALETTE pLogPal = (LPLOGPALETTE) LocalAlloc(
            LMEM_FIXED, sizeof (LOGPALETTE) + sizeof (PALETTEENTRY) * nColors);

        if (pLogPal)
            {
            pLogPal->palVersion    = 0x300;
            pLogPal->palNumEntries = (WORD)nColors;

            for (int i = 0; i < nColors; i++)
                {
                pLogPal->palPalEntry[i] = *(PALETTEENTRY*)colors++;
                pLogPal->palPalEntry[i].peFlags = 0;
                }

            if (! CreateSafePalette(pPal, pLogPal ))
                {
                theApp.SetGdiEmergency();
                delete pPal;
                pPal = NULL;
                }

            LocalFree(pLogPal);
            }
        else
            {
            theApp.SetMemoryEmergency();
            delete pPal;
            pPal = NULL;
            }
        }
    return pPal;
    }

/***************************************************************************/

CPalette* GetStd16Palette()
    {
    return CreatePalette( std16Colors, 16 );
    }

/***************************************************************************/

CPalette* GetStd2Palette()
    {
    return CreatePalette( std2Colors, 2 );
    }

/***************************************************************************/

CPalette* GetStd256Palette()
    {
    return CreatePalette( colorColorsDef, NUM_DEF_COLORS );
    }

/***************************************************************************/

CPalette *PaletteFromDS(HDC hdc)
{
    DWORD adw[257];
    int i,n;
        CPalette        *pPal = new CPalette;

    if ( n = GetDIBColorTable(hdc, 0, 256, (LPRGBQUAD)&adw[1]) )
                {
            for (i=1; i<=n; i++)
                adw[i] = RGB(GetBValue(adw[i]),GetGValue(adw[i]),GetRValue(adw[i]));

            adw[0] = MAKELONG(0x300, n);

                CreateSafePalette(pPal, (LPLOGPALETTE)&adw[0]);
                }
        else
                {
                // No Palette in Bitmap! Use default half-tone palette
                pPal->Attach(CreateHalftonePalette( NULL ));
                }

        return pPal;
}

/////////////////////////////////////////////////////////////////////////////
//
// Packed-DIB Handling Functions
//
// A packed-DIB is a bucket of bits usually consisting of a BITMAPINFOHEADER
// structure followed by an array of RGBQUAD structures followed by the words
// that make up the image.  An alternate form consists of a BITMAPCOREHEADER
// structure followed by an array of RGBTRIPLE structures and the image words.
// This format is used by OS/2, but is supported by Windows.  The only way
// to tell which format the DIB is using is to check the first word against
// the sizes of the header structures (pretty clever eh?).
//
// This is very similar to a DIB as stored in a file.  In fact, a DIB file is
// a BITMAPFILEHEADER structure followed by a packed DIB.
//
// These functions make dealing with packed-DIBs in memory easier.
//
#define WIDTHBYTES(bits) ((((bits) + 31) / 32) * 4)

/***************************************************************************/

void FreeDib(HGLOBAL hDib)
    {
    ASSERT( hDib );

    if (hDib)
        GlobalFree(hDib);
    }

/***************************************************************************/

HGLOBAL DibFromBitmap( HBITMAP hBitmap, DWORD dwStyle, WORD wBits,
                       CPalette* pPal, HBITMAP hMaskBitmap, DWORD& dwLen, 
                       LONG cXPelsPerMeter, LONG cYPelsPerMeter )
    {
    ASSERT(hBitmap);

    if (hBitmap == NULL)
        return NULL;

     ASSERT(hMaskBitmap == NULL || dwStyle == BI_RGB);

    HBITMAP            hbm;
    BITMAP             bm;
    BITMAPINFOHEADER   bi;
    LPBITMAPINFOHEADER lpbi;
    HDC                hDC;
    DWORD              dwSmallLen;


    HPALETTE hPal = (HPALETTE)(pPal->GetSafeHandle());

    if (theApp.m_bPaletted && ! hPal)
        hPal = (HPALETTE)::GetStockObject( DEFAULT_PALETTE );

    GetObject( hBitmap, sizeof( bm ), (LPSTR)&bm );

    if (wBits == 0)
        wBits = bm.bmPlanes * bm.bmBitsPixel;

    if (wBits <= 1)
        wBits = 1;
    else
        if (wBits <= 4)
            wBits = 4;
        else
            if (wBits <= 8)
                wBits = 8;
            else
                wBits = 24;

    bi.biSize          = sizeof( BITMAPINFOHEADER );
    bi.biWidth         = bm.bmWidth;
    bi.biHeight        = bm.bmHeight;
    bi.biPlanes        = 1;
    bi.biBitCount      = wBits;
    bi.biCompression   = dwStyle;
    bi.biSizeImage     = 0;
//  bi.biXPelsPerMeter = theApp.ScreenDeviceInfo.ixPelsPerDM * 10;
//  bi.biYPelsPerMeter = theApp.ScreenDeviceInfo.iyPelsPerDM * 10;
//    HDC hdc = GetDC(NULL);
//    bi.biXPelsPerMeter = MulDiv(::GetDeviceCaps(hdc, LOGPIXELSX),10000, 254);
//    bi.biYPelsPerMeter = MulDiv(::GetDeviceCaps(hdc, LOGPIXELSY),10000, 254);
//    ReleaseDC (NULL, hdc);
    bi.biXPelsPerMeter = cXPelsPerMeter;
    bi.biYPelsPerMeter = cYPelsPerMeter;
    bi.biClrUsed       = 0;
    bi.biClrImportant  = 0;

    dwSmallLen = dwLen = bi.biSize + PaletteSize( (LPSTR) &bi );

    lpbi = (LPBITMAPINFOHEADER) GlobalAlloc(GPTR, dwLen);

    if (lpbi == NULL)
        {
        theApp.SetMemoryEmergency();
        return NULL;
        }

    *lpbi = bi;

    hbm = CreateBitmap( 2, 2, bm.bmPlanes, bm.bmBitsPixel, NULL );
    hDC = CreateCompatibleDC( NULL );

    if (hbm == NULL || hDC == NULL)
        {
        if (hbm)
            DeleteObject( hbm );

        theApp.SetGdiEmergency();
        return NULL;
        }
    HPALETTE hPalOld = NULL;
    HANDLE   hbmOld  = SelectObject( hDC, hbm );

    if (hPal)
        {
        hPalOld = SelectPalette( hDC, hPal, FALSE );
        RealizePalette( hDC );
        }

    // Compute the byte size of the DIB...
    GetDIBits( hDC, hBitmap, 0, (WORD)bi.biHeight, NULL, (LPBITMAPINFO)lpbi, DIB_RGB_COLORS );

    bi = *lpbi;

    // If the driver did not fill in the biSizeImage field, make one up
    // NOTE: This size will be too big if the bitmap is compressed!
    // NOTE: This happens with the Paradise 800x600x256 driver...
    if (bi.biSizeImage == 0)
        {
        TRACE( TEXT("Display driver bug!  We have to compute DIB size...") );

        bi.biSizeImage = WIDTHBYTES( (DWORD)bi.biWidth * wBits ) * bi.biHeight;

        if (dwStyle != BI_RGB)
            bi.biSizeImage = (bi.biSizeImage * 3) / 2;
        }

    dwLen = bi.biSize + PaletteSize( (LPSTR)&bi ) + bi.biSizeImage;

    if (hMaskBitmap)
        dwLen += (LONG)WIDTHBYTES( bi.biWidth ) * bi.biHeight;

    CHAR* hpv = (CHAR*) GlobalAlloc(GPTR, dwLen);

    if (! hpv)
        {
        theApp.SetMemoryEmergency();

        GlobalFree(lpbi);

        if (hbmOld)
            DeleteObject( SelectObject( hDC, hbmOld ) );

        if (hPalOld)
            SelectPalette( hDC, hPalOld, FALSE );

        DeleteDC( hDC );

        return NULL;
        }

    memcpy( hpv, (void*)lpbi, dwSmallLen );

    GlobalFree(lpbi);

    lpbi = (LPBITMAPINFOHEADER)hpv;

    LPSTR lpBits = (LPSTR)lpbi + lpbi->biSize + PaletteSize((LPSTR)lpbi);
    DWORD biSizeImage = lpbi->biSizeImage;

    if (hMaskBitmap)
        {
        // Do the mask first so the dib ends up with the main bitmap's
        // size and palette when we're done...
        LONG cbAdjust     = ((LONG)WIDTHBYTES( bi.biWidth * wBits )) * bi.biHeight;
              lpBits     += cbAdjust;
        WORD biBitCount   = lpbi->biBitCount;
        lpbi->biBitCount  = 1;
        lpbi->biSizeImage = 0;

        if (GetDIBits( hDC, hMaskBitmap, 0, (WORD)bi.biHeight, lpBits,
                            (LPBITMAPINFO)lpbi, DIB_RGB_COLORS ) == 0)
            {
            GlobalFree(hpv);

            if (hbmOld)
                DeleteObject( SelectObject( hDC, hbmOld ) );

            if (hPalOld)
                SelectPalette( hDC, hPalOld, FALSE );

            DeleteDC(hDC);

            return NULL;
            }

        biSizeImage     += lpbi->biSizeImage;
        lpbi->biBitCount = biBitCount;
        lpBits          -= cbAdjust;
        }

    if (GetDIBits( hDC, hBitmap, 0, (WORD)bi.biHeight, lpBits,
                    (LPBITMAPINFO)lpbi, DIB_RGB_COLORS ) == 0)
        {
        GlobalFree(hpv);

        if (hbmOld)
            DeleteObject( SelectObject( hDC, hbmOld ) );

        if (hPalOld)
            SelectPalette( hDC, hPalOld, FALSE );

        DeleteDC(hDC);

        return NULL;
        }

    lpbi->biSizeImage = biSizeImage;

    if (hMaskBitmap)
        lpbi->biHeight *= 2;

    if (hbmOld)
        DeleteObject( SelectObject( hDC, hbmOld ) );

    if (hPalOld)
        SelectPalette( hDC, hPalOld, FALSE );
    DeleteDC( hDC );

    return (LPSTR)lpbi;
    }

/***************************************************************************/

UINT DIBBitsPixel(LPSTR lpbi)
{
        // Calculate the number of colors in the color table based on
        //  the number of bits per pixel for the DIB.

        if (IS_WIN30_DIB(lpbi))
        {
                return(((LPBITMAPINFOHEADER)lpbi)->biBitCount);
        }
        else
        {
                return(((LPBITMAPCOREHEADER)lpbi)->bcBitCount);
        }
}

WORD DIBNumColors(LPSTR lpbi, BOOL bJustUsed)
    {
    WORD wBitCount;

    // If this is a Windows style DIB, the number of colors in the
    //  color table can be less than the number of bits per pixel
    //  allows for (i.e. lpbi->biClrUsed can be set to some value).
    //  If this is the case, return the appropriate value.

    if (IS_WIN30_DIB( lpbi ) && bJustUsed)
        {
        DWORD dwClrUsed = ((LPBITMAPINFOHEADER)lpbi)->biClrUsed;

        if (dwClrUsed != 0)
            return (WORD)dwClrUsed;
        }


    // Calculate the number of colors in the color table based on
    //  the number of bits per pixel for the DIB.

    wBitCount = (WORD)DIBBitsPixel(lpbi);

    switch (wBitCount)
        {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
        }
    }


/***************************************************************************/

WORD PaletteSize(LPSTR lpbi)
    {


    if (IS_WIN30_DIB(lpbi) &&
                ((LPBITMAPINFOHEADER)lpbi)->biCompression==BI_BITFIELDS)
    {
            // Images with bitfields have 3 DWORD's that specify the RGB components
            // (respectively) of each pixel.
            if (((LPBITMAPINFOHEADER)lpbi)->biSize >= sizeof(BITMAPV4HEADER))
            {
               return 0;
            }
            else
               return(3 * sizeof(DWORD));
    }

    return DIBNumColors(lpbi,TRUE) *
        (IS_WIN30_DIB(lpbi) ? sizeof(RGBQUAD) : sizeof(RGBTRIPLE));
    }


/***************************************************************************/

LPSTR FindDIBBits(LPSTR lpbi, DWORD dwOffBits)
    {
    DWORD dwAfterHdr = *(LPDWORD)lpbi + PaletteSize(lpbi);
    DWORD dwOff;
#if 0
        if (dwOffBits && dwAfterHdr != dwOffBits)
        {
                MessageBeep(0);
        }
#endif
    dwOff = max(dwOffBits, dwAfterHdr);
    return(lpbi + dwOff);
    }


/***************************************************************************/

DWORD DIBWidth(LPSTR lpDIB)
    {

    LPBITMAPINFOHEADER lpbmi = (LPBITMAPINFOHEADER)lpDIB;
    LPBITMAPCOREHEADER lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    if (lpbmi->biSize >= sizeof (BITMAPINFOHEADER))
        return (DWORD) abs(lpbmi->biWidth);
    else
        return (DWORD) abs(lpbmc->bcWidth);
    }


/***************************************************************************/

DWORD DIBHeight(LPSTR lpDIB)
    {
    LPBITMAPINFOHEADER lpbmi = (LPBITMAPINFOHEADER)lpDIB;
    LPBITMAPCOREHEADER lpbmc = (LPBITMAPCOREHEADER)lpDIB;

    if (lpbmi->biSize >= sizeof (BITMAPINFOHEADER))
        return (DWORD) abs(lpbmi->biHeight);
    else
        return (DWORD) abs(lpbmc->bcHeight);
    }


/***************************************************************************/

HBITMAP DIBToBitmap( LPSTR lpDIBHdr, CPalette* pPal, HDC hdc )
    {
    ASSERT( lpDIBHdr );
    ASSERT( hdc );

    if (! lpDIBHdr || ! hdc)
        return NULL;

    LPBYTE    lpDIBBits = (LPBYTE)FindDIBBits( lpDIBHdr,0 );
    CPalette* ppalOld = NULL;
    CBitmap*  pbmOld  = NULL;
    CBitmap   bmTemp;
    CDC       dc;

    dc.Attach( hdc );

    if (bmTemp.CreateCompatibleBitmap( &dc, 2, 2 ))
        pbmOld = dc.SelectObject( &bmTemp );

    if (pPal)
        {
        ASSERT( pPal->m_hObject );

#ifdef FORCEBACKPALETTE
        ppalOld = dc.SelectPalette( pPal, TRUE );
#else
        ppalOld = dc.SelectPalette( pPal, FALSE );
#endif

        dc.RealizePalette();
        }

    HBITMAP hBitmap = CreateDIBitmap( dc.m_hDC, (LPBITMAPINFOHEADER)lpDIBHdr,
                                      CBM_INIT, lpDIBBits,
                                      (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS );
    if (ppalOld)
        dc.SelectPalette( ppalOld, FALSE );

    if (pbmOld)
        dc.SelectObject( pbmOld );

    if (bmTemp.m_hObject)
        bmTemp.DeleteObject();

    dc.Detach();

    return hBitmap;
    }

/***************************************************************************/

BOOL ShouldUseDDB(HDC hdc, LPBITMAPINFO lpDIBHdr)
{
        if (!IS_WIN30_DIB(lpDIBHdr))
        {
                // I don't want to write special code to deal with this case
                return(FALSE);
        }

        if (lpDIBHdr->bmiHeader.biPlanes*lpDIBHdr->bmiHeader.biBitCount != 4)
        {
                // No DDB for mono or 8bit or more
                return(FALSE);
        }

        UINT cBitCount = GetDeviceCaps( hdc, BITSPIXEL )
                * GetDeviceCaps( hdc, PLANES );
        if (cBitCount > 4)
        {
                return(FALSE);
        }

        RGBQUAD *lpPal = lpDIBHdr->bmiColors;

        for (int i=DIBNumColors((LPSTR)lpDIBHdr, TRUE); i>0; --i, ++lpPal)
        {
                COLORREF cr = RGB(lpPal->rgbRed, lpPal->rgbGreen, lpPal->rgbBlue);
                if (GetNearestColor(hdc, cr) != cr)
                {
                        return(FALSE);
                }
        }

        // OK, so this is a WIN30 DIB, the screen is 16 or less colors, it is
        // either an uncompressed or RLE DIB, and all the colors in the DIB can
        // be shown on the screen.  I guess we can use a DDB.
        return(TRUE);
}

/***************************************************************************/


HBITMAP DIBToDS( LPSTR lpDIB, DWORD dwOffBits, HDC hdc )
{
        ASSERT( lpDIB );
        ASSERT( hdc );

        if (! lpDIB || ! hdc)
        {
                return NULL;
        }

        LPVOID lpNewBits;
        LPBITMAPINFO lpDIBHdr = (LPBITMAPINFO)lpDIB;
        LPBYTE lpDIBBits = (LPBYTE)FindDIBBits( lpDIB, dwOffBits );

        {
                // New block just to scope dcScreen
                CClientDC dcScreen(NULL);

                if (ShouldUseDDB(dcScreen.m_hDC, lpDIBHdr))
                {
                        return(CreateDIBitmap( dcScreen.m_hDC, &lpDIBHdr->bmiHeader,
                                CBM_INIT, lpDIBBits, lpDIBHdr, DIB_RGB_COLORS ));
                }
        }

        // Compressed DIB sections are not allowed
        DWORD dwCompression = lpDIBHdr->bmiHeader.biCompression;
        if (IS_WIN30_DIB(lpDIB))
        {
                lpDIBHdr->bmiHeader.biCompression = BI_RGB;
        }
        HBITMAP hBitmap = CreateDIBSection( hdc, lpDIBHdr, DIB_RGB_COLORS,
                &lpNewBits, NULL, 0);

        if (IS_WIN30_DIB(lpDIB))
        {
                lpDIBHdr->bmiHeader.biCompression = dwCompression;
        }

        if (hBitmap)
        {
                HBITMAP hbmOld = (HBITMAP)SelectObject(hdc, hBitmap);
                if (hbmOld)
                {
                        UINT uWid = DIBWidth(lpDIB);
                        UINT uHgt = DIBHeight(lpDIB);

                        // Fill with white in case the bitmap has any jumps in it.
                        PatBlt(hdc, 0, 0, uWid, uHgt, WHITENESS);

//                        StretchDIBits(hdc, 0, 0, uWid, uHgt, 0, 0, uWid, uHgt, lpDIBBits,
//                                (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS, SRCCOPY);
                        SetDIBitsToDevice (hdc,0,0,uWid, uHgt,0,0,0,abs(uHgt),lpDIBBits,
                                           (LPBITMAPINFO)lpDIBHdr, DIB_RGB_COLORS);
                        SelectObject(hdc, hbmOld);

                        return(hBitmap);
                }

                DeleteObject(hBitmap);
        }

        return(NULL);
}

//------------------------------------------------------------------------------
// SetNewPalette - Used solely by GetRainbowPalette below for initialization.
//------------------------------------------------------------------------------

static void SetNewPalette( PALETTEENTRY* const pPal, PWORD pwRainbowColors,
                                                     UINT R, UINT G, UINT B )
    {
    if (*pwRainbowColors < 256)
        {
        WORD wC;

        for (wC = 0;  wC < *pwRainbowColors;  wC++)
            if (((UINT)GetRValue( *(DWORD*)&pPal[ wC ] ) /* + 1 & ~1 */ ) == (R /* + 1 & ~1 */ )
            &&  ((UINT)GetGValue( *(DWORD*)&pPal[ wC ] ) /* + 1 & ~1 */ ) == (G /* + 1 & ~1 */ )
            &&  ((UINT)GetBValue( *(DWORD*)&pPal[ wC ] ) /* + 1 & ~1 */ ) == (B /* + 1 & ~1 */ ))
                return;

        pPal[*pwRainbowColors].peRed   = (BYTE)R;
        pPal[*pwRainbowColors].peGreen = (BYTE)G;
        pPal[*pwRainbowColors].peBlue  = (BYTE)B;
        pPal[*pwRainbowColors].peFlags = 0;

        (*pwRainbowColors)++;
        }
    }

/***************************************************************************/

CPalette* CreateDIBPalette(LPSTR lpbi)
    {
    LPLOGPALETTE     lpPal;
    CPalette*        pPal = NULL;
    int              iLoop;
    int              wNumColors;
    LPBITMAPINFO     lpbmi;
    LPBITMAPCOREINFO lpbmc;
    BOOL             bWinStyleDIB;
    BOOL             bGetDriverDefaults = FALSE;

    ASSERT( lpbi );

    if (lpbi == NULL)
        return NULL;

    lpbmi = (LPBITMAPINFO)lpbi;
    lpbmc = (LPBITMAPCOREINFO)lpbi;
    wNumColors = (int)DIBNumColors(lpbi, TRUE);
    bWinStyleDIB = IS_WIN30_DIB(lpbi);

    if (! wNumColors)
        {
        if (! theApp.m_bPaletted)
            return NULL;

        bGetDriverDefaults = TRUE;
        wNumColors = 256;
        }

    lpPal = (LPLOGPALETTE)new CHAR [sizeof( LOGPALETTE ) + sizeof( PALETTEENTRY ) * (wNumColors - 1)];

    if (lpPal == NULL)
        {
        theApp.SetMemoryEmergency();
        return NULL;
        }

    if (bGetDriverDefaults)
        {
        #define DIM( X ) (sizeof(X) / sizeof(X[0]))

        // GetRainbowPalette - Based on
        // Fran Finnegan's column in Microsoft Systems Journal, Sept.-Oct., 1991 (#5).
        static BYTE C[] = { 255, 238, 221,
                            204, 187, 170,
                            153, 136, 119,
                            102,  85,  68,
                             51,  34,  17,
                              0
                          };
        PALETTEENTRY* pPal = &(lpPal->palPalEntry[0]);
        WORD wColors = 0;
        int iC;
        int iR;
        int iG;
        int iB;

        for (iC = 0;  iC < DIM( C );  iC++)
            SetNewPalette( pPal, &wColors, C[ iC ], C[ iC ], C[ iC ] );
        for (iR = 0;  iR < DIM( C );  iR += 3)
        for (iG = 0;  iG < DIM( C );  iG += 3)
        for (iB = 0;  iB < DIM( C );  iB += 3)
            SetNewPalette( pPal, &wColors, C[ iR ], C[ iG ], C[ iB ] );
        for (iC = 0;  iC < DIM( C );  iC++)
            {
            SetNewPalette( pPal, &wColors, C[ iC ],       0,       0 );
            SetNewPalette( pPal, &wColors,       0, C[ iC ],       0 );
            SetNewPalette( pPal, &wColors,       0,       0, C[ iC ] );
            }
        }
    else
        for (iLoop = 0; iLoop < wNumColors; iLoop++)
            {
            if (bWinStyleDIB)
                {
                lpPal->palPalEntry[iLoop].peRed   = lpbmi->bmiColors[iLoop].rgbRed;
                lpPal->palPalEntry[iLoop].peGreen = lpbmi->bmiColors[iLoop].rgbGreen;
                lpPal->palPalEntry[iLoop].peBlue  = lpbmi->bmiColors[iLoop].rgbBlue;
                lpPal->palPalEntry[iLoop].peFlags = 0;
                }
            else
                {
                lpPal->palPalEntry[iLoop].peRed   = lpbmc->bmciColors[iLoop].rgbtRed;
                lpPal->palPalEntry[iLoop].peGreen = lpbmc->bmciColors[iLoop].rgbtGreen;
                lpPal->palPalEntry[iLoop].peBlue  = lpbmc->bmciColors[iLoop].rgbtBlue;
                lpPal->palPalEntry[iLoop].peFlags = 0;
                }
            }
    lpPal->palVersion = 0x300;
    lpPal->palNumEntries = (WORD)wNumColors;

    pPal = new CPalette;

    if (pPal == NULL || ! CreateSafePalette(pPal, lpPal ))
        {
        if (pPal)
            delete pPal;
        pPal = NULL;
        }

    delete [] (CHAR*)lpPal;

    return pPal;
    }

/***************************************************************************/

void Draw3dRect(HDC hDC, RECT * prc)
    {
    CDC* pDC = CDC::FromHandle(hDC);
    CBrush* pOldBrush = pDC->SelectObject( GetSysBrush( COLOR_BTNSHADOW ) );

    pDC->PatBlt(prc->left, prc->top, prc->right - prc->left - 1, 1, PATCOPY);
    pDC->PatBlt(prc->left, prc->top + 1, 1, prc->bottom - prc->top - 2, PATCOPY);

    pDC->SelectObject(GetSysBrush( COLOR_BTNHIGHLIGHT ));

    pDC->PatBlt(prc->left + 1, prc->bottom - 1,
                               prc->right - prc->left - 1, 1, PATCOPY);
    pDC->PatBlt(prc->right - 1, prc->top + 1,
                             1, prc->bottom - prc->top - 1, PATCOPY);

    pDC->SelectObject(pOldBrush);
    }

/***************************************************************************/
// DrawBitmap:
// See header file for usage.

void DrawBitmap(CDC* dc, CBitmap* bmSrc, CRect* rc,
    DWORD dwROP /* = SRCCOPY */, CDC* memdc /* = NULL */)
    {
    CBitmap* obm;
    CDC* odc = (memdc? memdc : (new CDC));

    if (!memdc)
        odc->CreateCompatibleDC(dc);

    obm = odc->SelectObject(bmSrc);

    BITMAP bms;
    bmSrc->GetObject(sizeof (BITMAP), (LPSTR)(LPBITMAP)(&bms));

    if (rc)
        {
        dc->BitBlt(rc->left + ((rc->right - rc->left - bms.bmWidth) >> 1),
            rc->top + ((rc->bottom - rc->top - bms.bmHeight) >> 1),
            bms.bmWidth, bms.bmHeight, odc, 0, 0, dwROP);
        }
    else
        {
        dc->BitBlt(0, 0, bms.bmWidth, bms.bmHeight, odc, 0, 0, dwROP);
        }

    odc->SelectObject(obm);
    if (!memdc)
        {
        odc->DeleteDC();
        delete odc;
        }
    }

/***************************************************************************/

BOOL EnsureUndoSize(IMG* pimg)
    {
    if (cxUndoWidth < pimg->cxWidth || cyUndoHeight < pimg->cyHeight ||
            (int)cUndoPlanes != pimg->cPlanes ||
            (int)cUndoBitCount != pimg->cBitCount)
        {
        HBITMAP  hNewUndoImgBitmap  = NULL;
 //       HBITMAP  hNewUndoMaskBitmap = NULL;
        HPALETTE hNewUndoPalette    = NULL;

        hNewUndoImgBitmap = CreateCompatibleBitmap( pimg->hDC, pimg->cxWidth, pimg->cyHeight );

        if (hNewUndoImgBitmap == NULL)
            {
            TRACE(TEXT("EnsureUndoSize: Create image bitmap failed!\n"));
            return FALSE;
            }

//        hNewUndoMaskBitmap = CreateBitmap( pimg->cxWidth, pimg->cyHeight, 1, 1, NULL );

//        if (hNewUndoMaskBitmap == NULL)
 //           {
//            TRACE(TEXT("EnsureUndoSize: Create mask bitmap failed!\n"));

//            DeleteObject( hNewUndoImgBitmap );

 //           return FALSE;
   //         }

//      if (theApp.m_pPalette)
//          {
//          LOGPALETTE256 logPal;

//          logPal.palVersion    = 0x300;
//          logPal.palNumEntries = theApp.m_pPalette->GetPaletteEntries( 0, 256,
//                                                                &logPal.palPalEntry[0] );
//          theApp.m_pPalette->GetPaletteEntries( 0, logPal.palNumEntries,
//                                                                &logPal.palPalEntry[0] );
//          hNewUndoPalette = ::CreatePalette( (LPLOGPALETTE)&logPal );

//          if (! hNewUndoPalette)
//              {
//              TRACE("EnsureUndoSize: Create palette bitmap failed!\n");

//              DeleteObject( hNewUndoImgBitmap  );
//              DeleteObject( hNewUndoMaskBitmap );

//              return FALSE;
//              }
//          }

        if (g_hUndoImgBitmap)
            DeleteObject(g_hUndoImgBitmap);

//      if (g_hUndoPalette)
//          DeleteObject(g_hUndoPalette);

        g_hUndoImgBitmap = hNewUndoImgBitmap;
//      g_hUndoPalette   = hNewUndoPalette;
        cxUndoWidth      = pimg->cxWidth;
        cyUndoHeight     = pimg->cyHeight;
        cUndoPlanes      = (BYTE)pimg->cPlanes;
        cUndoBitCount    = (BYTE)pimg->cBitCount;
        }

    return TRUE;
    }

/***************************************************************************/

void CleanupImgUndo()
    {
    if (g_hUndoImgBitmap)
        DeleteObject(g_hUndoImgBitmap);

    if (g_hUndoPalette)
        DeleteObject(g_hUndoPalette);

    g_hUndoImgBitmap = NULL;
    g_hUndoPalette   = NULL;
    cxUndoWidth      = 0;
    cyUndoHeight     = 0;
    cUndoPlanes      = 0;
    cUndoBitCount    = 0;
    }

/***************************************************************************/

BOOL SetUndo(IMG* pimg)
    {
    BOOL    bSuccess   = FALSE;
    HDC     hTempDC    = NULL;
    HBITMAP hOldBitmap = NULL;
    CRect   rect;

    if (! EnsureUndoSize( pimg ))
        goto LReturn;

    rect.SetRect( 0, 0, pimg->cxWidth, pimg->cyHeight );

    hTempDC = CreateCompatibleDC( pimg->hDC );

    if (hTempDC == NULL)
        {
        TRACE( TEXT("SetUndo: CreateCompatibleDC failed\n") );
        goto LReturn;
        }

    hOldBitmap = (HBITMAP)SelectObject( hTempDC, g_hUndoImgBitmap );

    BitBlt( hTempDC, 0, 0, rect.Width(), rect.Height(),
                pimg->hDC, rect.left, rect.top, SRCCOPY );

    SelectObject( hTempDC, hOldBitmap );
    DeleteDC( hTempDC );

    bSuccess = TRUE;

    LReturn:

    return bSuccess;
    }

/***************************************************************************/

void DrawBrush(IMG* pimg, CPoint pt, BOOL bDraw)
    {
    int nStrokeWidth = CImgTool::GetCurrent()->GetStrokeWidth();

    if (g_bCustomBrush)
        {
        CRect rc(pt.x, pt.y,
                pt.x + theImgBrush.m_size.cx, pt.y + theImgBrush.m_size.cy);
        rc -= (CPoint)theImgBrush.m_handle;

        theImgBrush.m_rcSelection = rc;

        int nCombineMode;
        COLORREF cr;

        if (bDraw)
            {
            nCombineMode = (theImgBrush.m_bOpaque) ? combineReplace : combineMatte;
            cr = crLeft;
            }
        else
            {
            nCombineMode = combineColor;
            cr = crRight;
            }

        if (CImgTool::GetCurrentID() == IDMX_TEXTTOOL)
            {
//          extern CTextTool  g_textTool;
//          g_textTool.Render(CDC::FromHandle(pimg->hDC), rc, TRUE, TRUE);
            }
        else
            {
            switch (nCombineMode)
                {
#ifdef DEBUG
                default:
                    ASSERT(FALSE);
#endif

                case combineColor:
                    theImgBrush.BltColor(pimg, rc.TopLeft(), cr);
                    break;

                case combineMatte:
                    theImgBrush.BltMatte(pimg, rc.TopLeft());
                    break;

                case combineReplace:
                    theImgBrush.BltReplace(pimg, rc.TopLeft());
                    break;
                }
            }

            InvalImgRect (pimg, &rc);
            CommitImgRect(pimg, &rc);
        }
    else
        {
        DrawImgLine(pimg, pt, pt, bDraw ? crLeft : crRight,
                    CImgTool::GetCurrent()->GetStrokeWidth(),
                    CImgTool::GetCurrent()->GetStrokeShape(), TRUE);
        rcDragBrush.left = pt.x - nStrokeWidth / 2;
        rcDragBrush.top = pt.y - nStrokeWidth / 2;
        rcDragBrush.right = rcDragBrush.left + nStrokeWidth;
        rcDragBrush.bottom = rcDragBrush.top + nStrokeWidth;
        }
    }

/***************************************************************************/

void DrawDCLine(HDC hDC, CPoint pt1, CPoint pt2,
                 COLORREF color, int nWidth, int nShape,
                 CRect& rc)
{
        HPEN   hOldPen;
        HBRUSH hBrush;
        HBRUSH hOldBrush;
        int    sx;
        int    sy;
        int    ex;
        int    ey;
        int    nWidthD2 = nWidth / 2;

        sx = pt1.x;
        sy = pt1.y;
        ex = pt2.x;
        ey = pt2.y;

        if (hDC)
        {
                hBrush    = ::CreateSolidBrush( color );
                hOldBrush = (HBRUSH)SelectObject( hDC, hBrush );

                if (nWidth == 1)
                {
                        HPEN hPen = CreatePen(PS_SOLID, 1, (COLORREF)color);

                        if (hPen)
                        {
                            hOldPen = (HPEN)SelectObject(hDC, hPen);

                            ::MoveToEx(hDC, sx, sy, NULL);
                            LineTo(hDC, ex, ey);
                            SetPixel(hDC, ex, ey, color);
                            SelectObject(hDC, hOldPen);
                            DeleteObject(hPen);
                        }
                }
                else
                {
//                      hOldPen = (HPEN)SelectObject(hDC, GetStockObject( NULL_PEN ));

                        BrushLine( CDC::FromHandle(hDC),
                                CPoint(sx - nWidthD2, sy - nWidthD2),
                                CPoint(ex - nWidthD2, ey - nWidthD2), nWidth, nShape);
//                      SelectObject( hDC, hOldPen );
                }

                SelectObject(hDC, hOldBrush);
                DeleteObject(hBrush);
        }

        if (sx < ex)
        {
                rc.left = sx;
                rc.right = ex + 1;
        }
        else
        {
                rc.left = ex;
                rc.right = sx + 1;
        }

        if (sy < ey)
        {
                rc.top = sy;
                rc.bottom = ey + 1;
        }
        else
        {
                rc.top = ey;
                rc.bottom = sy + 1;
        }

        rc.left   -= nWidth * 2;
        rc.top    -= nWidth * 2;
        rc.right  += nWidth * 2;
        rc.bottom += nWidth * 2;
}


void DrawImgLine(IMG* pimg, CPoint pt1, CPoint pt2,
                 COLORREF color, int nWidth, int nShape,
                 BOOL bCommit)
{
        CRect  rc;

        DrawDCLine(pimg->hDC, pt1, pt2, color, nWidth, nShape, rc);

        InvalImgRect(pimg, &rc);

        if (bCommit)
        {
                CommitImgRect(pimg, &rc);
        }
}

/***************************************************************************/

void FillImgRect( HDC hDC, CRect* prc, COLORREF cr )
    {
    FixRect( prc );

    CPoint pt1 = prc->TopLeft();
    CPoint pt2 = prc->BottomRight();

    StandardiseCoords( &pt1, &pt2 );

    int sx = pt1.x;
    int sy = pt1.y;
    int ex = pt2.x;
    int ey = pt2.y;

    CRect rc( sx, sy, ex, ey );

    HBRUSH hBr = ::CreateSolidBrush( cr );

    if (! hBr)
        {
        theApp.SetGdiEmergency();
        return;
        }

    if (CImgTool::GetCurrentID() == IDMB_PICKRGNTOOL)
        {
        if (theImgBrush.m_cRgnPolyFreeHandSel.GetSafeHandle())
            {
            // offset bitmap in the imgwnd from selection boundary
            theImgBrush.m_cRgnPolyFreeHandSel.OffsetRgn( rc.left, rc.top );

            ::FillRgn( hDC, (HRGN)theImgBrush.m_cRgnPolyFreeHandSel.m_hObject,
                                                                        hBr );
            // offset back to selection boundary
            theImgBrush.m_cRgnPolyFreeHandSel.OffsetRgn( -rc.left, -rc.top );
            }
        }
    else
        {

        HPEN hPen = CreatePen( PS_NULL, 0, 0 );

        if (hPen)
            {
            HPEN   hOldPen =   (HPEN)SelectObject( hDC, hPen );
            HBRUSH hOldBr  = (HBRUSH)SelectObject( hDC, hBr );

            Rectangle( hDC, sx, sy, ex, ey );

            SelectObject( hDC, hOldPen);
            DeleteObject( hPen );
            SelectObject( hDC, hOldBr );
            }
        }

    DeleteObject( hBr );

    *prc = rc;
    }

/***************************************************************************/

void BrushLine(CDC* pDC, CPoint fromPt, CPoint toPt, int nWidth, int nShape)
    {
    CPen* pOldPen = (CPen*) pDC->SelectStockObject(NULL_PEN);
    CSize brushSize(nWidth, nWidth);

    int octant;

    if (nShape == slantedRightBrush || nShape == slantedLeftBrush)
        {
        int dx = abs(toPt.x - fromPt.x);
        int dy = abs(toPt.y - fromPt.y);

        if (toPt.x > fromPt.x)
            {
            if (toPt.y < fromPt.y)
                octant = (dx > dy) ? 0 : 1;
            else
                octant = (dx > dy) ? 7 : 6;
            }
        else
            {
            if (toPt.y < fromPt.y)
                octant = (dx > dy) ? 3 : 2;
            else
                octant = (dx > dy) ? 4 : 5;
            }
        }

    switch (nShape)
        {
        case squareBrush:
            PolyTo(pDC, fromPt, toPt, brushSize);
            break;

        case roundBrush:
            if (toPt != fromPt)
                {
                CRect tan;
                CPoint polyPts [4];

                if (!GetTanPt(brushSize, (CPoint)(toPt - fromPt), tan))
                    return;

                polyPts[0].x = fromPt.x + tan.left;
                polyPts[1].x = fromPt.x + tan.right;
                polyPts[2].x = toPt.x + tan.right;
                polyPts[3].x = toPt.x + tan.left;

                polyPts[0].y = fromPt.y + tan.top;
                polyPts[1].y = fromPt.y + tan.bottom;
                polyPts[2].y = toPt.y + tan.bottom;
                polyPts[3].y = toPt.y + tan.top;

                pDC->Polygon(polyPts, 4);

                Mylipse(pDC->m_hDC, fromPt.x, fromPt.y,
                        fromPt.x + brushSize.cx, fromPt.y + brushSize.cy, TRUE);
                }

            Mylipse(pDC->m_hDC, toPt.x, toPt.y,
                    toPt.x + brushSize.cx, toPt.y + brushSize.cy, TRUE);
            break;

        case slantedLeftBrush:
            {
            CPoint polyPts [6];

            fromPt.x -= 1;
            toPt.x -= 1;

            switch (octant)
                {
                case 0:
                    polyPts[0].x = fromPt.x;
                    polyPts[1].x = fromPt.x + 1;
                    polyPts[2].x = toPt.x + 1;
                    polyPts[3].x = toPt.x + brushSize.cy + 1;
                    polyPts[4].x = toPt.x + brushSize.cy;
                    polyPts[5].x = fromPt.x + brushSize.cy;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = toPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = fromPt.y;
                    break;

                case 1:
                    polyPts[0].x = fromPt.x;
                    polyPts[1].x = fromPt.x + 1;
                    polyPts[2].x = fromPt.x + brushSize.cy + 1;
                    polyPts[3].x = toPt.x + brushSize.cy + 1;
                    polyPts[4].x = toPt.x + brushSize.cy;
                    polyPts[5].x = toPt.x;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = fromPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = toPt.y + brushSize.cy;
                    break;

                case 2:
                case 3:
                    polyPts[0].x = fromPt.x + 1;
                    polyPts[1].x = fromPt.x;
                    polyPts[2].x = toPt.x;
                    polyPts[3].x = toPt.x + brushSize.cy;
                    polyPts[4].x = toPt.x + brushSize.cy + 1;
                    polyPts[5].x = fromPt.x + brushSize.cy + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = toPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = fromPt.y;
                    break;

                case 4:
                    polyPts[0].x = fromPt.x + brushSize.cy + 1;
                    polyPts[1].x = fromPt.x + brushSize.cy;
                    polyPts[2].x = toPt.x + brushSize.cy;
                    polyPts[3].x = toPt.x;
                    polyPts[4].x = toPt.x + 1;
                    polyPts[5].x = fromPt.x + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = toPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = fromPt.y + brushSize.cy;
                    break;

                case 5:
                    polyPts[0].x = fromPt.x + brushSize.cy + 1;
                    polyPts[1].x = fromPt.x + brushSize.cy;
                    polyPts[2].x = fromPt.x;
                    polyPts[3].x = toPt.x;
                    polyPts[4].x = toPt.x + 1;
                    polyPts[5].x = toPt.x + brushSize.cy + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = fromPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = toPt.y;
                    break;

                default:
                    polyPts[0].x = fromPt.x + brushSize.cy;
                    polyPts[1].x = fromPt.x + brushSize.cy + 1;
                    polyPts[2].x = toPt.x + brushSize.cy + 1;
                    polyPts[3].x = toPt.x + 1;
                    polyPts[4].x = toPt.x;
                    polyPts[5].x = fromPt.x;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = toPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = fromPt.y + brushSize.cy;
                    break;
                }

            pDC->Polygon(polyPts, 6);
            }
            break;

        case slantedRightBrush:
            {
            CPoint polyPts [6];

            switch (octant)
                {
                case 0:
                case 1:
                    polyPts[0].x = fromPt.x + brushSize.cy;
                    polyPts[1].x = fromPt.x + brushSize.cy + 1;
                    polyPts[2].x = toPt.x + brushSize.cy + 1;
                    polyPts[3].x = toPt.x + 1;
                    polyPts[4].x = toPt.x;
                    polyPts[5].x = fromPt.x;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = toPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = fromPt.y;
                    break;

                case 2 :
                    polyPts[0].x = fromPt.x + brushSize.cy + 1;
                    polyPts[1].x = fromPt.x + brushSize.cy;
                    polyPts[2].x = fromPt.x;
                    polyPts[3].x = toPt.x;
                    polyPts[4].x = toPt.x + 1;
                    polyPts[5].x = toPt.x + brushSize.cy + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = fromPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = toPt.y + brushSize.cy;
                    break;

                case 3 :
                    polyPts[0].x = fromPt.x + brushSize.cy + 1;
                    polyPts[1].x = fromPt.x + brushSize.cy;
                    polyPts[2].x = toPt.x + brushSize.cy;
                    polyPts[3].x = toPt.x;
                    polyPts[4].x = toPt.x + 1;
                    polyPts[5].x = fromPt.x + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y + brushSize.cy;
                    polyPts[2].y = toPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y;
                    polyPts[5].y = fromPt.y;
                    break;

                case 4 :
                case 5 :
                    polyPts[0].x = fromPt.x + 1;
                    polyPts[1].x = fromPt.x;
                    polyPts[2].x = toPt.x;
                    polyPts[3].x = toPt.x + brushSize.cy;
                    polyPts[4].x = toPt.x + brushSize.cy + 1;
                    polyPts[5].x = fromPt.x + brushSize.cy + 1;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = toPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = fromPt.y + brushSize.cy;
                    break;

                case 6 :
                    polyPts[0].x = fromPt.x;
                    polyPts[1].x = fromPt.x + 1;
                    polyPts[2].x = fromPt.x + brushSize.cy + 1;
                    polyPts[3].x = toPt.x + brushSize.cy + 1;
                    polyPts[4].x = toPt.x + brushSize.cy;
                    polyPts[5].x = toPt.x;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = fromPt.y + brushSize.cy;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = toPt.y;
                    break;

                default :
                    polyPts[0].x = fromPt.x;
                    polyPts[1].x = fromPt.x + 1;
                    polyPts[2].x = toPt.x + 1;
                    polyPts[3].x = toPt.x + brushSize.cy + 1;
                    polyPts[4].x = toPt.x + brushSize.cy;
                    polyPts[5].x = fromPt.x + brushSize.cy;
                    polyPts[0].y = polyPts[1].y = fromPt.y;
                    polyPts[2].y = toPt.y;
                    polyPts[3].y = polyPts[4].y = toPt.y + brushSize.cy;
                    polyPts[5].y = fromPt.y + brushSize.cy;
                    break;
                }

            pDC->Polygon(polyPts, 6);
            }
            break;
        }

    pDC->SelectObject(pOldPen);
    }

/***************************************************************************/

void SetCombineMode(int wNewCombineMode)
    {
    wCombineMode = wNewCombineMode;
    }

/***************************************************************************/

static int      cxLastShape;
static int      cyLastShape;
static int      cxShapeBitmap;
static int      cyShapeBitmap;
static CBitmap  shapeBitmap;
static enum { ellipse, roundRect }  nLastShape;

void Mylipse(HDC hDC, int x1, int y1, int x2, int y2, BOOL bFilled)
    {
        COLORREF crNewBk, crNewText;
        GetMonoBltColors(hDC, NULL, crNewBk, crNewText);

    COLORREF crOldText = SetTextColor(hDC, crNewText);
    COLORREF crOldBk   = SetBkColor  (hDC, crNewBk);

    int cx = x2 - x1;
    int cy = y2 - y1;

    if (!bFilled)
        {
        Ellipse(hDC, x1, y1, x2, y2);
        }
    else
        if (cx == cy && cx > 0 && cx <= 8)
            {
            // HACK: The Windows Ellipse function is no good for small ellipses, so I
            // use some little bitmaps here for filled circles 1 to 8 pixels in
            // diameter.

            static CBitmap  g_ellipses [8];

            if (g_ellipses[cx - 1].m_hObject == NULL &&
                    ! g_ellipses[cx - 1].LoadBitmap(IDB_ELLIPSE1 + cx - 1))
                {
                theApp.SetMemoryEmergency();
                SetTextColor(hDC, crOldText);
                SetBkColor(hDC, crOldBk);
                return;
                }

            HDC hTempDC = CreateCompatibleDC(hDC);
            if (hTempDC == NULL)
                {
                theApp.SetGdiEmergency();
                SetTextColor(hDC, crOldText);
                SetBkColor(hDC, crOldBk);
                return;
                }

            HBITMAP hOldBitmap = (HBITMAP) SelectObject(hTempDC,
                    g_ellipses[cx - 1].m_hObject);
            BitBlt(hDC, x1, y1, cx, cy, hTempDC, 0, 0, DSPDxax);
            SelectObject(hTempDC, hOldBitmap);
            DeleteDC(hTempDC);
            }
        else
            if (cx > 0 && cy > 0)
                {
                // Actually, Ellipse() is just no good...  Let's do as much
                // as possible our selves to fix it!  Here we draw the ellipse
                // into a monochrome bitmap to get the shape and then use that
                // as a mask to get the current pattern into the imge.

                HDC hTempDC = CreateCompatibleDC(hDC);
                if (hTempDC == NULL)
                    {
                    theApp.SetGdiEmergency();
                    SetTextColor(hDC, crOldText);
                    SetBkColor(hDC, crOldBk);
                    return;
                    }

                BOOL bRefill = FALSE;

                if (cx > cxShapeBitmap || cy > cyShapeBitmap)
                    {
                    shapeBitmap.DeleteObject();
                    if (shapeBitmap.CreateBitmap(cx, cy, 1, 1, NULL))
                        {
                        cxShapeBitmap = cx;
                        cyShapeBitmap = cy;
                        bRefill = TRUE;
                        }
                    }

                if (shapeBitmap.m_hObject == NULL)
                    {
                    theApp.SetMemoryEmergency();
                    DeleteDC(hTempDC);
                    SetTextColor(hDC, crOldText);
                    SetBkColor(hDC, crOldBk);
                    return;
                    }

                if (cx != cxLastShape || cy != cyLastShape || nLastShape != ellipse)
                    {
                    cxLastShape = cx;
                    cyLastShape = cy;
                    nLastShape = ellipse;
                    bRefill = TRUE;
                    }

                HBITMAP hOldBitmap = (HBITMAP)SelectObject(hTempDC,
                        shapeBitmap.m_hObject);

                if (bRefill)
                    {
                    PatBlt(hTempDC, 0, 0, cx, cy, BLACKNESS);
                    SelectObject(hTempDC, GetStockObject(WHITE_BRUSH));
                    SelectObject(hTempDC, GetStockObject(WHITE_PEN));
                    Ellipse(hTempDC, 0, 0, cx, cy);
                    }

                BitBlt(hDC, x1, y1, cx, cy, hTempDC, 0, 0, DSPDxax);

                SelectObject(hTempDC, hOldBitmap);
                DeleteDC(hTempDC);
                }

    SetTextColor(hDC, crOldText);
    SetBkColor(hDC, crOldBk);
    }

/***************************************************************************/
#ifdef XYZZYZ
void MyRoundRect(HDC hDC, int x1, int y1, int x2, int y2,
                 int nEllipseWidth, int nEllipseHeight, BOOL bFilled)
    {
    int cx = x2 - x1;
    int cy = y2 - y1;

    if (!bFilled)
        {
        RoundRect(hDC, x1, y1, x2, y2, nEllipseWidth, nEllipseHeight);
        return;
        }

    if (cx > 0 && cy > 0)
        {
        HDC hTempDC = CreateCompatibleDC(hDC);

        if (hTempDC == NULL)
            {
            theApp.SetGdiEmergency();
            return;
            }

        BOOL bRefill = FALSE;

        if (cx > cxShapeBitmap || cy > cyShapeBitmap)
            {
            shapeBitmap.DeleteObject();
            if (shapeBitmap.CreateBitmap(cx, cy, 1, 1, NULL))
                {
                cxShapeBitmap = cx;
                cyShapeBitmap = cy;
                bRefill = TRUE;
                }
            }

        if (shapeBitmap.m_hObject == NULL)
            {
            theApp.SetMemoryEmergency();
            DeleteDC(hTempDC);
            return;
            }

        if (cx != cxLastShape || cy != cyLastShape || nLastShape != roundRect)
            {
            cxLastShape = cx;
            cyLastShape = cy;
            nLastShape = roundRect;
            bRefill = TRUE;
            }

        HBITMAP hOldBitmap = (HBITMAP)SelectObject(hTempDC,
                shapeBitmap.m_hObject);

        if (bRefill)
            {
            PatBlt(hTempDC, 0, 0, cx, cy, BLACKNESS);
            SelectObject(hTempDC, GetStockObject(WHITE_BRUSH));
            SelectObject(hTempDC, GetStockObject(WHITE_PEN));
            RoundRect(hTempDC, 0, 0, cx, cy, nEllipseWidth, nEllipseHeight);
            }

        BitBlt(hDC, x1, y1, cx, cy, hTempDC, 0, 0, DSna);
        BitBlt(hDC, x1, y1, cx, cy, hTempDC, 0, 0, DSPao);

        SelectObject(hTempDC, hOldBitmap);
        DeleteDC(hTempDC);
        }
    }
#endif
/***************************************************************************/

void PolyTo(CDC* pDC, CPoint fromPt, CPoint toPt, CSize size)
    {
    CPoint polyPts [6];

    if (toPt.x > fromPt.x)
        {
        polyPts[0].x = polyPts[1].x = fromPt.x;
        polyPts[2].x = toPt.x;
        polyPts[3].x = polyPts[4].x = toPt.x + size.cx;
        polyPts[5].x = fromPt.x + size.cx;
        }
    else
        {
        polyPts[0].x = polyPts[1].x = fromPt.x + size.cx;
        polyPts[2].x = toPt.x + size.cx;
        polyPts[3].x = polyPts[4].x = toPt.x;
        polyPts[5].x = fromPt.x;
        }

    if (toPt.y > fromPt.y)
        {
        polyPts[0].y = polyPts[5].y = fromPt.y;
        polyPts[1].y = fromPt.y + size.cy;
        polyPts[2].y = polyPts[3].y = toPt.y + size.cy;
        polyPts[4].y = toPt.y;
        }
    else
        {
        polyPts[0].y = polyPts[5].y = fromPt.y + size.cy;
        polyPts[1].y = fromPt.y;
        polyPts[2].y = polyPts[3].y = toPt.y;
        polyPts[4].y = toPt.y + size.cy;
        }

    if (pDC)
        pDC->Polygon(polyPts, 6);
    }

/***************************************************************************/

BOOL GetTanPt(CSize size, CPoint delta, CRect& tan)
    {
    int x, y;
    int xExt, yExt, theExt, xTemp;
    CDC dc;
    CBitmap* pOldBitmap, bitmap;

    size.cx += 1;
    size.cy += 1;

    tan.SetRect(0, 0, 0, 0);

    if (!dc.CreateCompatibleDC(NULL))
        {
        theApp.SetGdiEmergency();
        return FALSE;
        }

    if (!bitmap.CreateCompatibleBitmap(&dc, size.cx, size.cy))
        {
        theApp.SetMemoryEmergency();
        return FALSE;
        }

    VERIFY((pOldBitmap = dc.SelectObject(&bitmap)));

    TRY
        {
        CBrush cBrushWhite(PALETTERGB(0xff, 0xff, 0xff));
        CRect cRectTmp(0,0,size.cx, size.cy);
        dc.FillRect(&cRectTmp, &cBrushWhite);
//    dc.PatBlt(0, 0, size.cx, size.cy, WHITENESS);
        }
    CATCH(CResourceException, e)
        {
        }
    END_CATCH
    dc.SelectStockObject(NULL_PEN);
    dc.SelectStockObject(BLACK_BRUSH);
    Mylipse(dc.m_hDC, 0, 0, size.cx, size.cy, TRUE);

    yExt = 0;
    for (xExt = 0; xExt < size.cx - 1; xExt++)
        {
        if (dc.GetPixel(xExt, 0) == 0)
            break;
        }
    theExt = 10 * xExt;

    if (delta.y == 0)
        {
        tan.SetRect(xExt, 0, xExt, size.cy - 1);
        }
    else
        {
        for (y = 0; y < size.cy; y++)
            {
            for (x = 0; x < size.cx - 1; x++)
                {
                if (dc.GetPixel(x, y) == 0)
                    break;
                }

            xTemp = 10 * x - 10 * y * delta.x / delta.y;
            if (theExt > xTemp)
                {
                xExt = x;
                yExt = y;
                theExt = xTemp;
                }
            else
                if (theExt < xTemp)
                    {
                    break;
                    }
            }

        tan.left = xExt;
        tan.top = yExt;

        for (y = 0; y < size.cy; y++)
            {
            for (x = size.cx - 1; x > 0; x--)
                {
                if (dc.GetPixel(x, y) == 0)
                    break;
                }
            xTemp = 10 * x - 10 * y * delta.x / delta.y;
            if (theExt < xTemp)
                {
                xExt = x;
                yExt = y;
                theExt = xTemp;
                }
            else
                if (theExt > xTemp)
                    {
                    break;
                    }
            }

        tan.right = xExt;
        tan.bottom = yExt;
        }

    dc.SelectObject(pOldBitmap);

    return TRUE;
    }

/***************************************************************************/

void PickupSelection()
    {
    // Time to pick up the bits!
    ASSERT(theImgBrush.m_pImg);

    if (theImgBrush.m_pImg == NULL)
        return;

    CPalette* ppalOld = theImgBrush.SetBrushPalette( &theImgBrush.m_dc, FALSE ); // Background ??
    CDC* pImgDC = CDC::FromHandle( theImgBrush.m_pImg->hDC );

    if (CImgTool::GetCurrentID() == IDMB_PICKRGNTOOL)
        {
        TRY {
            CBrush cBrushBk( crRight );
            CRect  cRectTmp( 0, 0, theImgBrush.m_size.cx,
                                   theImgBrush.m_size.cy );

            theImgBrush.m_dc.FillRect( &cRectTmp, &cBrushBk );
            }
        CATCH(CResourceException, e)
            {
            theApp.SetGdiEmergency();
            return;
            }
        END_CATCH

        if (theImgBrush.m_cRgnPolyFreeHandSel.GetSafeHandle())
            {
            theImgBrush.m_dc.SelectClipRgn(&theImgBrush.m_cRgnPolyFreeHandSel);

            theImgBrush.m_dc.StretchBlt( 0, 0, theImgBrush.m_size.cx,
                                               theImgBrush.m_size.cy,
                                       pImgDC, theImgBrush.m_rcSelection.left,
                                               theImgBrush.m_rcSelection.top,
                                               theImgBrush.m_rcSelection.Width(),
                                               theImgBrush.m_rcSelection.Height(),
                                               SRCCOPY );

            theImgBrush.m_dc.SelectClipRgn(NULL);
            }
        }
    else
        {
        theImgBrush.m_dc.BitBlt( 0, 0, theImgBrush.m_size.cx,
                                       theImgBrush.m_size.cy,
                               pImgDC, theImgBrush.m_rcSelection.left,
                                       theImgBrush.m_rcSelection.top, SRCCOPY );
        }

    if (ppalOld)
        theImgBrush.m_dc.SelectPalette( ppalOld, FALSE );

    theImgBrush.RecalcMask( crRight );
    }

/***************************************************************************/

void CommitSelection(BOOL bSetUndo)
    {
    if (theImgBrush.m_bMakingSelection)
        return;

    if (! theImgBrush.m_bSmearSel
    &&  ( theImgBrush.m_pImg == NULL
       || theImgBrush.m_bFirstDrag
       || theImgBrush.m_bLastDragWasASmear))
        {
        return;
        }

    if (theImgBrush.m_bLastDragWasFirst
    && (theImgBrush.m_bMoveSel
     || theImgBrush.m_bSmearSel))
        {
        return;
        }

    TRACE1( "CommitSelection(%d)\n", bSetUndo );

    if (bSetUndo)
        {
        HideBrush();
        CRect rectUndo = theImgBrush.m_rcSelection;

        if (theImgBrush.m_bLastDragWasFirst)
            {
            theImgBrush.m_bLastDragWasFirst = FALSE;
            rectUndo |= theImgBrush.m_rcDraggedFrom;
            }
        else
            {
            SetUndo(theImgBrush.m_pImg);
            }

        CImgWnd::c_pImgWndCur->FinishUndo(rectUndo);
        }

    if (CImgTool::GetCurrentID() != IDMX_TEXTTOOL)
        {
        if (theImgBrush.m_bOpaque)
            {
            theImgBrush.BltReplace( theImgBrush.m_pImg,
                                    theImgBrush.m_rcSelection.TopLeft() );
            }
        else
            {
            theImgBrush.BltMatte( theImgBrush.m_pImg,
                                  theImgBrush.m_rcSelection.TopLeft() );
            }
        }

    InvalImgRect ( theImgBrush.m_pImg, &theImgBrush.m_rcSelection );
    CommitImgRect( theImgBrush.m_pImg, &theImgBrush.m_rcSelection );

    DirtyImg( theImgBrush.m_pImg );
    }

/***************************************************************************/

void AddImgWnd(IMG* pimg, CImgWnd* pImgWnd)
    {
    ASSERT(pImgWnd->m_pNextImgWnd == NULL);
    pImgWnd->m_pNextImgWnd = pimg->m_pFirstImgWnd;
    pimg->m_pFirstImgWnd = pImgWnd;
    }

/***************************************************************************/

void GetImgSize(IMG* pImg, CSize& size)
    {
    size.cx = pImg->cxWidth;
    size.cy = pImg->cyHeight;
    }

/***************************************************************************/

BOOL SetImgSize(IMG* pImg, CSize newSize, BOOL bStretch)
    {
    if (newSize.cx != pImg->cxWidth
    ||  newSize.cy != pImg->cyHeight)
        {
        HBITMAP hNewBitmap = CreateCompatibleBitmap( pImg->hDC,
                                                     newSize.cx, newSize.cy );
        if (hNewBitmap == NULL)
            {
            return FALSE;
            }
        HDC hDC = CreateCompatibleDC( pImg->hDC );

        if (hDC == NULL)
            {
            DeleteObject( hNewBitmap );
            return FALSE;
            }
        HBITMAP hOldBitmap = (HBITMAP)SelectObject( hDC, hNewBitmap );

        ASSERT( hOldBitmap );

        HPALETTE hpalOld = NULL;

        if (theApp.m_pPalette
        &&  theApp.m_pPalette->m_hObject)
            {
            hpalOld = SelectPalette( hDC, (HPALETTE)theApp.m_pPalette->m_hObject, FALSE ); // Background ??
            RealizePalette( hDC );
            }

        if (bStretch)
            {
            UINT uStretch = HALFTONE;


            if (pImg->cPlanes * pImg->cBitCount == 1)
                {
                uStretch = BLACKONWHITE;

                if (GetRValue( crLeft )
                ||  GetGValue( crLeft )
                ||  GetBValue( crLeft ))
                    uStretch = WHITEONBLACK;
                }

            SetStretchBltMode( hDC, uStretch );

            StretchCopy( hDC, 0, 0, newSize.cx, newSize.cy,
                   pImg->hDC, 0, 0, pImg->cxWidth, pImg->cyHeight );
            }
        else
            {
            // Fill it with the background color first!
            HBRUSH hBrush  = ::CreateSolidBrush(crRight);

            ASSERT(hBrush);

            if (hBrush)
                {
                HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, hBrush);

                ASSERT(hOldBrush);

                PatBlt(hDC, 0, 0, newSize.cx, newSize.cy, PATCOPY);

                VERIFY(SelectObject(hDC, hOldBrush) == hBrush);

                DeleteObject(hBrush);
                }

            BitBlt(hDC, 0, 0, newSize.cx, newSize.cy, pImg->hDC, 0, 0, SRCCOPY);
            }

        if (hpalOld)
            SelectPalette( hDC, hpalOld, FALSE ); // Background ??

        VERIFY( SelectObject(       hDC, hOldBitmap) ==    hNewBitmap );
        VERIFY( SelectObject( pImg->hDC, hNewBitmap) == pImg->hBitmap );

        DeleteObject( pImg->hBitmap );
        DeleteDC( hDC );

        pImg->hBitmap  = hNewBitmap;
        pImg->cxWidth  = newSize.cx;
        pImg->cyHeight = newSize.cy;

        pRubberImg = NULL;

        SetupRubber( pImg );

        InvalImgRect(pImg, NULL);

        CImgWnd* pImgWnd = pImg->m_pFirstImgWnd;

        while (pImgWnd)
            {
            pImgWnd->Invalidate( FALSE );
            pImgWnd->CheckScrollBars();

            pImgWnd = pImgWnd->m_pNextImgWnd;
            }
        }

    return TRUE;
    }

/***************************************************************************/

// This may be set to TRUE via the "DriverCanStretch" entry in the INI file.
// Doing so will speed up the graphics editor, but will cause some device
// drivers to crash (hence the FALSE default).
//
BOOL  g_bDriverCanStretch = TRUE;

void StretchCopy( HDC hdcDest, int xDest, int yDest, int cxDest, int cyDest,
                  HDC hdcSrc , int xSrc , int ySrc , int cxSrc , int cySrc )
    {
    if (cxDest == cxSrc && cyDest == cySrc)
        {
        // No point in using the trick if we're not really stretching...

        BitBlt(hdcDest, xDest, yDest, cxDest, cyDest,
               hdcSrc,  xSrc,  ySrc, SRCCOPY);
        }
    else
        if (g_bDriverCanStretch ||
            cxDest < 0 || cyDest < 0 || cxSrc < 0 || cySrc < 0)
            {
            // We can't use the following trick when flipping, but the
            // driver will usually pass things on to GDI here anyway...

            StretchBlt(hdcDest, xDest, yDest, cxDest, cyDest,
                       hdcSrc, xSrc, ySrc, cxSrc, cySrc, SRCCOPY);
            }
        else
            {
            // Some drivers (e.g. Paradise) crash on memory to memory stretches,
            // so we trick them into just using GDI here by not using SRCCOPY...

            PatBlt(hdcDest, xDest, yDest, cxDest, cyDest, BLACKNESS);

            StretchBlt(hdcDest, xDest, yDest, cxDest, cyDest,
                        hdcSrc, xSrc, ySrc, cxSrc, cySrc, DSo);
            }
    }

/***************************************************************************/

void StandardiseCoords(CPoint* s, CPoint* e)
    {
    if (s->x > e->x)
        {
        int tx;

        tx = s->x;
        s->x = e->x;
        e->x = tx;
        }

    if (s->y > e->y)
        {
        int ty;

        ty = s->y;
        s->y = e->y;
        e->y = ty;
        }
    }

/***************************************************************************/

CPalette* MergePalettes( CPalette *pPal1, CPalette *pPal2, int& iAdds )
    {
    int           iPal1NumEntries;
    int           iPal2NumEntries;
    LOGPALETTE256 LogPal1;
    LOGPALETTE256 LogPal2;
    CPalette*     pPalMerged = NULL;

    iAdds = 0;

    if (pPal1 == NULL ||  pPal2 == NULL)
        return NULL;

    iPal1NumEntries = pPal1->GetPaletteEntries( 0, 256, &LogPal1.palPalEntry[0] );
    pPal1->GetPaletteEntries( 0, iPal1NumEntries, &LogPal1.palPalEntry[0] );

    iPal2NumEntries = pPal2->GetPaletteEntries( 0, 256, &LogPal2.palPalEntry[0] );
    pPal2->GetPaletteEntries( 0, iPal2NumEntries, &LogPal2.palPalEntry[0] );

    // check if room left in 1st palette to merge.  If no room, then use 1st palette
    for (int i = 0; i < iPal2NumEntries
                     && iPal1NumEntries < MAX_PALETTE_COLORS; i++)
        {
        for (int j = 0; j < iPal1NumEntries; j++)
            {
            if (LogPal1.palPalEntry[j].peRed   == LogPal2.palPalEntry[i].peRed
            &&  LogPal1.palPalEntry[j].peGreen == LogPal2.palPalEntry[i].peGreen
            &&  LogPal1.palPalEntry[j].peBlue  == LogPal2.palPalEntry[i].peBlue)
                break;
            }

        if (j < iPal1NumEntries)
            continue;  // found one

        // color was not found in 1st palette add it if room
        LogPal1.palPalEntry[iPal1NumEntries++] = LogPal2.palPalEntry[i];
        iAdds++;
        }

    LogPal1.palVersion    = 0x300;
    LogPal1.palNumEntries = (WORD)iPal1NumEntries;

    pPalMerged = new CPalette();

    if (pPalMerged)
        if (! CreateSafePalette(pPalMerged, (LPLOGPALETTE)&LogPal1 ))
            {
            delete pPalMerged;
            pPalMerged = NULL;
            }

    return pPalMerged;
    }

/******************************************************************************/

void AdjustPointForGrid(CPoint *ptPointLocation)
    {
    if (theApp.m_iSnapToGrid != 0)
        {
        int iNextGridOffset;

        iNextGridOffset = ptPointLocation->x % theApp.m_iGridExtent;
        // if distance to next grid is less than 1/2 distance between grids
        // closer to previous grid
        if (iNextGridOffset <= theApp.m_iGridExtent/2)
            {
            iNextGridOffset *= -1; // closer to previous grid location
            }
        else
            {
            iNextGridOffset = theApp.m_iGridExtent -iNextGridOffset; // closer to next grid location
            }
        ptPointLocation->x = ptPointLocation->x + iNextGridOffset;

        // if distance to next grid is less than 1/2 distance between grids
        // closer to previous grid
        iNextGridOffset = ptPointLocation->y % theApp.m_iGridExtent;
        if (iNextGridOffset <= theApp.m_iGridExtent/2)
            {
            iNextGridOffset *= -1; // closer to previous grid location
            }
        else
            {
            iNextGridOffset = theApp.m_iGridExtent -iNextGridOffset; // closer to next grid location
            }

        ptPointLocation->y = ptPointLocation->y + iNextGridOffset;
        }
    }

static unsigned short  bmapHorzBorder [] =
                                { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0 };

static unsigned short  bmapVertBorder [] =
                                { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 };

       CBrush   g_brSelectHorz;
       CBrush   g_brSelectVert;
static CBitmap  m_bmSelectHorz;
static CBitmap  m_bmSelectVert;

void InitCustomData()                           // called once in main
    {
    //
    // no windows critical error message box, return errors to open call
    //
    if (m_bmSelectHorz.CreateBitmap( 8, 8, 1, 1, (LPSTR)bmapHorzBorder ))
        g_brSelectHorz.CreatePatternBrush( &m_bmSelectHorz );

    if (m_bmSelectVert.CreateBitmap( 8, 8, 1, 1, (LPSTR)bmapVertBorder ))
        g_brSelectVert.CreatePatternBrush( &m_bmSelectVert );

    SetErrorMode( SEM_FAILCRITICALERRORS );
    }

//
// make sure global resoures are freed
//
void CustomExit()
    {
    if (g_brSelectHorz.m_hObject)
        g_brSelectHorz.DeleteObject();

    if (m_bmSelectHorz.m_hObject)
        m_bmSelectHorz.DeleteObject();

    if (g_brSelectVert.m_hObject)
        g_brSelectVert.DeleteObject();

    if (m_bmSelectVert.m_hObject)
        m_bmSelectVert.DeleteObject();
    }

/*
int FileTypeFromExtension( const TCHAR far* lpcExt )
    {
    if (*lpcExt == TEXT('.'))        // skip the . in .*
        lpcExt++;

    // must be redone
    return NULL;
    }
*/

CPalette *PBSelectPalette(CDC *pDC, CPalette *pPalette, BOOL bForceBk)
{
        if (!pPalette)
        {
                return(NULL);
        }

        if (IsInPlace())
        {
                bForceBk = TRUE;
        }

        CPalette *ppalOld = pDC->SelectPalette( theApp.m_pPalette, bForceBk );
        pDC->RealizePalette();

        return(ppalOld);
}

