Viewing contents of file '../idllib/contrib/fanning/zimage.pro'
;+
; NAME:
;       ZIMAGE
;
; PURPOSE:
;       The purpose of this program is to display an image which
;       can be zoomed by drawing a rubberband box on top of it. The
;      "zoomed" image appears in its own window.
;
; CATEGORY:
;      Image Processing, Widgets.
;
; CALLING SEQUENCE:
;
;      ZIMAGE, image
;
; INPUTS:
;
;      image:     A 2D array of image data.
;
; KEYWORD PARAMETERS:
;
;       BOTTOM:   The lowest color index of the colors to be used (see
;                 NCOLORS). The default is 0.
;
;       COLORINDEX: The color index for the rubberband box. This index will
;                 be loaded with a green color. Whatever color is there will
;                 be restored when the ZIMAGE program exits. The default is
;                 NCOLORS + BOTTOM.
;
;       NCOLORS:  This is the number of colors to use when displaying the
;                 image. The default is !D.N_COLORS-2.
;
;       GROUP:    This keyword is used to assign a group leader to this
;                 program. This program will be destroyed when the group
;                 leader is destroyed. Use this keyword if you are calling
;                 ZIMAGE from another widget program.
;
;
; OUTPUTS:
;       None.
;
; COMMON BLOCKS:
;       None.
;
; SIDE EFFECTS:
;       The COLORINDEX color is turned to green while the rubberband box
;       is being drawn. The color is restored after the box is drawn.
;
; RESTRICTIONS:
;       Uses XCOLORS from the Coyote Library:
;          http://www.dfanning.com/programs/xcolors.pro
;
; PROCEDURE:
;       Clicking the left mouse button allows you to drag a rubberband box
;       over the portion of the window you want to zoom into.
;
;       Clicking the right mouse button calls up hidden controls that allow
;       you to load different color tables and set the zoom factor.
;
;       The rubberband box is drawn with pixmaps and the "device copy"
;       technique.
;
;       This is an excellent example of how you can take advantage of the
;       widget program *as* the loop do to something (i.e., draw the box)
;       that in a regular IDL program would have to be done in a loop. It
;       is also a good example of modular programming style. Notice the draw
;       widget has two event handlers associated with it. Motion events
;       are only turned on for the draw widget when the box has to be
;       drawn.
;
; EXAMPLE:
;
;        To display an image you can zoom into, type:
;
;        filename = FILEPATH(SUBDIR=['examples','data'], 'worldelv.dat')
;        image = BYTARR(360,360)
;        OPENR, lun, filename, /GET_LUN
;        READU, lun, image
;        FREE_LUN, lun
;
;        ZIMAGE, image
;
; MODIFICATION HISTORY:
;        Written by: David Fanning, 15 August 96.
;        Fixed a !D.N_Colors problem. 17 June 98.
;        Made modifications so program works in 24-bit environment. 28 July 98. DWF.
;        Fixed a problem with the pop-up controls under certain circumstances.
;          13 Oct 98. DWF.
;        Added 24-bit color response. 13 Oct 98. DWF.
;-



PRO ZIMAGE_COLORS, event

Widget_Control, event.top, Get_UValue=info, /No_Copy

    ; What kind of event is this?

thisEvent = Tag_Names(event, /Structure)
CASE thisEvent OF
    'WIDGET_BUTTON': BEGIN
        XColors, Group=event.top, NColors = info.ncolors, $
            Bottom=info.bottom, NotifyID=[event.id, event.top]
        Widget_Control, info.controlID, Map=0
        info.map = 0
        ENDCASE
    'XCOLORS_LOAD':BEGIN
        Device, Get_Visual_Depth=thisDepth
        IF thisDepth GT 8 THEN BEGIN

                ; Redisplay the image.

            WSet, info.drawIndex
            TV, BytScl(info.image, Top=info.ncolors-1) + info.bottom
            WSet, info.pixIndex
            Device, Copy=[0, 0, info.xsize, info.ysize, 0, 0, info.drawIndex]

                ; Is a zoom window open? If so, redisplay it as well.

            IF Widget_Info(info.zoomDrawID, /Valid_ID) THEN BEGIN
                WSet, info.zoomWindowID
                TV, *info.subimage
            ENDIF

            ENDIF
        ENDCASE
ENDCASE
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;*******************************************************************



PRO ZIMAGE_QUITTER, event
Widget_Control, event.top, /Destroy
END ;*******************************************************************



Function What_Button_Pressed, event

   ; Checks event.press to find out what kind of button
   ; was pressed in a draw widget.  This is NOT an event handler.

button = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT']
Return, button(event.press)
END ;*******************************************************************



PRO ZIMAGE_CLEANUP, tlb

   ; The purpose of this program is to delete the pixmap window
   ; when the program ZOOMER is destroyed. Get the info structure,
   ; which holds the pixmap window index number and delete the window.

Widget_Control, tlb, Get_UValue=info, /No_Copy
IF N_Elements(info) NE 0 THEN BEGIN
    WDelete, info.pixIndex
    Ptr_Free, info.subimage
ENDIF
END ; of ZOOMER_CLEANUP **********************************************************



PRO ZIMAGE_FACTOR, event

   ; The purpose of this event handler is to set the zoom factor.

Widget_Control, event.top, Get_UValue=info, /No_Copy
Widget_Control, event.id, Get_UValue=factor
info.zoomfactor = factor(event.index)
Widget_Control, info.controlID, Map=0
info.map = 0
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; of ZOOMER_CLEANUP **********************************************************



PRO ZIMAGE_BUTTON_DOWN, event

   ; This event handler ONLY responds to button down events from the
   ; draw widget. If it gets a DOWN event, it does three things: (1) sets
   ; the static and dynamic corners of the zoom box, (2) changes the
   ; event handler for the draw widget to DRAW_ZOOMBOX and turns on MOTION
   ; events, and (3) takes over color index 1 of the color table for the
   ; zoom box drawing color.

possibleEventTypes = [ 'DOWN', 'UP', 'MOTION', 'SCROLL' ]
thisEvent = possibleEventTypes(event.type)
IF thisEvent NE 'DOWN' THEN RETURN

    ; Must be DOWN event to get here, so get info structure.

 Widget_Control, event.top, Get_UValue=info, /No_Copy

    ; Is this the left or right button?
    ; If RIGHT, then map or unmap controls.

buttonPressed = What_Button_Pressed(event)
IF buttonPressed EQ 'RIGHT' THEN BEGIN
   IF info.map EQ 1 THEN BEGIN
      Widget_Control, info.controlID, Map=0
      info.map = 0
    ENDIF ELSE BEGIN
       Widget_Control, info.controlID, Map=1
       info.map = 1
    ENDELSE
    Widget_Control, event.top, Set_UValue=info, /No_Copy
    RETURN
 ENDIF

    ; Set the static corners of the box to current
    ; cursor location.

 info.xs = event.x
 info.ys = event.y

    ; Change the event handler for the draw widget and turn MOTION
    ; events ON.

 Widget_Control, event.id, Event_Pro='ZIMAGE_DRAWBOX', $
    Draw_Motion_Events=1

   ; Take over color index 1 for the zoom box drawing color. Store the
   ; current (r,g,b) values for color index 1 so you can restore the
   ; current colors after the zoom box is drawn.

TVLct, r, g, b, /Get
info.r_old = r(info.colorIndex)
info.g_old = g(info.colorIndex)
info.b_old = b(info.colorIndex)

   ; Put the info structure back into its storage location.

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; of ZIMAGE_BUTTON_DOWN *****************************************************



PRO ZIMAGE_DRAWBOX, event

   ; This event handler continuously draws and erases the zoom box until it
   ; receives an UP event from the draw widget. Then it turns draw widget
   ; motion events OFF and changes the event handler for the draw widget
   ; back to ZIMAGE_BUTTON_DOWN.

   ; Get the info structure out of the top-level base.

Widget_Control, event.top, Get_UValue=info, /No_Copy

   ; What type of an event is this?

possibleEventTypes = [ 'DOWN', 'UP', 'MOTION', 'SCROLL' ]
thisEvent = possibleEventTypes(event.type)

