/*
======================================================================
imgwnd.c

Ernie Wright  11 Apr 05
MSVC 4.0

Functions that manage the the child window on which the image is
drawn.
====================================================================== */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "qv.h"


/*
======================================================================
update_frame()

Redraw the image display.

INPUTS
   hwnd           the image frame window

Invalidates the image frame, which generates a WM_PAINT.  This doesn't
do much, but it's here in case we want to do more with it later.
====================================================================== */

static void update_frame( HWND hwnd )
{
   ImageInfo *im;

   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
   InvalidateRect( hwnd, NULL, FALSE );
   UpdateWindow( hwnd );
   statusZoom( im->izoom );
}


/*
======================================================================
mouseImageCoords()

Determine which image pixel is under the mouse cursor.

INPUTS
   hwnd           the image frame
   pmouse         window-relative coordinates
   use_pmouse     value in pmouse is valid

If the mouse position relative to the upper left corner of the window
is known (as it will be when processing a mouse message sent to the
window procedure), that position is passed in pmouse, and use_pmouse
is true.  Otherwise the screen position is found using GetCursorPos()
and translated to a window-relative position.

If an image is being displayed (the slot isn't empty) and the cursor
is within the image extent, its position is scaled by the zoom factor
and translated by the scroll values, then written back into pmouse,
and the function returns true.  Otherwise it returns false, and pmouse
is unchanged.
====================================================================== */

int mouseImageCoords( HWND hwnd, POINTS *pmouse, int use_pmouse )
{
   ImageInfo *im;
   POINTS mouse;
   RECT r;

   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   /* empty image */

   if ( im->disp == NULL )
      return 0;

   /* if !use_pmouse, need to find the mouse */

   if ( !use_pmouse ) {
      POINT pt;
      GetClientRect( hwnd, &r );
      GetCursorPos( &pt );
      ScreenToClient( hwnd, &pt );
      if ( pt.x < 0 || pt.x >= r.right || pt.y < 0 || pt.y >= r.bottom )
         return 0;
      mouse.x = ( SHORT ) pt.x;
      mouse.y = ( SHORT ) pt.y;
   }
   else {
      mouse.x = pmouse->x;
      mouse.y = pmouse->y;
   }

   /* mouse outside the image */

   if ( mouse.x < 0 || mouse.x >= ( int )( im->width * im->zoom ) ||
        mouse.y < 0 || mouse.y >= ( int )( im->height * im->zoom ))
      return 0;

   /* image coordinates */

   pmouse->x = ( SHORT )(( im->curScroll.x + mouse.x ) / im->zoom );
   pmouse->y = ( SHORT )(( im->curScroll.y + mouse.y ) / im->zoom );
   return 1;
}


/*
======================================================================
update_mouse()

Update the coordinate and pixel value displays in the status bar.

INPUTS
   hwnd           the image frame
   pmouse         the current mouse position (may be NULL if unknown)

The coordinates display shows the (x,y) pixel coordinates of the point
in the image under the mouse cursor.  The pixel value display shows
the RGB intensities of that pixel.
====================================================================== */

static void update_mouse( HWND hwnd, POINTS *mouse )
{
   ImageInfo *im;
   POINTS pt;

   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   if ( mouse ) pt = *mouse;
   if ( !mouseImageCoords( hwnd, &pt, mouse != NULL ))
      statusPixel( NULL, -1, 0, 0 );
   else
      statusPixel( im->disp, pt.x, pt.y, im->channel );
}


/*
======================================================================
imvUpdateImage()

Notifies the draw window that the ImageInfo has changed (a new image
or channel, for example).  It turns out we can synchronize with the
new image data by just calling imvUpdateZoom().
====================================================================== */

void imvUpdateImage( HWND hwnd )
{
   imvUpdateZoom( hwnd, NULL, 0 );
}


