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 ****************************************************************************