IF thisEvent EQ 'UP' THEN BEGIN

      ; If this is an UP event, you need to erase the zoombox, restore
      ; the user's color table, turn motion events OFF, set the
      ; draw widget's event handler back to PROCESS_DRAW_EVENTS, and
      ; draw the "zoomed" plot in both the draw widget and the pixmap.

      ; Erase the zoombox one final time by copying the plot from the pixmap.

   WSet, info.drawIndex
   Device, Copy = [0, 0, info.xsize, info.ysize, 0, 0, info.pixIndex]

      ; Restore the user's color table.

   TVLct, info.r_old, info.g_old, info.b_old, info.colorIndex

      ; Turn motion events off and redirect the events to PROCESS_DRAW_EVENTS.

    Widget_Control, event.id, Draw_Motion_Events=0, $
       Event_Pro='ZIMAGE_BUTTON_DOWN'

      ; Draw the "zoomed" image. Start by getting the LAST zoom
      ; box outline. These are indices into image array.

   x = [info.xs, event.x]
   y = [info.ys, event.y]

      ; Make sure the user didn't just click in the window.

   IF info.xs EQ event.x OR info.ys EQ event.y THEN BEGIN
      Widget_Control, event.top, Set_UValue=info, /No_Copy
      RETURN
   ENDIF

      ; Make sure the x and y values are ordered as [min, max].

   IF info.xs GT event.x THEN x = [event.x, info.xs]
   IF info.ys GT event.y THEN y = [event.y, info.ys]

      ; Set the zoom factor (3) and determine the new X and Y
      ; sizes of the Zoom Window.

   zoomXSize = (x(1) - x(0) + 1) * info.zoomFactor
   zoomYSize = (y(1) - y(0) + 1) * info.zoomFactor

      ; Subset the image, and apply the zoom factor to it.

   imageSubset = info.image(x(0):x(1), y(0):y(1))
   zoomedImage = Congrid(imageSubset, zoomXSize, zoomYSize, /Interp)

      ; If the Zoom Window exists, make it the proper size and load
      ; the zoomed image into it. If it does not exists, create it.

   IF Widget_Info(info.zoomDrawID, /Valid_ID) THEN BEGIN

         ; Zoomed window exists. Make it correct size and load image.

      Widget_Control, info.zoomDrawID, XSize=zoomXSize, YSize=zoomYSize
      WSet, info.zoomWindowID
      IF Ptr_Valid(info.subimage) THEN $
        *info.subimage = BytScl(zoomedImage, Top=info.ncolors-1, $
            Max=Max(info.image), Min=Min(info.image)) + info.bottom ELSE $
         info.subimage = Ptr_New(BytScl(zoomedImage, Top=info.ncolors-1, $
            Max=Max(info.image), Min=Min(info.image)) + info.bottom)
      TV, *info.subimage


   ENDIF ELSE BEGIN

         ; Zoomed window does not exist. Create it.

      zoomtlb = Widget_Base(Title='Zoomed Image', Group=event.top)
      zoomdraw = Widget_Draw(zoomtlb, XSize=zoomXSize, YSize=zoomYSize)
      Widget_Control, zoomtlb, /Realize
      Widget_Control, zoomdraw, Get_Value=windowID
      info.zoomDrawID = zoomdraw
      info.zoomWindowID = windowID
      WSet, windowID
      IF Ptr_Valid(info.subimage) THEN $
        *info.subimage = BytScl(zoomedImage, Top=info.ncolors-1, $
            Max=Max(info.image), Min=Min(info.image)) + info.bottom ELSE $
         info.subimage = Ptr_New(BytScl(zoomedImage, Top=info.ncolors-1, $
            Max=Max(info.image), Min=Min(info.image)) + info.bottom)
      TV, *info.subimage
    ENDELSE

      ; If the controls were mapped, unmap them.

   IF info.map EQ 1 THEN BEGIN
      Widget_Control, info.controlID, Map=0
      info.map = 0
   ENDIF

      ; Put the info structure back into its storage location and then,
      ; out of here!

   Widget_Control, event.top, Set_UValue=info, /No_Copy
   RETURN
ENDIF ; thisEvent = UP


   ; Most of the action in this event handler occurs here while we are waiting
   ; for an UP event to occur. As long as we don't get it, keep erasing the
   ; old zoom box and drawing a new one.

   ; Erase the old zoom box.

WSet, info.drawIndex
Device, Copy = [0, 0, info.xsize, info.ysize, 0, 0, info.pixIndex]

   ; Update the dynamic corner of the zoom box to the current cursor location.

 info.xd = event.x
 info.yd = event.y

   ; Load a green color in color index 1 to draw the zoom box with.

TVLct, 0B, 255B, 0B, info.colorIndex

   ; Draw the zoom box.

PlotS, [info.xs, info.xs, info.xd, info.xd, info.xs], $
       [info.ys, info.yd, info.yd, info.ys, info.ys], $
       /Device, Color=info.colorIndex

   ; Put the info structure back into its storage location.

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ; of ZIMAGE_DRAWBOX ******************************************************************



PRO ZIMAGE, image, ColorIndex = colorIndex, Bottom=bottom, $
   Group=group, NColors = ncolors

   ; This procedure allows the user to "zoom" into an image
   ; by drawing a box around the part of the image to zoom into.
   ; The zoom box will be drawn and erased by using a pixmap and
   ; the "Device Copy" technique.

   ; On an error condition, return to the main level of IDL.