/*
======================================================================
imvUpdateZoom()

Modify the zoom and parameters affected by it.

INPUTS
   hwnd           the image frame
   pmouse         the current mouse position (may be NULL if unknown)
   message        WM_LBUTTONDOWN, WM_RBUTTONDOWN, or neither

The zoom can change in several ways.  The user can click on the image
to zoom in or out.  In this case, the mouse cursor position is known,
and the image is grown or shrunk around the selected pixel--this pixel
is centered in the image frame if possible.

If the keyboard is used to zoom, the cursor position is assumed to be
the one currently in the center of the frame window.

Zoom can also change under program control, e.g. when the main window
is first opened or when the user hits the Fit button.  In this case,
the ImageInfo izoom field will already have been set to the new value,
so the message argument will be 0, and the new center is again taken
to be the same as the current one.

We can even get here from imvNewImage().  When that happens, the image
to be displayed has changed, and what we're really updating is the
relationship of the new image to the old zoom and scroll settings.

The center is modified if it would create invalid scroll parameters.
The image is never allowed to "scroll off the display," and the scroll
amounts are constrained to multiples of the zoom factor for zoom > 1.

The image frame is sent a WM_SIZE to synchronize the scroll bars.  It
also gets repainted, and the zoom status text is updated.
====================================================================== */

void imvUpdateZoom( HWND hwnd, POINTS *pmouse, UINT message )
{
   ImageInfo *im;
   int sw, rw, sh, rh;
   float x, y, mx, my;
   RECT r;


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   /* empty image */

   if ( im->disp == NULL ) return;

   /* Increment izoom if we got here because of a mouse button message.
      If we got here some other way, izoom has already been set to the new
      value.  And if we're already at the zoom limits, do nothing */

   if ( message == WM_LBUTTONDOWN ) {
      if ( im->izoom == IZOOM_MAX ) return;
      ++im->izoom;
   }
   if ( message == WM_RBUTTONDOWN ) {
      if ( im->izoom == IZOOM_MIN ) return;
      --im->izoom;
   }

   /* the width and height of the draw window */

   GetClientRect( hwnd, &r );
   rw = r.right - r.left;
   rh = r.bottom - r.top;

   /* the window coordinates of the user's click, or the window center */

   if ( pmouse ) {
      mx = pmouse->x;
      my = pmouse->y;
   }
   else {
      mx = rw * 0.5f;
      my = rh * 0.5f;
   }

   /* the image coordinates of the user's click, or the window center:  the
      displayed image will be centered on these coordinates, if possible */

   x = ( im->curScroll.x + mx ) / im->zoom;
   y = ( im->curScroll.y + my ) / im->zoom;

   /* now we can update the zoom */

   im->zoom = ZOOMI2F( im->izoom );

   /* the new zoomed size of the image */

   sw = ( int )( im->width * im->zoom );
   sh = ( int )( im->height * im->zoom );

   /* figure out the scroll positions */

   im->curScroll.x = ( int )( x * im->zoom - rw * 0.5f + 0.5f );
   im->curScroll.x = min( im->curScroll.x, sw - rw );
   im->curScroll.x = max( im->curScroll.x, 0 );
   if ( im->curScroll.x && im->izoom > IZOOM_ONE )
      im->curScroll.x -= im->curScroll.x % ( int ) im->zoom;

   im->curScroll.y = ( int )( y * im->zoom - rh * 0.5f + 0.5f );
   im->curScroll.y = min( im->curScroll.y, sh - rh );
   im->curScroll.y = max( im->curScroll.y, 0 );
   if ( im->curScroll.y && im->izoom > IZOOM_ONE )
      im->curScroll.y -= im->curScroll.y % ( int ) im->zoom;

   /* a WM_SIZE message causes a rethink of the scroll bars */

   SendMessage( hwnd, WM_SIZE, SIZE_RESTORED, MAKELPARAM( r.right, r.bottom ));

   /* redraw the display */

   update_frame( hwnd );
   update_mouse( hwnd, pmouse );
}


/*
======================================================================
imvFitWindow()

Size the main window so the current image fits the image frame.

INPUTS
   hwnd           the image frame

The main window is centered on the desktop.  If the image at the
current zoom would be too large to fit in a window that fits on the
desktop, the zoom is changed to the largest one that is small enough
to fit.  Otherwise the main window is sized to fit the image at the
current zoom.

This is called when the window is first opened and when the user asks
for a window fit.
====================================================================== */

