    /***************************************************************************
     *                                                                         *
     *  MODULE      : WorkWP.c                                                 *
     *                                                                         *
     *  DESCRIPTION : Functions controlling the workspace window.              *
     *                                                                         *
     *  HISTORY     : 6/21/89 LR                                               *
     *                                                                         *
     ***************************************************************************/
    
    #include "imagedit.h"
    
    #include <stdlib.h>
    
    
    /*
     * This structure is used in conjunction with DeltaGenInit() and DeltaGen().
     */
    typedef struct _DELTAGEN { /* dg */
        BOOL fSwap;
        INT xf;
        INT yf;
        INT dx;
        INT dy;
        INT d;
        INT incx;
        INT incy;
        INT inc1;
        INT inc2;
    } DELTAGEN;
    typedef DELTAGEN *PDELTAGEN;
    
    
    STATICFN VOID NEAR WorkPaint(HWND hwnd);
    STATICFN VOID WorkButtonDown(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID WorkButtonMouseMove(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID WorkButtonUp(HWND hwnd, UINT msg, PPOINT ppt);
    STATICFN VOID NEAR SnapPointToGrid(PPOINT ppt);
    STATICFN VOID DrawToPoint(HWND hwnd, PPOINT ppt, BOOL fBrush);
    STATICFN BOOL NEAR DeltaGenInit(PDELTAGEN pdg, INT x0, INT y0,
        INT xf, INT yf, PINT px, PINT py);
    STATICFN BOOL NEAR DeltaGen(PDELTAGEN pdg, PINT px, PINT py);
    STATICFN VOID DrawPoint(HWND hwnd, PPOINT ppt, BOOL fBrush);
    STATICFN VOID NEAR RubberBandLine(BOOL fFirstTime);
    STATICFN VOID NEAR RectDPDraw(HWND hwnd);
    STATICFN VOID NEAR RubberBandRect(BOOL fFirstTime);
    STATICFN VOID NEAR CircleDPDraw(HWND hwnd);
    STATICFN VOID NEAR RubberBandCircle(BOOL fFirstTime);
    STATICFN VOID NEAR MarkHotSpotPosition(INT x, INT y);
    STATICFN VOID NEAR StartRubberBanding(HWND hwnd);
    STATICFN VOID NEAR EndRubberBanding(HWND hwnd);
    
    
    static BOOL fDrawing = FALSE;           // TRUE if mouse button is down.
    static BOOL fLeftButtonDown;            // TRUE if left button was pressed.
    static POINT ptStart;                   // Saves the starting point.
    static POINT ptEnd;                     // Saves the ending point.
    static POINT ptPrev;                    // Saves the previous point.
    static HDC hdcRubberBand;               // DC used during rubber banding.
    static BOOL fRubberBanding = FALSE;     // Tracking is in progress.
    
    
    
    /****************************************************************************
     * WorkWndProc
     *                                                                          *
     * purpose: Processes basic create and size and paint messages for the      *
     *          workspace window.
     *                                                                          *
     ****************************************************************************/
    
    WINDOWPROC WorkWndProc(
        HWND hwnd,
        UINT msg,
        WPARAM wParam,
        LPARAM lParam)
    {
        LPCREATESTRUCT cs;
        POINT pt;
    
        switch (msg) {
            case WM_CREATE:
                /* set up image variables */
                cs = (LPCREATESTRUCT)lParam;
                gcxWorkSpace = cs->cx;
                gcyWorkSpace = cs->cy;
                break;
    
            case WM_SIZE:
                gcxWorkSpace = LOWORD(lParam);
                gcyWorkSpace = HIWORD(lParam);
    
                break;
    
            case WM_PAINT:
                WorkPaint(hwnd);
                break;
    
            case WM_MOUSEMOVE:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonMouseMove(hwnd, msg, &pt);
                break;
    
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonDown(hwnd, msg, &pt);
                break;
    
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
                WorkButtonUp(hwnd, msg, &pt);
                break;
    
            case WM_KEYDOWN:
                switch (wParam) {
                    case VK_ESCAPE:
                        if (fDrawing) {
                            if (fRubberBanding) {
                                EndRubberBanding(hwnd);
                                WorkUpdate();
                            }
    
                            PropBarClearSize();
                            ReleaseCapture();
                            fDrawing = FALSE;
                        }
    
                        break;
                }
    
                break;
    
            default:
                return DefWindowProc(hwnd, msg, wParam, lParam);
        }
    
        return 0;
    }
    
    
    
    /****************************************************************************
    * WorkPaint
    *
    * Handles WM_PAINT for the workspace window.
    *
    * History:
    *
    ****************************************************************************/
    
    STATICFN VOID NEAR WorkPaint(    HWND hwnd)
    {
        register INT i;
        PAINTSTRUCT ps;
        HCURSOR hcurOld;
        HDC hdcTemp;
        HBITMAP hbmTemp;
    
        hcurOld = SetCursor(hcurWait);
        BeginPaint(hwnd, &ps);
    
        /*
         * Do they want a grid and is there enough room to show the lines?
         */
        if (gfGrid && gZoomFactor > 1) {
            /*
             * Stretch the bits onto a temporary DC.
             */
            hdcTemp = CreateCompatibleDC(ghdcImage);
            hbmTemp = CreateCompatibleBitmap(ghdcImage,
                    gcxWorkSpace, gcyWorkSpace);
            SelectObject(hdcTemp, hbmTemp);
            StretchBlt(hdcTemp, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    ghdcImage, 0, 0, gcxImage, gcyImage, SRCCOPY);
    
            /*
             * Draw the grid lines on the temp DC.
             */
            for (i = gZoomFactor - 1; i < gcxWorkSpace; i += gZoomFactor)
                PatBlt(hdcTemp, i, 0, 1, gcyWorkSpace, BLACKNESS);
            for (i = gZoomFactor - 1; i < gcyWorkSpace; i += gZoomFactor)
                PatBlt(hdcTemp, 0, i, gcxWorkSpace, 1, BLACKNESS);
    
            /*
             * Copy the bits to the screen.
             */
            BitBlt(ps.hdc, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    hdcTemp, 0, 0, SRCCOPY);
    
            DeleteDC(hdcTemp);
            DeleteObject(hbmTemp);
        }
        else {
            /*
             * No grid.  Just stretch the image to the screen.
             */
            StretchBlt(ps.hdc, 0, 0, gcxWorkSpace, gcyWorkSpace,
                    ghdcImage, 0, 0, gcxImage, gcyImage, SRCCOPY);
        }
    
        EndPaint(hwnd, &ps);
        SetCursor(hcurOld);
    }
    
    
    
    /****************************************************************************
    * WorkUpdate
    *
    * This function updates the workspace window.
    *
    * History:
    *
    ****************************************************************************/
    
    VOID WorkUpdate(VOID)
    {
        /*
         * Invalidate the workspace window.  Because the image will be
         * be blt'ed onto it, we do not need to force the background to
         * be cleared first.
         */
        InvalidateRect(ghwndWork, NULL, FALSE);
    }
    
    
    
    /****************************************************************************
    * WorkReset
    *
    * This function reset the workspace window.  It should be called
    * any time that a new image is loaded (because the size could
    * change) or the size of the main window changes (because the
    * workspace window needs to be resized to fit).
    *
    * History:
    *
    ****************************************************************************/
    
    VOID WorkReset(VOID)
    {
        RECT rcClient;
        INT cx;
        INT cy;
        INT xScale;
        INT yScale;
        INT cxBorder;
        INT cyBorder;
    
        cxBorder = GetSystemMetrics(SM_CXBORDER);
        cyBorder = GetSystemMetrics(SM_CYBORDER);
    
        if (!gcxImage || !gcyImage) {
            gZoomFactor = 1;
        }
        else {
            GetClientRect(ghwndMain, &rcClient);
            cx = rcClient.right - (2 * PALETTEMARGIN) - (2 * cxBorder);
            cy = rcClient.bottom - (2 * PALETTEMARGIN) - (2 * cyBorder) -
                    gcyPropBar;
    
            xScale = cx / gcxImage;
            yScale = cy / gcyImage;
    
            if (xScale > 0 && yScale > 0)
                gZoomFactor = min(xScale, yScale);
            else
                gZoomFactor = 1;
        }
    
        SetWindowPos(ghwndWork, NULL,
                PALETTEMARGIN, PALETTEMARGIN + gcyPropBar,
                (gZoomFactor * gcxImage) + (2 * cxBorder),
                (gZoomFactor * gcyImage) + (2 * cyBorder),
                SWP_NOACTIVATE | SWP_NOZORDER);
        WorkUpdate();
    }
    
    
    
    /************************************************************************
    * WorkButtonDown
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID WorkButtonDown(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        /*
         * If the other button is already down, just ignore this one.
         */
        if (fDrawing)
            return;
    
        SetFocus(hwnd);
        fLeftButtonDown = (msg == WM_LBUTTONDOWN) ? TRUE : FALSE;
    
        SnapPointToGrid(ppt);
        ptStart = ptPrev = ptEnd = *ppt;
    
        if (fLeftButtonDown) {
            ghbrDraw = ghbrLeft;
            ghbrDrawSolid = ghbrLeftSolid;
            gfDrawMode = gfModeLeft;
            ghpenDraw = ghpenLeft;
        }
        else {
            ghbrDraw = ghbrRight;
            ghbrDrawSolid = ghbrRightSolid;
            gfDrawMode = gfModeRight;
            ghpenDraw = ghpenRight;
        }
    
        /*
         * If this tool draws on the down-click, update the undo
         * buffer now.
         */
        if (gaTools[gCurTool].fDrawOnDown)
            ImageUpdateUndo();
    
        SetCapture(ghwndWork);
        fDrawing = TRUE;
    
        (*gpfnDrawProc)(hwnd, msg, *ppt);
    
        PropBarSetSize(ptStart, ptEnd);
    }
    
    
    
    /************************************************************************
    * WorkButtonMouseMove
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID WorkButtonMouseMove(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        static POINT ptNZLast;  // Saves the last point (non-zoomed).
        POINT ptNZ;
    
        SetCursor(gaTools[gCurTool].hcur);
    
        SnapPointToGrid(ppt);
    
        /*
         * Calculate the point as it would be on the actual image
         * (non-zoomed).
         */
        ptNZ.x = ppt->x / gZoomFactor;
        ptNZ.y = ppt->y / gZoomFactor;
    
        /*
         * Only call the drawing proc if the point changed enough to
         * jump over a zoomed pixels width (it jumped a grid square).
         * This prevents calling the DrawProc for a mouse move of
         * a single pixel (unless the zoom factor is 1, of course).
         */
        if (ptNZLast.x != ptNZ.x || ptNZLast.y != ptNZ.y) {
            ptEnd = *ppt;
            (*gpfnDrawProc)(hwnd, msg, *ppt);
            ptPrev = ptEnd;
    
            PropBarSetPos(ptNZ.x, ptNZ.y);
    
            if (fDrawing)
                PropBarSetSize(ptStart, ptEnd);
    
            ptNZLast = ptNZ;
        }
    }
    
    
    
    /************************************************************************
    * WorkButtonUp
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID WorkButtonUp(
        HWND hwnd,
        UINT msg,
        PPOINT ppt)
    {
        /*
         * Pass this on to the draw procs, but only if we are still drawing.
         * The drawing could have been cancelled by the Escape key, in
         * which case we just ignore the button up message.
         */
        if (fDrawing) {
            SnapPointToGrid(ppt);
    
            /*
             * If this tool draws on the up-click, update the undo
             * buffer now.
             */
            if (gaTools[gCurTool].fDrawOnUp)
                ImageUpdateUndo();
    
            (*gpfnDrawProc)(hwnd, msg, *ppt);
    
            ReleaseCapture();
            fDrawing = FALSE;
        }
    }
    
    
    
    /******************************************************************************
     * SnapPointToGrid
     *
     * PURPOSE : Snap the current mouse coordinate to the nearest grid intersection.
     *
     * PARAMS  : PPOINT ppt : current mouse coordinates
     *
     *****************************************************************************/
    
    STATICFN VOID NEAR SnapPointToGrid(
        PPOINT ppt)
    {
        /*
         * Scale the point down (this gridizes it at the same time).
         */
        ppt->x = ppt->x / gZoomFactor;
        ppt->y = ppt->y / gZoomFactor;
    
        /*
         * Limit the point to within the image.
         */
        if (ppt->x < 0)
            ppt->x = 0;
    
        if (ppt->y < 0)
            ppt->y = 0;
    
        if (ppt->x >= gcxImage)
            ppt->x = gcxImage - 1;
    
        if (ppt->y >= gcyImage)
            ppt->y = gcyImage - 1;
    
        /*
         * Finally, scale it back up to the workspace window size.
         */
        ppt->x *= gZoomFactor;
        ppt->y *= gZoomFactor;
    }
    
    
    
    /************************************************************************
    * PencilDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    VOID PencilDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                DrawPoint(hwnd, &ptNew, FALSE);
                break;
    
            case WM_MOUSEMOVE:
                if (fDrawing)
                    DrawToPoint(hwnd, &ptNew, FALSE);
    
                break;
        }
    }
    
    
    
    /************************************************************************
    * BrushDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    VOID BrushDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                DrawPoint(hwnd, &ptNew, TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (fDrawing)
                    DrawToPoint(hwnd, &ptNew, TRUE);
    
                break;
        }
    }
    
    
    
    /************************************************************************
    * DrawToPoint
    *
    * This function draws from the previous point to the given point.
    * This includes all points between.
    *
    * The global ptPrev must have been initialized prior to the  first time
    * this function is called during a drawing operation.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID DrawToPoint(
        HWND hwnd,
        PPOINT ppt,
        BOOL fBrush)
    {
        DELTAGEN dg;
        BOOL fContinue;
        POINT pt;
        INT x;
        INT y;
    
        x = ppt->x / gZoomFactor;
        y = ppt->y / gZoomFactor;
        DeltaGenInit(&dg, ptPrev.x / gZoomFactor, ptPrev.y / gZoomFactor,
                ppt->x / gZoomFactor, ppt->y / gZoomFactor, &x, &y);
        do {
            pt.x = x * gZoomFactor;
            pt.y = y * gZoomFactor;
            DrawPoint(hwnd, &pt, fBrush);
            fContinue = DeltaGen(&dg, &x, &y);
        } while (fContinue);
    }
    
    
    
    /***************************** Public  Function ****************************\
    * DeltaGenInit
    *
    * This routine initializes the pdg, px and py in preparation for using
    * DeltaGen().  Returns fContinue.
    *
    * Algorithm derived from BRESENHAM line algorighm on p. 435 of Fund. of
    * interactive computer graphics, Foley/VanDam, addison-wesley 1983.
    *
    * History:
    *       3/7/89  sanfords        created
    \***************************************************************************/
    
    STATICFN BOOL NEAR DeltaGenInit(
        PDELTAGEN pdg,
        INT x0,
        INT y0,
        INT xf,
        INT yf,
        PINT px,
        PINT py)
    {
        INT nT;
    
        pdg->xf = xf;
        pdg->yf = yf;
    
        if (x0 == xf && y0 == yf)
            return FALSE;
    
        if (xf >= x0)
            pdg->incx = 1;
        else
            pdg->incx = -1;
    
        if (yf >= y0)
            pdg->incy = 1;
        else
            pdg->incy = -1;
    
        pdg->dx = (xf - x0) * pdg->incx;
        pdg->dy = (yf - y0) * pdg->incy;
    
        if (pdg->dy > pdg->dx) {
            nT = pdg->dy;
            pdg->dy = pdg->dx;
            pdg->dx = nT;
            nT = pdg->incx;
            pdg->incx = pdg->incy;
            pdg->incy = nT;
            pdg->fSwap = TRUE;
        }
        else {
            pdg->fSwap = FALSE;
        }
    
        pdg->inc1 = pdg->dy * 2;
        pdg->inc2 = (pdg->dy - pdg->dx) * 2;
        pdg->d = pdg->inc1 - pdg->dx;
    
        pdg->xf = xf;
        pdg->yf = yf;
    
        *px = x0;
        *py = y0;
    
        return TRUE;
    }
    
    
    
    /***************************** Public  Function ****************************\
    * DeltaGen
    *
    * This routine generates the next coordinates for px,py assuming that this
    * point is proceeding linearly from x0,y0 to xf, yf.  It returns FALSE only
    * if *px == xf and *py == yf on entry.  (ie returns fContinue)  pdg should
    * have been previously set by DeltaGenInit().
    *
    * Algorithm derived from BRESENHAM line algorighm on p. 435 of Fund. of
    * interactive computer graphics, Foley/VanDam, addison-wesley 1983.
    *
    * History:
    *       3/7/89  sanfords        created
    \***************************************************************************/
    
    STATICFN BOOL NEAR DeltaGen(
        PDELTAGEN pdg,
        PINT px,
        PINT py)
    {
        PINT pnT;
    
        if ((*px == pdg->xf) && (*py == pdg->yf))
            return FALSE;
    
        if (pdg->fSwap) {
            pnT = px;
            px = py;
            py = pnT;
        }
    
        *px += pdg->incx;
        if (pdg->d < 0) {
            pdg->d += pdg->inc1;
        }
        else {
            *py += pdg->incy;
            pdg->d += pdg->inc2;
        }
    
        return TRUE;
    }
    
    
    
    /************************************************************************
    * DrawPoint
    *
    * This function is called to draw a point on the image.  It is used
    * by the Pencil and Brush tools.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID DrawPoint(
        HWND hwnd,
        PPOINT ppt,
        BOOL fBrush)
    {
        HDC hDC;
        HBRUSH hbrOld;
        INT wx;
        INT wy;
        INT iStartY;
        INT wStep;
        INT i;
        INT j;
        INT nBrushSize;
    
        if (ppt->x < 0 || ppt->y < 0)
            return;
    
        hDC = GetDC(hwnd);
    
        /*
         * If this is a point from the brush tool, use the current
         * brush width.  Otherwise, draw a single pixel point.
         */
        if (fBrush)
            nBrushSize = gnBrushSize;
        else
            nBrushSize = 1;
    
        /*
         * Determine some starting factors, then draw the point in
         * the workspace window.
         */
        hbrOld = SelectObject(hDC, ghbrDrawSolid);
        wy = iStartY = ppt->y - (ppt->y % gZoomFactor)
                -(nBrushSize / 2) * gZoomFactor;
        wx = ppt->x - (ppt->x % gZoomFactor)
                -(nBrushSize / 2) * gZoomFactor;
        wStep = gZoomFactor;
    
        if (gfGrid && gZoomFactor > 1)
            wStep -= 1;
    
        for (i = 0; i < nBrushSize; i++, wx += gZoomFactor) {
            wy = iStartY;
            for (j = 0; j < nBrushSize; j++, wy += gZoomFactor)
                PatBlt(hDC, wx, wy, wStep, wStep, PATCOPY);
        }
    
        SelectObject(hDC, hbrOld);
        ReleaseDC(hwnd, hDC);
    
        /*
         * Set the point in the bitmap directly as an optimization.
         */
        wx = ppt->x / gZoomFactor;
        wy = ppt->y / gZoomFactor;
        if (wx < gcxImage && wy < gcyImage) {
            hbrOld = SelectObject(ghdcImage, ghbrDrawSolid);
            PatBlt(ghdcImage, wx - nBrushSize / 2, wy - nBrushSize / 2,
                    nBrushSize, nBrushSize, PATCOPY);
    
            if (giType != FT_BITMAP) {
                /*
                 * If in color mode, set the mask bits black.  Otherwise make
                 * them white.
                 */
                PatBlt(ghdcANDMask, wx - nBrushSize / 2, wy - nBrushSize / 2,
                        nBrushSize, nBrushSize,
                        (gfDrawMode == MODE_COLOR) ? BLACKNESS : WHITENESS);
            }
    
            SelectObject(ghdcImage, hbrOld);
    
            /*
             * Draw the point in the view window directly as an optimization.
             */
            if (ghwndView)
                ViewSetPixel(wx, wy, nBrushSize);
        }
    
        /*
         * Mark the image as changed.
         */
        fImageDirty = TRUE;
    }
    
    
    
    /************************************************************************
    * PickDP
    *
    * Drawing proc that selects a rectangular portion of the image.
    * It updates the global picking rectangle.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    VOID PickDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
    
        switch (msg) {
            case WM_LBUTTONDOWN:
            case WM_RBUTTONDOWN:
                /* erase any previous ghost rectangle */
                WorkUpdate();
                UpdateWindow(ghwndWork);
    
                /*
                 * Initialize the pick rectangle to cover the entire screen.
                 */
                PickSetRect(0, 0, gcxImage - 1, gcyImage - 1);
    
                StartRubberBanding(hwnd);
                RubberBandRect(TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandRect(FALSE);
    
                break;
    
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                EndRubberBanding(hwnd);
    
                /*
                 * Flip the points (if needed) and scale down.
                 */
                ptTL = ptStart;
                ptBR = ptEnd;
                NormalizePoints(&ptTL, &ptBR);
                ptTL.x /= gZoomFactor;
                ptTL.y /= gZoomFactor;
                ptBR.x /= gZoomFactor;
                ptBR.y /= gZoomFactor;
    
                PickSetRect(ptTL.x, ptTL.y, ptBR.x, ptBR.y);
    
                break;
        }
    }
    
    
    
    /******************************************************************************
     * VOID LineDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw a straight line according to tracking line.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          unsigned msg  : Upper left corner of rect;
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/
    
    VOID LineDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        INT sx;
        INT sy;
        INT ex;
        INT ey;
        HPEN hpen;
        HPEN hpenOld;
        HBRUSH hbrOld;
    
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandLine(TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandLine(FALSE);
    
                break;
    
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                EndRubberBanding(hwnd);
    
                /* transform selected coordinates to those of actual image */
                sx = ptStart.x / gZoomFactor;
                sy = ptStart.y / gZoomFactor;
                ex = ptEnd.x / gZoomFactor;
                ey = ptEnd.y / gZoomFactor;
    
                hpenOld = SelectObject(ghdcImage, ghpenDraw);
                MoveToEx(ghdcImage, sx, sy, NULL);
                LineTo(ghdcImage, ex, ey);
                SelectObject(ghdcImage, hpenOld);
    
                if (giType != FT_BITMAP) {
                    /* for icons and cursors draw the line on the AND DC (memory)
                     * in black (if in color mode) or white (otherwise)
                     */
                    hpen = CreatePen(PS_INSIDEFRAME, 1,
                            (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                    hpenOld = SelectObject(ghdcANDMask, hpen);
                    hbrOld = SelectObject(ghdcANDMask, GetStockObject(NULL_BRUSH));
                    MoveToEx(ghdcANDMask, sx, sy, NULL);
                    LineTo(ghdcANDMask, ex, ey);
                    SelectObject(ghdcANDMask, hbrOld);
                    SelectObject(ghdcANDMask, hpenOld);
                    DeleteObject(hpen);
                }
    
                /*
                 * Because the LineTo function does not draw the ending
                 * point, we must do it manually here.
                 */
                DrawPoint(hwnd, &ptEnd, FALSE);
    
                fImageDirty = TRUE;
    
                ViewUpdate();
    
                break;
        }
    }
    
    
    
    /************************************************************************
    * RubberBandLine
    *
    * This function erases the old line and draws the new tracking line
    * when using the "Line" tool.
    *
    * Arguments:
    *   BOOL fFirstTime - TRUE if starting to track the line (it doesn't
    *                     need to erase the old line).
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID NEAR RubberBandLine(
        BOOL fFirstTime)
    {
        INT nOffset;
    
        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);
    
        /*
         * If we are magnifying the image at all, the line needs to be
         * slightly offset so that it will be draw exactly in between
         * the grid lines.
         */
        if (gZoomFactor > 1)
            nOffset = -1;
        else
            nOffset = 0;
    
        if (!fFirstTime) {
            /*
             * Erase the old line.
             */
            
            MoveToEx(hdcRubberBand, ptStart.x + (gZoomFactor / 2) + nOffset,
                    ptStart.y + (gZoomFactor / 2) + nOffset, NULL);
            LineTo(hdcRubberBand, ptPrev.x + (gZoomFactor / 2) + nOffset,
                    ptPrev.y + (gZoomFactor / 2) + nOffset);
        }
    
        /*
         * Draw the new one.
         */
        
        MoveToEx(hdcRubberBand, ptStart.x + (gZoomFactor / 2) + nOffset,
                ptStart.y + (gZoomFactor / 2) + nOffset, NULL);
        LineTo(hdcRubberBand, ptEnd.x + (gZoomFactor / 2) + nOffset,
                ptEnd.y + (gZoomFactor / 2) + nOffset);
    }
    
    
    
    /******************************************************************************
     * VOID RectDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw a rectangle (filled/hollow) in the area specified
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          WORD   msg  :
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/
    
    VOID RectDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandRect(TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandRect(FALSE);
    
                break;
    
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                RectDPDraw(hwnd);
                break;
        }
    }
    
    
    
    /************************************************************************
    * RectDPDraw
    *
    * Does the final drawing of a rectangle when using the Rectangle tool.
    *
    * Arguments:
    *   HWND hwnd - Window handle to the workspace.
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID NEAR RectDPDraw(
        HWND hwnd)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
        HBRUSH hbr;
        HBRUSH hbrOld;
        HPEN hpen;
        HPEN hpenOld;
        INT nOutset;
    
        EndRubberBanding(hwnd);
    
        /*
         * Flip the points (if needed) and scale down.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        ptTL.x /= gZoomFactor;
        ptTL.y /= gZoomFactor;
        ptBR.x /= gZoomFactor;
        ptBR.y /= gZoomFactor;
    
        if (gCurTool == TOOL_RECT) {
            hpen = ghpenDraw;
            hbr = GetStockObject(NULL_BRUSH);
            nOutset = 1;
        }
        else {
            hpen = GetStockObject(NULL_PEN);
            hbr = ghbrDraw;
            nOutset = 2;
        }
    
        hpenOld = SelectObject(ghdcImage, hpen);
        hbrOld = SelectObject(ghdcImage, hbr);
        Rectangle(ghdcImage, ptTL.x, ptTL.y,
                ptBR.x + nOutset, ptBR.y + nOutset);
        SelectObject(ghdcImage, hpenOld);
        SelectObject(ghdcImage, hbrOld);
    
        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            if (gCurTool == TOOL_RECT) {
                hpen = CreatePen(PS_INSIDEFRAME, 1,
                        (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                hbr = GetStockObject(NULL_BRUSH);
            }
            else {
                hpen = GetStockObject(NULL_PEN);
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);
            }
    
            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Rectangle(ghdcANDMask, ptTL.x, ptTL.y,
                    ptBR.x + nOutset, ptBR.y + nOutset);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);
    
            if (gCurTool == TOOL_RECT)
                DeleteObject(hpen);
        }
    
        fImageDirty = TRUE;
    
        ViewUpdate();
    }
    
    
    
    /******************************************************************************
     * VOID PASCAL RubberBandRect()
     *
     * PURPOSE: Draw rubberbanding rect.
     *
     * PARAMS : HANDLE hDst : handle to dest. DC
     *
     *****************************************************************************/
    
    STATICFN VOID NEAR RubberBandRect(
        BOOL fFirstTime)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
    
        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);
    
        if (!fFirstTime) {
            /*
             * Erase the old rectangle.
             */
            ptTL = ptStart;
            ptBR = ptPrev;
            NormalizePoints(&ptTL, &ptBR);
            Rectangle(hdcRubberBand, ptTL.x, ptTL.y,
                    ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
        }
    
    
        /*
         * Draw the new one.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        Rectangle(hdcRubberBand, ptTL.x, ptTL.y,
                ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
    }
    
    
    
    /******************************************************************************
     * VOID CircleDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Draw an ellipse (filled/hollow) in the area specified.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          unsigned msg  : Upper left corner of rect;
     *          POINT  ptNew   : end pt. of line
     *
     * SIDE EFFECTS: may change bits in image DC
     *
     *****************************************************************************/
    
    VOID CircleDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                StartRubberBanding(hwnd);
                RubberBandCircle(TRUE);
                break;
    
            case WM_MOUSEMOVE:
                if (fRubberBanding)
                    RubberBandCircle(FALSE);
    
                break;
    
            case WM_LBUTTONUP:
            case WM_RBUTTONUP:
                CircleDPDraw(hwnd);
                break;
        }
    }
    
    
    
    /************************************************************************
    * CircleDPDraw
    *
    * Does the final drawing of an ellipse when using the Ellipse tool.
    *
    * Arguments:
    *   HWND hwnd - Window handle to the workspace.
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID NEAR CircleDPDraw(
        HWND hwnd)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
        HBRUSH hbr;
        HBRUSH hbrOld;
        HPEN hpen;
        HPEN hpenOld;
        INT nOutset;
    
        EndRubberBanding(hwnd);
    
        /*
         * Flip the points (if needed) and scale down.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        ptTL.x /= gZoomFactor;
        ptTL.y /= gZoomFactor;
        ptBR.x /= gZoomFactor;
        ptBR.y /= gZoomFactor;
    
    #ifdef WIN16
        /*
         * The win 3.x code does not properly draw an ellipse if it
         * has a NULL pen selected in (to not draw the border).  For
         * this platform, we must select in the drawing pen.  This is
         * not necessary for NT (we can use a NULL pen to avoid
         * drawing the solid border).
         */
    
        if (gCurTool == TOOL_CIRCLE)
            hbr = GetStockObject(NULL_BRUSH);
        else
            hbr = ghbrDraw;
    
        hpenOld = SelectObject(ghdcImage, ghpenDraw);
        hbrOld = SelectObject(ghdcImage, hbr);
        Ellipse(ghdcImage, ptTL.x, ptTL.y, ptBR.x + 1, ptBR.y + 1);
        SelectObject(ghdcImage, hbrOld);
        SelectObject(ghdcImage, hpenOld);
    
        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            hpen = CreatePen(PS_INSIDEFRAME, 1,
                    (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
    
            if (gCurTool == TOOL_CIRCLE)
                hbr = GetStockObject(NULL_BRUSH);
            else
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);
    
            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Ellipse(ghdcANDMask, ptTL.x, ptTL.y, ptBR.x + 1, ptBR.y + 1);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);
            DeleteObject(hpen);
        }
    
    #else
    
        if (gCurTool == TOOL_CIRCLE) {
            hpen = ghpenDraw;
            hbr = GetStockObject(NULL_BRUSH);
            nOutset = 1;
        }
        else {
            hpen = GetStockObject(NULL_PEN);
            hbr = ghbrDraw;
            nOutset = 2;
        }
    
        hpenOld = SelectObject(ghdcImage, hpen);
        hbrOld = SelectObject(ghdcImage, hbr);
        Ellipse(ghdcImage, ptTL.x, ptTL.y, ptBR.x + nOutset, ptBR.y + nOutset);
        SelectObject(ghdcImage, hpenOld);
        SelectObject(ghdcImage, hbrOld);
    
        if (giType != FT_BITMAP) {
            /* for icons and cursors draw the shape on the AND DC (memory)
             * in black (if in color mode) or white (otherwise)
             */
            if (gCurTool == TOOL_CIRCLE) {
                hpen = CreatePen(PS_INSIDEFRAME, 1,
                        (gfDrawMode == MODE_COLOR) ? RGB_BLACK : RGB_WHITE);
                hbr = GetStockObject(NULL_BRUSH);
            }
            else {
                hpen = GetStockObject(NULL_PEN);
                hbr = GetStockObject((gfDrawMode == MODE_COLOR) ?
                        BLACK_BRUSH : WHITE_BRUSH);
            }
    
            hpenOld = SelectObject(ghdcANDMask, hpen);
            hbrOld = SelectObject(ghdcANDMask, hbr);
            Ellipse(ghdcANDMask, ptTL.x, ptTL.y, ptBR.x + nOutset, ptBR.y + nOutset);
            SelectObject(ghdcANDMask, hpenOld);
            SelectObject(ghdcANDMask, hbrOld);
    
            if (gCurTool == TOOL_CIRCLE)
                DeleteObject(hpen);
        }
    
    #endif
    
        fImageDirty = TRUE;
    
        ViewUpdate();
    }
    
    
    
    /******************************************************************************
     * VOID PASCAL RubberBandCircle()
     *
     * PURPOSE: Draw rubberbanding circle
     *
     *
     *****************************************************************************/
    
    STATICFN VOID NEAR RubberBandCircle(
        BOOL fFirstTime)
    {
        POINT ptTL;         // Top-Left point.
        POINT ptBR;         // Bottom-Right point
    
        /*
         * Set the raster-op to invert.
         */
        SetROP2(hdcRubberBand, R2_NOT);
    
        if (!fFirstTime) {
            /*
             * Erase the old circle.
             */
            ptTL = ptStart;
            ptBR = ptPrev;
            NormalizePoints(&ptTL, &ptBR);
            Ellipse(hdcRubberBand, ptTL.x, ptTL.y,
                    ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
        }
    
    
        /*
         * Draw the new one.
         */
        ptTL = ptStart;
        ptBR = ptEnd;
        NormalizePoints(&ptTL, &ptBR);
        Ellipse(hdcRubberBand, ptTL.x, ptTL.y,
                ptBR.x + gZoomFactor, ptBR.y + gZoomFactor);
    }
    
    
    
    /************************************************************************
    * FloodDP
    *
    *
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    VOID FloodDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        HDC dc;
        HDC bwdc;
        HBRUSH hbrOld;
        HBITMAP bwbit;
        HCURSOR hcurSave;
    
        switch (msg) {
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDOWN:
                hcurSave = SetCursor(hcurWait);
    
                dc = GetDC(hwnd);
                /* create temporary DC */
                bwdc = CreateCompatibleDC(dc);
    
                /* create temporary monochrome bitmap */
                if (!(bwbit = CreateBitmap(gcxImage, gcyImage, 1, 1, NULL))) {
                    DeleteDC(bwdc);
                    ReleaseDC(hwnd, dc);
                    Message(MSG_OUTOFMEMORY);
                    return;
                }
                SelectObject(bwdc, bwbit);
    
                /*  Set background color of image DC to desired floodfill color.*/
                SetBkColor(ghdcImage,
                        GetPixel(ghdcImage, (ptNew.x / gZoomFactor),
                        (ptNew.y / gZoomFactor)));
    
                /******* OPERATION 0 ******/
                /* First create a monochrome mask of the image after setting background
                 * color to the floodfill color. This will make the region to be
                 * flooded white(background), and it's boundary black (foreground) in the
                 * mask.
                 */
                BitBlt(bwdc, 0, 0, gcxImage, gcyImage, ghdcImage, 0, 0, SRCCOPY);
    
                /******* OPERATION 1 ******/
                /* floodfill selected region in mask (which is white bounded by black)
                 * with black.
                 */
                SelectObject(bwdc, GetStockObject(BLACK_BRUSH));
                ExtFloodFill(bwdc, ptNew.x / gZoomFactor,
                        ptNew.y / gZoomFactor, RGB_BLACK, FLOODFILLBORDER);
    
                /******* OPERATION 2 ******/
                /* Now XOR the original image on the mask , inverting the
                 * flood-filled pixels on mask (black --> white).
                 */
                BitBlt(bwdc, 0, 0, gcxImage, gcyImage, ghdcImage, 0, 0, SRCINVERT);
    
                /* the AND mask needs to be updated only if in screen or inverse mode */
                if ((giType == FT_CURSOR) || (giType == FT_ICON)) {
                    if (gfDrawMode == MODE_COLOR) {
                        SetBkColor(ghdcANDMask, RGB(0, 0, 0));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSna);
                        SelectObject(ghdcANDMask, GetStockObject(BLACK_BRUSH));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSPao);
                    }
                    else {
                        SetBkColor(ghdcANDMask, RGB(0xff, 0xff, 0xff));                    BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSna);
                        SelectObject(ghdcANDMask, GetStockObject(WHITE_BRUSH));
                        BitBlt(ghdcANDMask, 0, 0, gcxImage, gcyImage, bwdc,
                                0, 0, ROP_DSPao);
                    }
                }
    
                SetBkColor(ghdcImage, RGB_WHITE);
                /****** OPERATION 3 ******/
                /* The following operation turns the flooded area on-screen black,
                 * on the image, preserving the rest of the it.
                 */
                BitBlt(ghdcImage, 0, 0, gcxImage, gcyImage, bwdc, 0, 0, ROP_DSna);
    
                /****** OPERATION 4 ******/
                /* Rop_DSPao ANDs the pattern (current brush which is the floodfill
                 * color) on the source making flooded area (which was white as a
                 * result of operation 2 ) the current brush color, and keeps the rest
                 * of the source black. The source is then ORed into the original image
                 * (whose flooded area is black as a result of operation 3) to get
                 * the desired end result.
                 */
                hbrOld = SelectObject(ghdcImage, ghbrDraw);
                BitBlt(ghdcImage, 0, 0, gcxImage, gcyImage, bwdc, 0, 0, ROP_DSPao);
                SelectObject(ghdcImage, hbrOld);
    
                /* clean up */
                DeleteDC(bwdc);
                DeleteObject(bwbit);
                ReleaseDC(hwnd, dc);
    
                /*
                 * Mark the image as changed.
                 */
                fImageDirty = TRUE;
    
                ViewUpdate();
    
                SetCursor(hcurSave);
    
                break;
        }
    }
    
    
    
    /******************************************************************************
     * VOID HotSpotDP(hwnd, msg, ptNew)
     *
     * PURPOSE: Sets the hotspot.
     *
     * PARAMS : HWND   hwnd : handle to dest. DC
     *          WORD   msg  :
     *          POINT  ptNew   : end pt.
     *
     *****************************************************************************/
    
    VOID HotSpotDP(
        HWND hwnd,
        UINT msg,
        POINT ptNew)
    {
        switch (msg) {
            case WM_LBUTTONDOWN:
                PropBarSetHotSpot(ptNew.x / gZoomFactor, ptNew.y / gZoomFactor);
                break;
    
            case WM_MOUSEMOVE:
                if (fDrawing && fLeftButtonDown)
                    PropBarSetHotSpot(ptNew.x / gZoomFactor,
                            ptNew.y / gZoomFactor);
    
                break;
    
            case WM_LBUTTONUP:
                MarkHotSpotPosition(ptNew.x / gZoomFactor, ptNew.y / gZoomFactor);
                break;
        }
    }
    
    
    
    /************************************************************************
    * MarkHotSpotPosition
    *
    * Updates the hotspot location in the currently selected cursor image.
    *
    * Arguments:
    *
    * History:
    *
    ************************************************************************/
    
    STATICFN VOID NEAR MarkHotSpotPosition(
        INT x,
        INT y)
    {
        gpImageCur->iHotspotX = x;
        gpImageCur->iHotspotY = y;
        PropBarSetHotSpot(x, y);
    
        /*
         * Mark the image as changed.
         */
        fImageDirty = TRUE;
    }
    
    
    
    /******************************************************************************
     * NormalizePoints
     *
     * PURPOSE : interchange start and end pts
     *           if start point is > end point.
     *
     *****************************************************************************/
    
    VOID NormalizePoints(
        PPOINT pptStart,
        PPOINT pptEnd)
    {
        INT n;
    
        if (pptStart->x > pptEnd->x) {
            n = pptEnd->x;
            pptEnd->x = pptStart->x;
            pptStart->x = n;
        }
    
        if (pptStart->y > pptEnd->y) {
            n = pptEnd->y;
            pptEnd->y = pptStart->y;
            pptStart->y = n;
        }
    }
    
    
    
    /******************************************************************************
     * HDC PASCAL StartRubberBanding(hwnd)
     *
     * PURPOSE: Sets up rubberbanding for all tools.
     *
     * PARAMS : HANDLE hDst : handle to box DC
     *
     * RETURNS :handle to destination display context
     *
     * SIDE EFFECTS: alters a few global flags for tracking
     *
     *****************************************************************************/
    
    STATICFN VOID NEAR StartRubberBanding(
        HWND hwnd)
    {
        hdcRubberBand = GetDC(hwnd);
    
        /*
         * Select a white pen, and a null brush (prevents drawing the
         * interior of rectangles and ellipses).
         */
        SelectObject(hdcRubberBand, GetStockObject(WHITE_PEN));
        SelectObject(hdcRubberBand, GetStockObject(NULL_BRUSH));
    
        fRubberBanding = TRUE;
    }
    
    
    
    /******************************************************************************
     * VOID PASCAL EndRubberBanding()
     *
     * PURPOSE: Stops rubberbanding rect. and cleans up
     *
     *****************************************************************************/
    
    STATICFN VOID NEAR EndRubberBanding(
        HWND hwnd)
    {
        ReleaseDC(hwnd, hdcRubberBand);
        fRubberBanding = FALSE;
    }
