    /****************************************************************************/
    /*                                                                          */
    /*                 Copyright (C) 1987-1996 Microsoft Corp.                */
    /*                           All Rights Reserved                            */
    /*                                                                          */
    /****************************************************************************/
    /****************************** Module Header *******************************
    * Module Name: util.c
    *
    * Contains miscellaneous utility functions for ImagEdit.
    *
    * History:
    *
    ****************************************************************************/
    
    #include "imagedit.h"
    
    #include <stdio.h>
    #include <stdarg.h>
    
    #define CBOVERHEAD      (sizeof(INT)+sizeof(INT)+sizeof(INT))
    #define MEMSIGHEAD      0x1234
    #define MEMSIGTAIL      0x5678
    
    
    
    /****************************************************************************
    * MyAlloc
    *
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/
    
    VOID *MyAlloc(
        INT cbAlloc)
    {
        register HANDLE hMem;
    
        if (hMem = LocalAlloc(LMEM_FIXED, cbAlloc)) {
            return (VOID *)hMem;
        }
        else {
            MessageBeep(0);
            Message(MSG_OUTOFMEMORY);
    
            return NULL;
        }
    }
    
    
    
    /****************************************************************************
    * MyRealloc
    *
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/
    
    VOID *MyRealloc(
        VOID *npMem,
        INT cbNewAlloc)
    {
        npMem = (VOID *)LocalReAlloc((HANDLE)npMem, cbNewAlloc, LMEM_MOVEABLE);
    
        if (!npMem) {
            MessageBeep(0);
            Message(MSG_OUTOFMEMORY);
    
            return NULL;
        }
    
        return npMem;
    }
    
    
    
    /****************************************************************************
    * MyFree
    *
    *
    * History:
    *  25-Jul-1989   - Created
    ****************************************************************************/
    
    VOID *MyFree(
        VOID *npMem)
    {
        if (LocalFree((HANDLE)npMem)) {
            MessageBeep(0);
            Message(MSG_MEMERROR);
    
            return npMem;
        }
    
        return NULL;
    }
    
    
    
    /************************************************************************
    * Message
    *
    * This function puts up a message box.  The message is described in
    * the gamdMessages table.
    *
    * Arguments:
    *   UINT idMsg - Index to the message.
    *   ...        - Optional arguments.
    *
    * Returns:
    *     What MessageBox returns.
    *
    ************************************************************************/
    
    INT Message(
        UINT idMsg,
        ...)
    {
        va_list marker;
        INT RetCode;
        CHAR szT[CCHTEXTMAX];
    
        va_start(marker, idMsg);
        vsprintf(szT, ids(gamdMessages[idMsg].ids), marker);
    
        RetCode = MessageBox(NULL, szT, ids(IDS_PGMTITLE),
                gamdMessages[idMsg].fMessageBox | MB_TASKMODAL);
    
        va_end(marker);
    
        return RetCode;
    }
    
    
    
    /************************************************************************
    * CenterWindow
    *
    * This function centers the given window over its owner.  It ensures
    * that the window is entirely within the visible screen, however.
    * If the window does not have an owner, it is centered over the
    * desktop.
    *
    * Arguments:
    *   HWND hwnd - The window to center.
    *
    * History:
    *
    ************************************************************************/
    
    VOID CenterWindow(
        HWND hwnd)
    {
        RECT rc;
        RECT rcOwner;
        RECT rcCenter;
        HWND hwndOwner;
    
        GetWindowRect(hwnd, &rc);
    
        if (!(hwndOwner = GetWindow(hwnd, GW_OWNER)))
            hwndOwner = GetDesktopWindow();
    
        GetWindowRect(hwndOwner, &rcOwner);
    
        /*
         *  Calculate the starting x,y for the new
         *  window so that it would be centered.
         */
        rcCenter.left = rcOwner.left +
                (((rcOwner.right - rcOwner.left) -
                (rc.right - rc.left))
                / 2);
    
        rcCenter.top = rcOwner.top +
                (((rcOwner.bottom - rcOwner.top) -
                (rc.bottom - rc.top))
                / 2);
    
        rcCenter.right = rcCenter.left + (rc.right - rc.left);
        rcCenter.bottom = rcCenter.top + (rc.bottom - rc.top);
    
        FitRectToScreen(&rcCenter);
    
        SetWindowPos(hwnd, NULL, rcCenter.left, rcCenter.top, 0, 0,
                SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
    }
    
    
    
    /************************************************************************
    * FitRectToScreen
    *
    * This function ensures that the given rectangle is entirely within
    * the visible screen, adjusting it if necessary.
    *
    * Arguments:
    *   PRECT prc - The rectangle.
    *
    * History:
    *
    ************************************************************************/
    
    VOID FitRectToScreen(
        PRECT prc)
    {
        INT cxScreen;
        INT cyScreen;
        INT delta;
    
        cxScreen = GetSystemMetrics(SM_CXSCREEN);
        cyScreen = GetSystemMetrics(SM_CYSCREEN);
    
        if (prc->right > cxScreen) {
            delta = prc->right - prc->left;
            prc->right = cxScreen;
            prc->left = prc->right - delta;
        }
    
        if (prc->left < 0) {
            delta = prc->right - prc->left;
            prc->left = 0;
            prc->right = prc->left + delta;
        }
    
        if (prc->bottom > cyScreen) {
            delta = prc->bottom - prc->top;
            prc->bottom = cyScreen;
            prc->top = prc->bottom - delta;
        }
    
        if (prc->top < 0) {
            delta = prc->bottom - prc->top;
            prc->top = 0;
            prc->bottom = prc->top + delta;
        }
    }
    
    
    
    /************************************************************************
    * ids
    *
    * This function will return a string, given the string id.  If this is
    * the first time that the string has been retrieved, memory will be
    * allocated for it and it will be loaded.  After it is loaded once, it
    * is then cached in a PSTR array and is available for later without
    * having to load it again.
    *
    * Arguments:
    *   UINT idString - String ID of the string to retrieve.
    *
    * History:
    *
    ************************************************************************/
    
    PSTR ids(
        UINT idString)
    {
        static PSTR apstr[CSTRINGS];        // String resource array cache.
        PSTR pstr;
        INT cch;
    
        if (apstr[idString])
            return apstr[idString];
    
        if (!(pstr = MyAlloc(CCHTEXTMAX)))
            return "";
    
        if (!(cch = LoadString(ghInst, idString, pstr, CCHTEXTMAX))) {
            MyFree(pstr);
            return "";
        }
    
        apstr[idString] = pstr = MyRealloc(pstr, cch + 1);
    
        return (pstr ? pstr : "");
    }
    
    
    
    /************************************************************************
    * MyCreateBitmap
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    HBITMAP MyCreateBitmap(
        HDC hdc,
        INT cx,
        INT cy,
        INT nColors)
    {
        BITMAPINFOHEADER bmih;
    
        if (nColors == 2) {
            return CreateBitmap(cx, cy, 1, 1, NULL);
        }
        else {
            bmih.biSize = sizeof(BITMAPINFOHEADER);
            bmih.biWidth = cx;
            bmih.biHeight = cy;
            bmih.biPlanes = 1;              // 1 plane, 4 bpp is
            bmih.biBitCount = 4;            // 16 colors.
    
            bmih.biCompression =
            bmih.biSizeImage =
            bmih.biXPelsPerMeter =
            bmih.biYPelsPerMeter =
            bmih.biClrUsed =
            bmih.biClrImportant = 0;
    
            return CreateDIBitmap(hdc, &bmih, 0L, NULL, NULL, 0);
        }
    }
    
    
    
    #if defined(DBG) && defined(WIN16)
    /****************************************************************************
    * DBGStackReport
    *
    * This debugging function reports how much stack is used by a program.
    * To use it, call it with fInit == TRUE right at the beginning of the
    * program and then with fInit == FALSE just before the program exits.
    * The stack space used during the current run of the program will be
    * displayed on the debug terminal.
    *
    * It is implemented by filling the stack with a certain value down to
    * the bottom of the stack (if fInit is TRUE).  When it is called with
    * fInit == FALSE, it starts at the bottom of the stack and looks for
    * where this "signature" value has been overwritten with data, then
    * does a little math to compute the used stack.
    *
    * Arguments:
    *   BOOL fInit - TRUE if the stack should be initialized, FALSE to
    *                print out the report.
    *
    * History:
    *  28-Aug-1990  - Created
    ****************************************************************************/
    
    /*
     * This signature byte will be used to fill the stack.
     */
    #define STACKSIG    'A'
    
    /*
     * This is a C runtime global that is always at the very end of the
     * global data.  Taking its address is a way that the "bottom" of the
     * stack can be found.
     */
    extern CHAR end;
    
    VOID DBGStackReport(
        BOOL fInit)
    {
        static PBYTE pbStackTop;
        PBYTE pb;
        BYTE bDummy;
    
        if (fInit) {
            /*
             * The address of one of this functions local variables is
             * taken and considered the "top" of the stack.  This means
             * that it will work best when it is called first thing in
             * the program.
             */
            pbStackTop = pb = &bDummy;
    
            /*
             * Fill the stack up.
             */
            while (pb > &end)
                *pb-- = STACKSIG;
        }
        else {
            /*
             * Start at the bottom of the stack and search upwards.
             */
            pb = &end;
            while (*(++pb) == STACKSIG && pb < pbStackTop)
                ;
    
            /*
             * Display the results.
             */
            DBGprintf("ImagEdit stack used: %d bytes.", pbStackTop - pb);
        }
    }
    #endif
    
    
    
    #ifdef DBG
    /****************************************************************************
    * DBGBltImage
    *
    * This debugging function blits out the given image in the specified
    * DC to the screen.  Every time that it is called, it will blit the
    * image to the right of the last one, starting at the top of the
    * screen.  It assumes that each image is 32x32 pixels.
    *
    * Arguments:
    *   HDC hdc - The DC with the image to blit.  If this is NULL, the
    *             current XOR and AND images are blit'd, with the AND
    *             image below the XOR image.
    *
    * History:
    *  16-Sep-1991  - Created
    ****************************************************************************/
    
    VOID DBGBltImage(
        HDC hdc)
    {
        static INT x;
        HDC hdcScreen;
    
        hdcScreen = GetDC(NULL);
    
        if (hdc) {
            BitBlt(hdcScreen, x, 0, 32, 32, hdc, 0, 0, SRCCOPY);
        }
        else {
            BitBlt(hdcScreen, x, 0, 32, 32, ghdcImage, 0, 0, SRCCOPY);
            BitBlt(hdcScreen, x, 32 + 1, 32, 32, ghdcANDMask, 0, 0, SRCCOPY);
        }
    
        ReleaseDC(NULL, hdcScreen);
        x += 32 + 1;
    }
    
    
    
    /****************************************************************************
    * DBGprintf
    *
    * This debugging function prints out a string to the debug output.
    * An optional set of substitutional parameters can be specified,
    * and the final output will be the processed result of these combined
    * with the format string, just like printf.  A newline is always
    * output after every call to this function.
    *
    * Arguments:
    *   PSTR fmt - Format string (printf style).
    *   ...      - Variable number of arguments.
    *
    * History:
    *  28-Aug-1990  - Created
    ****************************************************************************/
    
    VOID DBGprintf(PSTR fmt, ...)
    {
        va_list marker;
        CHAR szBuf[CCHTEXTMAX];
    
        va_start(marker, fmt);
        vsprintf(szBuf, fmt, marker);
        va_end(marker);
    
        OutputDebugString(szBuf);
        OutputDebugString("\r\n");
    }
    #endif