void imvFitWindow( HWND hwnd )
{
   ImageInfo *im;
   HWND hwndParent;
   RECT
      r0,      /* parent window  */
      r1,      /* parent client  */
      r2;      /* desktop window */
   int
      x, y,    /* parent window upper left corner */
      w, h,    /* parent window width and height  */
      dw, dh;  /* desktop window size, minus the task bar */
   float
      w2, h2;  /* scaled parent width and height, relative to the desktop */


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
   hwndParent = GetParent( hwnd );

   GetWindowRect( hwndParent, &r0 );
   GetClientRect( hwndParent, &r1 );
   SystemParametersInfo( SPI_GETWORKAREA, 0, &r2, 0 );

   /* desktop dimensions, minus the task bar */

   dw = r2.right - r2.left;
   dh = r2.bottom - r2.top;

   /* parent window margins:  the parent window is this much bigger than the
      draw window */

   w = DRAWWND_L + DRAWWND_R + ( r0.right - r0.left ) - ( r1.right - r1.left );
   h = DRAWWND_T + DRAWWND_B + ( r0.bottom - r0.top ) - ( r1.bottom - r1.top );
   h += toolbarHeight( hwndParent );
   h += statusHeight();
   h += radioHeight();

   /* scaled parent window size, relative to the desktop, at the current zoom */

   w2 = ( w + im->width * im->zoom ) / dw;
   h2 = ( h + im->height * im->zoom ) / dh;

   /* adjust the zoom until the window fits on the desktop */

   if ( w2 > 1.0 || h2 > 1.0 ) {
      if ( w2 > h2 ) {
         for ( im->izoom--; im->izoom > IZOOM_MIN; im->izoom-- ) {
            im->zoom = ZOOMI2F( im->izoom );
            if ( w + im->width * im->zoom < dw ) {
               break;
            }
         }
      }
      else {
         for ( im->izoom--; im->izoom > IZOOM_MIN; im->izoom-- ) {
            im->zoom = ZOOMI2F( im->izoom );
            if ( h + im->height * im->zoom < dh ) {
               break;
            }
         }
      }
      imvUpdateZoom( hwnd, NULL, 0 );
   }

   /* the actual parent window dimensions */

   w += ( int )( im->width * im->zoom );
   w = min( w, dw );
   w = max( w, MIN_MAINWND_X );
   h += ( int )( im->height * im->zoom );
   h = min( h, dh );
   h = max( h, MIN_MAINWND_Y );

   /* the parent window upper left corner */

   x = r2.left + ( dw - w ) / 2;
   y = r2.top + ( dh - h ) / 2;
   if ( x < 0 ) x = 0;
   if ( y < 0 ) y = 0;

   SetWindowPos( hwndParent, NULL, x, y, w, h, 0 );
}


/*
======================================================================
handle_dwsize()

Rethink the scroll bars when the image display frame changes size.

INPUTS
   hwnd           the image frame
   w              the new client area width
   h              the new client area height

This is called by imvDrawWndProc() in response to a WM_SIZE message.

Windows doesn't always correctly determine whether the scroll bars
should be visible, so we take some extra steps to figure that out on
our own.  We also depart from the example code in the Win32 docs
shipped with MSVC 4.0, since it contains a number of errors.  And we
currently don't use the client sizes reported by Windows, since they
interact with scroll bar visibility and may be incorrect.
====================================================================== */

static void handle_dwsize( HWND hwnd, int w, int h )
{
   ImageInfo *im;
   SCROLLINFO si;
   RECT r;
   int sw, sh, rw, rh, sbsize;


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   si.cbSize = sizeof( si );
   si.fMask  = SIF_RANGE | SIF_PAGE | SIF_POS;
   si.nMin   = 0;

   GetWindowRect( hwnd, &r );
   sbsize = GetSystemMetrics( SM_CXVSCROLL );

   /* size of the zoomed image */

   sw = ( int )( im->zoom * im->width );
   sh = ( int )( im->zoom * im->height );

   /* size of the display window */

   rw = r.right - r.left;   // - 4, if window border
   rh = r.bottom - r.top;   // - 4, if window border

   /* if the window is bigger than the image, we don't need scroll bars */

   if (( rw >= sw ) && ( rh >= sh )) {
      im->maxScroll.x = im->curScroll.x = 0;
      im->maxScroll.y = im->curScroll.y = 0;
      si.nMax = si.nPage = si.nPos = 0;
      SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );
      SetScrollInfo( hwnd, SB_VERT, &si, TRUE );
      return;
   }

   /* if the image is both wider and taller than the window, we need room for
      both horizontal and vertical scroll bars */

   if (( rw < sw ) && ( rh < sh )) {
      rw -= sbsize;
      rh -= sbsize;
   }

   /* ...else if the image is wider but not taller, we need room for a
      horizontal scroll bar, and if that makes the remaining height too small
      for the image, we need room for a vertical scroll bar, too */

   else if ( rw < sw ) {
      rh -= sbsize;
      if ( rh < sh ) rw -= sbsize;
   }

   /* ...else the image is taller but not wider... */

   else {
      rw -= sbsize;
      if ( rw < sw ) rh -= sbsize;
   }

   im->maxScroll.x = max( sw - rw, 0 );
   im->curScroll.x = min( im->curScroll.x, im->maxScroll.x );

   im->maxScroll.y = max( sh - rh, 0 );
   im->curScroll.y = min( im->curScroll.y, im->maxScroll.y );

   si.nMax  = im->maxScroll.x + rw;
   si.nPage = rw;
   si.nPos  = im->curScroll.x;
   SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );

   si.nMax  = im->maxScroll.y + rh;
   si.nPage = rh;
   si.nPos  = im->curScroll.y;
   SetScrollInfo( hwnd, SB_VERT, &si, TRUE );
}