On_Error, 1

   ; Was an image passed into the procedure?
   ;If not, exit with error message.

IF N_Params() EQ 0 THEN $
   Message, 'Must pass ZIMAGE one positional parameter (an image).'

   ; Make sure a window has been opened in this IDL session for
   ; accurate color number determination.

Window, /Pixmap, XSize=10, YSize=10
WDelete, !D.Window

   ; Check for keywords. Set defaults to size of image if necessary.

imageSize = Size(image)
IF imageSize(0) NE 2 Then Message, 'Image parameter must be 2D.'
xsize = imageSize(1)
ysize = imageSize(2)
IF N_Elements(ncolors) EQ 0 THEN ncolors = (!D.N_Colors - 2) < 254
IF N_Elements(bottom) EQ 0 THEN bottom = 0B
IF N_Elements(colorIndex) EQ 0 THEN colorIndex = (ncolors + bottom) < 255

   ; Works with 2D images.

Device, Decomposed=0

   ; Create a top-level base for this program. No resizing of this base.

tlb = Widget_Base(Title='Full Size Image', TLB_Frame_Attr=1)


   ; Create two bases. One for controls and the other for the
   ; draw widget. Leave the control base unmapped for now.

controlID = Widget_Base(tlb, Map=0, Column=1)
colors = Widget_Button(controlID, Value='Load Colors', $
   Event_Pro='ZIMAGE_COLORS')
factorString = ['2x', '3x', '5x', '8x']
factors = [2, 3, 5, 8]
zoomfactor = Widget_DropList(controlID, Value=factorString, $
   Event_Pro='ZIMAGE_FACTOR', UValue=factors, Title='Zoom Factor')
quitter = Widget_Button(controlID, Value='Exit Program', $
   Event_Pro='ZIMAGE_QUITTER')

drawbase = Widget_Base(tlb, Map=1)
draw = Widget_Draw(drawbase, XSize=xsize, YSize=ysize, $
   Button_Events=1, Event_Pro='ZIMAGE_BUTTON_DOWN')

   ; Realize the program.

Widget_Control, tlb, /Realize

   ; Get the window index number of the draw widget.
   ; Make the draw widget the current graphics window
   ; and display the image in it.

Widget_Control, draw, Get_Value=drawIndex
WSet, drawIndex
TV, BytScl(image, Top=ncolors-1, Max=Max(image), Min=Min(image)) + bottom

  ; Create a pixmap window the same size as the draw widget window.
  ; Store its window index number in a local variable. Display the
  ; image you just put in the draw widget in the pixmap window.

Window, /Free, XSize=xsize, YSize=ysize, /Pixmap
pixIndex = !D.Window
TV, BytScl(image, Top=ncolors-1, Max=Max(image), Min=Min(image)) + bottom

   ; Create an info structure to hold information required by the program.

info = { $
   image:image, $               ; The original image.
   subimage:Ptr_New(), $        ; The scaled and resized subimage.
   xsize:xsize, $               ; The x size of the image window.
   ysize:ysize, $               ; The y size of the image window.
   drawIndex:drawIndex, $       ; The draw window index number.
   pixIndex:pixIndex, $         ; The pixmap window index number.
   ncolors:ncolors, $           ; The number of colors for the image.
   bottom:bottom, $             ; The bottom color index.
   colorIndex:colorIndex, $     ; The drawing color index.
   xs:0, $                      ; X static corner of the zoom box.
   ys:0, $                      ; Y static corner of the zoom box.
   xd:0, $                      ; X dynamic corner of the zoom box.
   yd:0, $                      ; Y dynamic corner of the zoom box.
   zoomDrawID:-1L, $            ; Zoomed image draw widget ID.
   zoomWindowID:-1, $           ; Zoomed image window index number.
   r_old:0, $                   ; The user's red color value.
   g_old:0, $                   ; The user's green color value.
   b_old:0, $                   ; The user's blue color value.
   zoomfactor:2, $              ; The initial zoom factor.
   map:0, $                     ; A flag to tell if the controls are mapped.
   controlID:controlID}         ; The identifier of the control base to map.

   ; Store the info structure in the user value of the top-level base.

Widget_Control, tlb, Set_UValue=info, /No_Copy

   ; Register this program and set up the event loop.

XManager, 'zimage', tlb, Cleanup='ZIMAGE_CLEANUP', Group_Leader=group, /No_Block
END ; of ZIMAGE ****************************************************************************