/*
======================================================================
handle_dwhscroll()

Change the display in response to horizontal scroll bar movement.

INPUTS
   hwnd           the image frame
   code           the type of movement
   pos            the new position for SB_THUMB type messages

This is called by imvDrawWndProc() in response to a WM_HSCROLL
message.

Pixels are moved using ScrollWindowEx(), and the area uncovered is
painted by causing a WM_PAINT.
====================================================================== */

static void handle_dwhscroll( HWND hwnd, int code, int pos )
{
   ImageInfo *im;
   SCROLLINFO si;
   int xnew, dx, step;


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   /* if the image is smaller than the window, do nothing */

   if ( im->maxScroll.x == 0 ) return;

   /* the number of pixels in a scrolling step depends on the zoom */

   step = 4;
   if ( im->izoom > IZOOM_ONE )
      step *= im->izoom - IZOOM_ONE + 1;

   /* the amount of scroll depends on the message code */

   switch ( code ) {
      case SB_TOP:            xnew = 0;                            break;
      case SB_BOTTOM:         xnew = im->maxScroll.x;              break;
      case SB_PAGEUP:         xnew = im->curScroll.x - step * 16;  break;
      case SB_PAGEDOWN:       xnew = im->curScroll.x + step * 16;  break;
      case SB_LINEUP:         xnew = im->curScroll.x - step;       break;
      case SB_LINEDOWN:       xnew = im->curScroll.x + step;       break;
      case SB_THUMBTRACK:
      case SB_THUMBPOSITION:  xnew = pos;                          break;
      default:                xnew = im->curScroll.x;
   }

   /* if zoomed in, align pixel blocks to the left edge of the window */

   if ( im->zoom > 1.0 )
      xnew -= ( xnew % ( int ) im->zoom );

   /* xnew must be between 0 and maxScroll.x */

   xnew = max( 0, xnew );
   xnew = min( im->maxScroll.x, xnew );

   /* if the new offset is the same as the current one, we have nothing to do */

   if ( xnew == im->curScroll.x ) return;

   /* move the bitmap */

   dx = im->curScroll.x - xnew;
   im->curScroll.x = xnew;
   ScrollWindowEx( hwnd, dx, 0, NULL, NULL, NULL, NULL, SW_INVALIDATE );
   UpdateWindow( hwnd );

   /* update the scroll bars */

   si.cbSize = sizeof( si );
   si.fMask  = SIF_POS;
   si.nPos   = im->curScroll.x;
   SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );
}


/*
======================================================================
handle_dwvscroll()

Change the display in response to vertical scroll bar movement.

INPUTS
   hwnd           the image frame
   code           the type of movement
   pos            the new position for SB_THUMB type messages

This is called by imvDrawWndProc() in response to a WM_VSCROLL
message.

Pixels are moved using ScrollWindowEx(), and the area uncovered is
painted by causing a WM_PAINT.
====================================================================== */

static void handle_dwvscroll( HWND hwnd, int code, int pos )
{
   ImageInfo *im;
   SCROLLINFO si;
   int ynew, dy, step;

   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   /* if the image is smaller than the window, do nothing */

   if ( im->maxScroll.y == 0 ) return;

   /* the number of pixels in a scrolling step depends on the zoom */

   step = 4;
   if ( im->izoom > IZOOM_ONE )
      step *= im->izoom - IZOOM_ONE + 1;

   /* the amount of scroll depends on the message code */

   switch ( code ) {
      case SB_TOP:            ynew = 0;                            break;
      case SB_BOTTOM:         ynew = im->maxScroll.y;              break;
      case SB_PAGEUP:         ynew = im->curScroll.y - step * 16;  break;
      case SB_PAGEDOWN:       ynew = im->curScroll.y + step * 16;  break;
      case SB_LINEUP:         ynew = im->curScroll.y - step;       break;
      case SB_LINEDOWN:       ynew = im->curScroll.y + step;       break;
      case SB_THUMBTRACK:
      case SB_THUMBPOSITION:  ynew = pos;                          break;
      default:                ynew = im->curScroll.y;
   }

   /* if zoomed in, align pixel blocks to the left edge of the window */

   if ( im->zoom > 1.0 )
      ynew -= ( ynew % ( int ) im->zoom );

   /* ynew must be between 0 and maxScroll.y */

   ynew = max( 0, ynew );
   ynew = min( im->maxScroll.y, ynew );

   /* if the new offset is the same as the current one, we have nothing to do */

   if ( ynew == im->curScroll.y ) return;

   /* move the bitmap */

   dy = im->curScroll.y - ynew;
   im->curScroll.y = ynew;
   ScrollWindowEx( hwnd, 0, dy, NULL, NULL, NULL, NULL, SW_INVALIDATE );
   UpdateWindow( hwnd );

   /* update the scroll bars */

   si.cbSize = sizeof( si );
   si.fMask  = SIF_POS;
   si.nPos   = im->curScroll.y;
   SetScrollInfo( hwnd, SB_VERT, &si, TRUE );
}


/*
======================================================================
handle_dwdrag()

Change the display in response to the user drag-scrolling the image.

INPUTS
   hwnd           the image frame
   pmouse         mouse movement

This is called by imvDrawWndProc() in response to a WM_MOUSEMOVE
message when it has occurred between a WM_LBUTTONDOWN and WM_LBUTTONUP
and the mouse mode is drag.

Pixels are moved using ScrollWindowEx(), and the area uncovered is
painted by causing a WM_PAINT.
====================================================================== */

static void handle_dwdrag( HWND hwnd, POINTS *pmouse )
{
   ImageInfo *im;
   SCROLLINFO si;
   int xnew, dx, ynew, dy;


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );

   /* if the image is smaller than the window, do nothing */

   if ( im->maxScroll.x == 0 && im->maxScroll.y == 0 ) return;

   /* if the mouse has moved less than one image pixel, do nothing */

   dx = pmouse->x - im->mouse.x;
   dy = pmouse->y - im->mouse.y;
   if ( dx == 0 && dy == 0 ) return;

   xnew = im->curScroll.x - dx;
   ynew = im->curScroll.y - dy;

   /* if zoomed in, align pixel blocks to the left edge of the window */

   if ( im->zoom > 1.0 ) {
      xnew -= ( xnew % ( int ) im->zoom );
      ynew -= ( ynew % ( int ) im->zoom );
   }

   /* xnew and ynew must be between 0 and maxScroll */

   xnew = max( 0, xnew );
   xnew = min( im->maxScroll.x, xnew );
   ynew = max( 0, ynew );
   ynew = min( im->maxScroll.y, ynew );

   /* if the new offset is the same as the current one, we have nothing to do */

   if ( xnew == im->curScroll.x && ynew == im->curScroll.y ) return;

   /* move the bitmap */

   dx = im->curScroll.x - xnew;
   dy = im->curScroll.y - ynew;
   im->curScroll.x = xnew;
   im->curScroll.y = ynew;
   ScrollWindowEx( hwnd, dx, dy, NULL, NULL, NULL, NULL, SW_INVALIDATE );
   UpdateWindow( hwnd );

   /* update the mouse anchor point */

   im->mouse.x += dx;
   im->mouse.y += dy;

   /* update the scroll bars */

   si.cbSize = sizeof( si );
   si.fMask  = SIF_POS;
   si.nPos   = im->curScroll.x;
   SetScrollInfo( hwnd, SB_HORZ, &si, TRUE );
   si.nPos   = im->curScroll.y;
   SetScrollInfo( hwnd, SB_VERT, &si, TRUE );
}


/*
======================================================================
handle_dwpaint()

Paint the invalid part of the image display.

INPUTS
   hwnd           the image frame

This is called by imvDrawWndProc() in response to a WM_PAINT message.

We're given a rectangle within the image frame that needs to be
painted, and we need to figure out, based on the current zoom and
scroll, which pixels in the image belong in that rectangle.  We then
blit that part of the image into the window.  If any part of the
rectangle contains background, we fill that with the background brush
of the window class.
====================================================================== */

static void handle_dwpaint( HWND hwnd )
{
   ImageInfo *im;
   PAINTSTRUCT ps;
   HBRUSH hbrback;
   FRect r;
   int sx, sy, sw, sh, dx, dy, dw, dh, c;


   im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
   hbrback = ( HBRUSH ) GetClassLongPtr( hwnd, GCLP_HBRBACKGROUND );

   BeginPaint( hwnd, &ps );

   /* if the image is empty, just erase the rectangle */

   if ( im->disp == NULL ) {
      FillRect( ps.hdc, &ps.rcPaint, hbrback );
      EndPaint( hwnd, &ps );
      return;
   }

   /* the rcPaint rectangle in image coordinates */

   r.left   = ( im->curScroll.x + ps.rcPaint.left   ) / im->zoom;
   r.top    = ( im->curScroll.y + ps.rcPaint.top    ) / im->zoom;
   r.right  = ( im->curScroll.x + ps.rcPaint.right  ) / im->zoom;
   r.bottom = ( im->curScroll.y + ps.rcPaint.bottom ) / im->zoom;

   /* if no part of the image is in rcPaint, we have nothing to do
      except erase the rectangle */

   if ( r.left >= im->width || r.top >= im->height ) {
      FillRect( ps.hdc, &ps.rcPaint, hbrback );
      EndPaint( hwnd, &ps );
      return;
   }

   /* If part of rcPaint is beyond the right or bottom image edges,
      shrink the image rect.  We don't need to worry about the left or
      top edges, since the image is always snug against the upper left
      corner of the window. */

   if ( r.right > im->width )
      r.right = ( float ) im->width;
   if ( r.bottom > im->height )
      r.bottom = ( float ) im->height;

   /* source coordinates for StretchDIBits() ...sy is upside-down! */

   sx = ( int )( r.left + 0.5f );
   sy = ( int )( im->height - r.bottom + 0.5f );
   sw = ( int )( r.right - r.left + 0.5f );
   sh = ( int )( r.bottom - r.top + 0.5f );

   /* destination coordinates */

   dx = ps.rcPaint.left;
   dy = ps.rcPaint.top;
   dw = ( int )(( r.right - r.left ) * im->zoom + 0.5f );
   dh = ( int )(( r.bottom - r.top ) * im->zoom + 0.5f );

   /* blit the image */

   SetStretchBltMode( ps.hdc, COLORONCOLOR );

   switch ( im->channel ) {
      case CH_RED:    c = RGB( 255, 0, 0 );  break;
      case CH_GREEN:  c = RGB( 0, 255, 0 );  break;
      case CH_BLUE:   c = RGB( 0, 0, 255 );  break;
      default:        c = 0;                 break;
   }

   /* red, green, or blue channel */

   if ( c ) {
      HBRUSH hbrush;
      hbrush = CreateSolidBrush(( COLORREF ) c );
      hbrush = SelectObject( ps.hdc, hbrush );
      StretchDIBits( ps.hdc, dx, dy, dw, dh, sx, sy, sw, sh,
         im->disp->rgb->bits, ( BITMAPINFO * ) &im->disp->rgb->dib,
         DIB_RGB_COLORS, MERGECOPY );
      DeleteObject( SelectObject( ps.hdc, hbrush ));
   }

   /* alpha channel */

   else if ( im->channel == CH_ALPHA )
      StretchDIBits( ps.hdc, dx, dy, dw, dh, sx, sy, sw, sh,
         im->disp->alpha->bits, ( BITMAPINFO * ) &im->disp->alpha->dib,
         DIB_RGB_COLORS, SRCCOPY );

   /* RGB image */

   else
      StretchDIBits( ps.hdc, dx, dy, dw, dh, sx, sy, sw, sh,
         im->disp->rgb->bits, ( BITMAPINFO * ) &im->disp->rgb->dib,
         DIB_RGB_COLORS, SRCCOPY );

   /* hot pixels */

   if ( im->mask & MASK_HOT )
      StretchDIBits( ps.hdc, dx, dy, dw, dh, sx, sy, sw, sh,
         im->disp->hot->bits, ( BITMAPINFO * ) &im->disp->hot->dib,
         DIB_RGB_COLORS, SRCAND );

   /* safe overlay */

   if ( im->mask & MASK_SAFE )
      StretchDIBits( ps.hdc, dx, dy, dw, dh, sx, sy, sw, sh,
         im->disp->safe->bits, ( BITMAPINFO * ) &im->disp->safe->dib,
         DIB_RGB_COLORS, SRCINVERT );

   /* erase the rest of the invalid rectangle */

   if ( dx + dw < ps.rcPaint.right ) {
      RECT r = { dx + dw, dy, ps.rcPaint.right, dy + dh };
      FillRect( ps.hdc, &r, hbrback );
   }
   if ( dy + dh < ps.rcPaint.bottom ) {
      RECT r = { dx, dy + dh, ps.rcPaint.right, ps.rcPaint.bottom };
      FillRect( ps.hdc, &r, hbrback );
   }

   EndPaint( hwnd, &ps );
}


/*
======================================================================
imvDrawWndProc()

The window procedure for the image display frame.

The image to be displayed is passed to the window procedure when the
window is created.  The lpCreateParams field of the LPCREATESTRUCT
(passed as the lparam for the WM_CREATE message) is a pointer to an
ImageInfo.  The ImageInfo is initialized during WM_CREATE, then added
to the window's property list using SetProp().  Other functions can
then get the ImageInfo pointer using GetProp().
====================================================================== */

LRESULT CALLBACK imvDrawWndProc( HWND hwnd, UINT message, WPARAM wparam,
   LPARAM lparam )
{
   LPCREATESTRUCT cs;
   ImageInfo *im;
   POINTS pt;
   int result;

   switch ( message )
   {
      case WM_CREATE:
         cs = ( LPCREATESTRUCT ) lparam;
         im = ( ImageInfo * ) cs->lpCreateParams;

         im->curScroll.x = im->maxScroll.x = 0;
         im->curScroll.y = im->maxScroll.y = 0;
         im->zoom = 1.0f;
         im->izoom = IZOOM_ONE;

         SetProp( hwnd, IMAGEINFO_POINTER, ( HANDLE ) im );

         return TRUE;

      case WM_SIZE:
         handle_dwsize( hwnd, LOWORD( lparam ), HIWORD( lparam ));
         return 0;

      case WM_HSCROLL:
         handle_dwhscroll( hwnd, LOWORD( wparam ), HIWORD( wparam ));
         return 0;

      case WM_VSCROLL:
         handle_dwvscroll( hwnd, LOWORD( wparam ), HIWORD( wparam ));
         return 0;

      case WM_PAINT:
         handle_dwpaint( hwnd );
         return 0;

      case WM_LBUTTONDOWN:
         im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
         im->mouse = MAKEPOINTS( lparam );
         if ( im->addingtext ) {
            result = mouseImageCoords( hwnd, &im->mouse, TRUE );
            addText( GetParent( hwnd ), im->mouse.x, im->mouse.y, result );
            return 0;
         }
         if ( wparam & MK_CONTROL ) {
            im->dragging = TRUE;
            set_cursor( hwnd, IDC_SIZEALL );
            SetCapture( hwnd );
         }
         else
            imvUpdateZoom( hwnd, &im->mouse, message );
         return 0;

      case WM_RBUTTONDOWN:
         im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
         im->mouse = MAKEPOINTS( lparam );
         if ( !im->dragging ) {
            imvUpdateZoom( hwnd, &im->mouse, message );
            return 0;
         }
         break;

      case WM_MOUSEMOVE:
         im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
         pt = MAKEPOINTS( lparam );
         if ( im->dragging ) {
            handle_dwdrag( hwnd, &pt );
            return 0;
         }
         else
            update_mouse( hwnd, &pt );
         break;

      case WM_LBUTTONUP:
         im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
         if ( im->dragging ) {
            ReleaseCapture();
            im->dragging = FALSE;
            set_cursor( hwnd, IDC_CROSS );
            return 0;
         }
         break;

      case WM_DESTROY:
         im = ( ImageInfo * ) GetProp( hwnd, IMAGEINFO_POINTER );
         RemoveProp( hwnd, IMAGEINFO_POINTER );
         return 0;

      default:
         break;
   }

   return DefWindowProc( hwnd, message, wparam, lparam );
}
