; docformat = 'rst' ; ; NAME: ; cgZImage ; ; PURPOSE: ; Allows the user to interactively zoom into an image. Program controls are available ; by right-clicking in the full-sized image window. Zoom factors from 2x to 16x are ; available. Use the left mouse button to draw a box on the full-sized image to locate ; the region of the image to zoom. ; ;******************************************************************************************; ; ; ; Copyright (c) 2010, by Fanning Software Consulting, Inc. All rights reserved. ; ; ; ; Redistribution and use in source and binary forms, with or without ; ; modification, are permitted provided that the following conditions are met: ; ; ; ; * Redistributions of source code must retain the above copyright ; ; notice, this list of conditions and the following disclaimer. ; ; * Redistributions in binary form must reproduce the above copyright ; ; notice, this list of conditions and the following disclaimer in the ; ; documentation and/or other materials provided with the distribution. ; ; * Neither the name of Fanning Software Consulting, Inc. nor the names of its ; ; contributors may be used to endorse or promote products derived from this ; ; software without specific prior written permission. ; ; ; ; THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY ; ; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ; ; OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ; ; SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, ; ; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ; ; TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; ; ; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ; ; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ; ; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ; ; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ; ;******************************************************************************************; ;+ ; Allows the user to interactively zoom into an image. Program controls are available ; by right-clicking in the full-sized image window. Zoom factors from 2x to 16x are ; available. Use the left mouse button to draw a box on the full-sized image to locate ; the region of the image to zoom. ; ; :Categories: ; Graphics ; ; :Examples: ; Code examples:: ; IDL> image = cgDemoData(7) ; IDL> cgZImage, image ; 2D image ; IDL> image = cgDemoData(16) ; IDL> cgZImage, image ; True-Color image ; ; :Author: ; FANNING SOFTWARE CONSULTING:: ; David W. Fanning ; 1645 Sheely Drive ; Fort Collins, CO 80526 USA ; Phone: 970-221-0438 ; E-mail: david@idlcoyote.com ; Coyote's Guide to IDL Programming: http://www.idlcoyote.com ; ; :History: ; Change History:: ; Written, 20 September 2012 from previous FSC_ZImage program. DWF. ; Modernized the info structure handling to reflect modern sensibilities. 3 Oct 2012. DWF. ; Changes to allow this to work with very large images. Can now zoom to actual pixel values. ; Also fixed a problem that left zoom windows lying around unused if scroll bars were ; needed. 18 October 2012. DWF. ; The color palette was not always being included when images were zoomed. Fixed. 17 Nov 2012. DWF. ; Added ZoomFactor keyword to allow the zoom factor to be set on start-up. 28 Nov 2012. DWF. ; Fixed a typo that caused bringing controls into the window to fail. 13 Jan 2014. DWF. ; ; :Copyright: ; Copyright (c) 2012-2014, Fanning Software Consulting, Inc. ;- ;+ ; Event handler for the motion events coming from the zoom window. Find the location ; and value of the image at the cursor location and report it to the status bar in the ; main image window. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_ZoomWindow_Events, event ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() RETURN ENDIF ; Get the info structure. Widget_Control, event.top, Get_UValue=tlb Widget_Control, tlb, Get_UValue=info ; Create the proper vectors to locate the cursor in the image. xvec = cgScaleVector(Findgen((*info).zxsize), (*info).xrange[0], (*info).xrange[1]) yvec = cgScaleVector(Findgen((*info).zysize), (*info).yrange[0], (*info).yrange[1]) xloc = 0 > Round(xvec[event.x]) < ((*info).xsize-1) yloc = 0 > Round(yvec[event.y]) < ((*info).ysize-1) ; Create the text for the status bar. dims = Image_Dimensions((*info).image, XSize=xsize, YSize=ysize, TrueIndex=trueindex) CASE trueIndex OF -1: value = ((*info).image)[xloc, yloc] 0: BEGIN image = Transpose((*info).image, [1,2,0]) value = [(image[*,*,0])[xloc, yloc], (image[*,*,1])[xloc, yloc], (image[*,*,1])[xloc, yloc]] Undefine, image END 1: BEGIN image = Transpose((*info).image, [0,2,1]) value = [(image[*,*,0])[xloc, yloc], (image[*,*,1])[xloc, yloc], (image[*,*,1])[xloc, yloc]] Undefine, image END 2: BEGIN value = [((*info).image[*,*,0])[xloc, yloc], ((*info).image[*,*,1])[xloc, yloc], ((*info).image[*,*,1])[xloc, yloc]] END ENDCASE ; Create the text for the statusbar widget and update the status bar. IF Obj_Valid(*(*info).map) THEN BEGIN *(*info).map -> GetProperty, XRANGE=xrange, YRANGE=yrange xvec = cgScaleVector(Findgen((*info).xsize), xrange[0], xrange[1]) yvec = cgScaleVector(Findgen((*info).ysize), yrange[0], yrange[1]) ll = *(*info).map -> Inverse(xvec[xloc], yvec[yloc]) loctext = 'Lat: ' + String(ll[1], Format='(F0.3)') + ' Lon: ' + String(ll[0], Format='(F0.3)') ENDIF ELSE BEGIN loctext = 'XLoc: ' + Strtrim(xloc,2) + ' YLoc: ' + Strtrim(yloc,2) ENDELSE imageType = Size(value, /TNAME) IF imageType EQ 'BYTE' THEN value = Fix(value) IF N_Elements(value) EQ 1 THEN BEGIN valuetext = ' Value: ' + StrTrim(value,2) ENDIF ELSE BEGIN valuetext = ' RGB Value: (' + StrTrim(value[0],2) + ', ' + $ StrTrim(value[1],2) + ', ' + StrTrim(value[2],2) + ')' ENDELSE Widget_Control, (*info).statusbar, Set_Value=loctext + valuetext ; Draw the box and a small circle to locate the cursor on the ; larger image. WSet, (*info).drawIndex Device, Copy=[0, 0, (*info).xsize, (*info).ysize, 0, 0, (*info).pixIndex] xvec = cgScaleVector(Findgen(!D.X_Size), 0, (*info).xsize) yvec = cgScaleVector(Findgen(!D.Y_Size), 0, (*info).ysize) xdloc = Value_Locate(xvec, xloc) ydloc = Value_Locate(yvec, yloc) cgPlotS, [(*info).xs, (*info).xs, (*info).xd, (*info).xd, (*info).xs], $ [(*info).ys, (*info).yd, (*info).yd, (*info).ys, (*info).ys], $ /Device, Color=(*info).boxcolor cgPlotS, xdloc, ydloc, /Device, PSYM='OpenCircle', Color=(*info).boxcolor, SymSize=1.5 END ;+ ; A clean-up routine for the zoom window, if the zoom window is killed. ; ; :Params: ; zoomID: in, required, type=long ; The zoom widget identifier ;- PRO cgZImage_ZoomDied, zoomID ; Come here when the zoom window dies. Basically, you ; want to erase the zoom box in the full-size window. ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg(/Quiet) RETURN ENDIF ; Get the TLB of the full-sized window. Widget_Control, zoomID, GET_UVALUE=tlb ; If that base is gone, disappear! IF Widget_Info(tlb, /VALID_ID) EQ 0 THEN RETURN ; Get the information you need to redisplay the image. Widget_Control, tlb, Get_UValue=info ; Redisplay the image. WSet, (*info).drawIndex cgImage, (*info).image, $ BETA=*(*info).beta, $ BOTTOM=*(*info).bottom, $ CLIP=*(*info).clip, $ EXCLUDE=*(*info).exclude, $ EXPONENT=*(*info).exponent, $ GAMMA=*(*info).gamma, $ INTERPOLATE=*(*info).interpolate, $ MAXVALUE=*(*info).max, $ MEAN=*(*info).mean, $ MISSING_COLOR=*(*info).missing_color, $ MISSING_INDEX=*(*info).missing_index, $ MISSING_VALUE=*(*info).missing_value, $ NEGATIVE=*(*info).negative, $ MINVALUE=*(*info).min, $ MULTIPLIER=*(*info).multiplier, $ NCOLORS=*(*info).ncolors, $ PALETTE=*(*info).palette, $ SCALE=*(*info).scale, $ SIGMA=*(*info).sigma, $ STRETCH=*(*info).stretch, $ TOP=*(*info).top WSet, (*info).pixIndex Device, Copy=[0, 0, (*info).xsize, (*info).ysize, 0, 0, (*info).drawIndex] ; Clear the statusbar widget. Widget_Control, (*info).statusbar, Set_Value="" END ; ---------------------------------------------------------------------- ;+ ; Event handler for changing the rubber-band box color. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_BoxColor, event ; Come here to change the selector box color. ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() RETURN ENDIF ; Get the information you need to redisplaythe image. Widget_Control, event.top, Get_UValue=info boxcolor = cgPickColorName((*info).boxColor, Group_Leader=event.top) (*info).boxColor = boxColor ; Redisplay the image. WSet, (*info).drawIndex cgImage, (*info).image, $ BETA=*(*info).beta, $ BOTTOM=*(*info).bottom, $ CLIP=*(*info).clip, $ EXCLUDE=*(*info).exclude, $ EXPONENT=*(*info).exponent, $ GAMMA=*(*info).gamma, $ INTERPOLATE=*(*info).interpolate, $ MAXVALUE=*(*info).max, $ MEAN=*(*info).mean, $ MISSING_COLOR=*(*info).missing_color, $ MISSING_INDEX=*(*info).missing_index, $ MISSING_VALUE=*(*info).missing_value, $ NEGATIVE=*(*info).negative, $ MINVALUE=*(*info).min, $ MULTIPLIER=*(*info).multiplier, $ NCOLORS=*(*info).ncolors, $ PALETTE=*(*info).palette, $ SCALE=*(*info).scale, $ SIGMA=*(*info).sigma, $ STRETCH=*(*info).stretch, $ TOP=*(*info).top WSet, (*info).pixIndex Device, Copy=[0, 0, (*info).xsize, (*info).ysize, 0, 0, (*info).drawIndex] ; Unmap the controls. Widget_Control, (*info).controlID, Map=0 (*info).mapcontrols = 0 END ; ---------------------------------------------------------------------- ;+ ; Event handler for changing the colors the image is displayed in. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_LoadColors, event ; Come here to load colors or to respond to color loading events. ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() RETURN ENDIF Widget_Control, event.top, Get_UValue=info ; What kind of event is this? thisEvent = Tag_Names(event, /Structure) ; Do the right thing. CASE thisEvent OF 'WIDGET_BUTTON': BEGIN TVLCT, (*info).r, (*info).g, (*info).b, *(*info).bottom XColors, Group=event.top, NColors = *(*info).ncolors, $ Bottom=*(*info).bottom, NotifyID=[event.id, event.top], $ Title='ZImage Colors (' + StrTrim((*info).drawIndex,2) + ')' Widget_Control, (*info).controlID, Map=0 (*info).mapcontrols = 0 END 'XCOLORS_LOAD':BEGIN ; Extract the new color table vectors from XCOLORS. (*info).r = event.r(*(*info).bottom:*(*info).bottom+*(*info).ncolors-1) (*info).g = event.g(*(*info).bottom:*(*info).bottom+*(*info).ncolors-1) (*info).b = event.b(*(*info).bottom:*(*info).bottom+*(*info).ncolors-1) ; Redisplay the image. WSet, (*info).drawIndex cgImage, (*info).image, $ BETA=*(*info).beta, $ BOTTOM=*(*info).bottom, $ CLIP=*(*info).clip, $ EXCLUDE=*(*info).exclude, $ EXPONENT=*(*info).exponent, $ GAMMA=*(*info).gamma, $ INTERPOLATE=*(*info).interpolate, $ MAXVALUE=*(*info).max, $ MEAN=*(*info).mean, $ MISSING_COLOR=*(*info).missing_color, $ MISSING_INDEX=*(*info).missing_index, $ MISSING_VALUE=*(*info).missing_value, $ NEGATIVE=*(*info).negative, $ MINVALUE=*(*info).min, $ MULTIPLIER=*(*info).multiplier, $ NCOLORS=*(*info).ncolors, $ PALETTE=*(*info).palette, $ SCALE=*(*info).scale, $ SIGMA=*(*info).sigma, $ STRETCH=*(*info).stretch, $ TOP=*(*info).top 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 IF Ptr_Valid((*info).zoomedImage) THEN BEGIN cgImage, *(*info).zoomedImage, $ BETA=*(*info).beta, $ BOTTOM=*(*info).bottom, $ CLIP=*(*info).clip, $ EXCLUDE=*(*info).exclude, $ EXPONENT=*(*info).exponent, $ GAMMA=*(*info).gamma, $ INTERPOLATE=*(*info).interpolate, $ MAXVALUE=*(*info).max, $ MEAN=*(*info).mean, $ MISSING_COLOR=*(*info).missing_color, $ MISSING_INDEX=*(*info).missing_index, $ MISSING_VALUE=*(*info).missing_value, $ NEGATIVE=*(*info).negative, $ MINVALUE=*(*info).min, $ MULTIPLIER=*(*info).multiplier, $ NCOLORS=*(*info).ncolors, $ PALETTE=*(*info).palette, $ SCALE=*(*info).scale, $ SIGMA=*(*info).sigma, $ STRETCH=*(*info).stretch, $ TOP=*(*info).top ENDIF ENDIF END ENDCASE END ; ---------------------------------------------------------------------- ;+ ; Event handler for quiting the program. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_Quit, event Widget_Control, event.top, /Destroy END ; ---------------------------------------------------------------------- ;+ ; The clean-up routine for the program. Come here to release pointers and ; memory. ; ; :Params: ; tlb: in, required, type=long ; The identifier of the top-level base of the widget program. ;- PRO cgZImage_Cleanup, tlb ; The purpose of this program is to delete the pixmap window ; when the program cgZImage is destroyed. Get the info structure, ; which holds the pixmap window index number and delete the window. Widget_Control, tlb, Get_UValue=info IF N_Elements(info) NE 0 THEN BEGIN WDelete, (*info).pixIndex Ptr_Free, (*info).zoomedImage Ptr_Free, (*info).beta Ptr_Free, (*info).bottom Ptr_Free, (*info).clip Ptr_Free, (*info).exclude Ptr_Free, (*info).exponent Ptr_Free, (*info).gamma Ptr_Free, (*info).interpolate IF (*info).createdmap THEN BEGIN mapObj = *(*info).map Obj_Destroy, mapObj Ptr_Free, (*info).map ENDIF ELSE BEGIN mapObj = *(*info).map Ptr_Free, (*info).map ENDELSE Ptr_Free, (*info).max Ptr_Free, (*info).mean Ptr_Free, (*info).missing_color Ptr_Free, (*info).missing_index Ptr_Free, (*info).missing_value Ptr_Free, (*info).negative Ptr_Free, (*info).min Ptr_Free, (*info).multiplier Ptr_Free, (*info).ncolors Ptr_Free, (*info).palette Ptr_Free, (*info).scale Ptr_Free, (*info).sigma Ptr_Free, (*info).stretch Ptr_Free, (*info).top ENDIF END ; ---------------------------------------------------------------------- ;+ ; Event handler for changing the zoom factor. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_Factor, event ; The purpose of this event handler is to set the zoom factor. ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() ; Put the info structure back. IF N_Elements(info) NE 0 THEN Widget_Control, event.top, Set_UValue=info RETURN ENDIF Widget_Control, event.top, Get_UValue=info Widget_Control, event.id, Get_UValue=factor (*info).zoomfactor = factor[event.index] Widget_Control, (*info).controlID, Map=0 (*info).mapcontrols = 0 END ; ---------------------------------------------------------------------- ;+ ; Event handler for handling the rubber-band box events to create ; the zoom window. ; ; :Params: ; event: in, required, type=structure ; The event structure passed to the program by the window manager. ;- PRO cgZImage_DrawEvents, 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. ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() ; Turn motion events off. Widget_Control, event.id, Draw_Motion_Events=0 RETURN ENDIF ; Get the info structure out of the top-level base. Widget_Control, event.top, Get_UValue=info ; What type of an event is this? possibleEventTypes = [ 'DOWN', 'UP', 'MOTION', 'SCROLL' ] thisEvent = possibleEventTypes[event.type] buttons = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT'] CASE thisEvent OF 'DOWN': BEGIN ; Is this the left or right button? ; If RIGHT, then map or unmap controls. buttonPressed = buttons[event.press] IF buttonPressed EQ 'RIGHT' THEN BEGIN IF (*info).mapcontrols EQ 1 THEN BEGIN Widget_Control, (*info).controlID, Map=0 (*info).mapcontrols = 0 ENDIF ELSE BEGIN Widget_Control, (*info).controlID, Map=1 (*info).mapcontrols = 1 ENDELSE RETURN ENDIF ; Set the static corners of the box to current ; cursor location. (*info).xs = event.x (*info).ys = event.y ; Turn draw MOTION events ON. Widget_Control, event.id, Draw_Motion_Events=1 ENDCASE 'UP': BEGIN ; Is this the left or right button? ; If RIGHT, then do nothing. buttonReleased = buttons[event.release] IF buttonReleased EQ 'RIGHT' THEN RETURN ; If this is an UP event, you need to erase the zoombox, turn motion events OFF, and ; draw the "zoomed" plot in both the draw widget and the pixmap. ; Turn motion events off. Widget_Control, event.id, Draw_Motion_Events=0 ; Draw the "zoomed" image. Start by getting the LAST zoom ; box outline. These are indices into image array. event.x = 0 > event.x < ((*info).xsize - 1) event.y = 0 > event.y < ((*info).ysize - 1) 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 ; Erase the zoombox. WSet, (*info).drawIndex TVLCT, (*info).r, (*info).g, (*info).b ; Copy from the pximap. Device, Copy = [0, 0, (*info).xsize, (*info).ysize, 0, 0, (*info).pixIndex] 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] ; Make sure these are in image pixel coordinates, not just ; window pixel coordinates. xvec = cgScaleVector(Indgen((*info).xsize), 0, !D.X_Size-1) yvec = cgScaleVector(Indgen((*info).ysize), 0, !D.Y_Size-1) x = Value_Locate(xvec, x) y = Value_Locate(yvec, y) (*info).xrange = x (*info).yrange = y ; Set the zoom factor 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 (*info).zxsize = zoomXSize (*info).zysize = zoomYSize ; Subset the image, and apply the zoom factor to it. CASE (*info).trueIndex OF -1: imageSubset = (*info).scaled[x[0]:x[1], y[0]:y[1]] 0: imageSubset = (*info).scaled[*, x[0]:x[1], y[0]:y[1]] 1: imageSubset = (*info).scaled[x[0]:x[1], *, y[0]:y[1]] 2: imageSubset = (*info).scaled[x[0]:x[1], y[0]:y[1], *] ENDCASE zoomedImage = cgResizeImage(imageSubset, zoomXSize, zoomYSize, Interp=0) IF Ptr_Valid((*info).zoomedImage) $ THEN *(*info).zoomedImage = zoomedImage $ ELSE (*info).zoomedImage = Ptr_New(zoomedImage, /No_Copy) ; 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 ; If the new zoomed image needs scroll bars, or the window has ; scroll bars, destroy it and recreate it. dims = Image_Dimensions(*(*info).zoomedimage, XSIZE=ixsize, YSIZE=iysize) IF (ixsize GT (*info).maxSize) OR (iysize GT (*info).maxSize) OR ((*info).hasScrollBars) THEN BEGIN ; Get offset positions for the non-existing zoom window. Widget_Control, (*info).zoomDrawID, TLB_Get_Offset=offsets xpos = offsets[0] ypos = offsets[1] Widget_Control, (*info).zoomtlb, /Destroy ; Calculate a window size. Maximum window size is 800. dims = Image_Dimensions(*(*info).zoomedimage, XSIZE=ixsize, YSIZE=iysize) aspect = Float(ixsize)/iysize MAXSIZE = 800 IF ixsize GT MAXSIZE OR iysize GT MAXSIZE THEN BEGIN x_scroll_size = MAXSIZE < ixsize y_scroll_size = MAXSIZE < iysize (*info).hasScrollBars = 1 ; Make sure window is not off the display. maxwinsize = MaxWindowSize() IF (xpos + x_scroll_size) GT maxwinsize[0] THEN $ xpos = maxwinsize[0] - x_scroll_size IF (ypos + y_scroll_size) GT maxwinsize[1] THEN $ ypos = maxwinsize[1] - y_scroll_size ENDIF ELSE (*info).hasScrollBars = 0 ; Zoom window does not exist. Create it. zoomTLB = Widget_Base(Title='Zoomed Image', Group=event.top, $ XOffset=xpos, YOffset=ypos, KILL_NOTIFY='cgZImage_ZoomDied', $ UVALUE=event.top, X_Scroll_Size=x_scroll_size, Y_Scroll_Size=y_scroll_size) zoomdraw = Widget_Draw(zoomtlb, XSize=zoomXSize, YSize=zoomYSize, $ /MOTION_EVENTS, Event_Pro='cgZImage_ZoomWindow_Events') Widget_Control, zoomtlb, /Realize Widget_Control, zoomdraw, Get_Value=windowID (*info).zoomDrawID = zoomdraw (*info).zoomWindowID = windowID (*info).zoomTLB = zoomTLB WSet, windowID IF Ptr_Valid((*info).zoomedImage) THEN cgImage, *(*info).zoomedImage, PALETTE=*(*info).palette ENDIF ELSE 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).zoomedImage) THEN cgImage, *(*info).zoomedImage, PALETTE=*(*info).palette ENDELSE ENDIF ELSE BEGIN ; Get offset positions for the non-existing zoom window. Widget_Control, event.top, TLB_Get_Size=sizes, TLB_Get_Offset=offsets xpos = sizes[0] + offsets[0] + 20 ypos = offsets[1] + 40 ; Calculate a window size. Maximum window size is 800. dims = Image_Dimensions(*(*info).zoomedimage, XSIZE=ixsize, YSIZE=iysize) aspect = Float(ixsize)/iysize MAXSIZE = 800 IF ixsize GT MAXSIZE OR iysize GT MAXSIZE THEN BEGIN x_scroll_size = MAXSIZE < ixsize y_scroll_size = MAXSIZE < iysize (*info).hasScrollBars = 1 ; Make sure window is not off the display. maxwinsize = MaxWindowSize() IF (xpos + x_scroll_size) GT maxwinsize[0] THEN $ xpos = maxwinsize[0] - x_scroll_size IF (ypos + y_scroll_size) GT maxwinsize[1] THEN $ ypos = maxwinsize[1] - y_scroll_size ENDIF ELSE (*info).hasScrollBars = 0 ; Zoom window does not exist. Create it. zoomtlb = Widget_Base(Title='Zoomed Image', Group=event.top, TLB_Frame_Attr=1, $ XOffset=xpos, YOffset=ypos, KILL_NOTIFY='cgZImage_ZoomDied', $ UVALUE=event.top, X_Scroll_Size=x_scroll_size, Y_Scroll_Size=y_scroll_size) zoomdraw = Widget_Draw(zoomtlb, XSize=zoomXSize, YSize=zoomYSize, $ /MOTION_EVENTS, Event_Pro='cgZImage_ZoomWindow_Events') Widget_Control, zoomtlb, /Realize Widget_Control, zoomdraw, Get_Value=windowID (*info).zoomDrawID = zoomdraw (*info).zoomWindowID = windowID (*info).zoomTLB = zoomTLB WSet, windowID IF Ptr_Valid((*info).zoomedImage) THEN cgImage, *(*info).zoomedImage, PALETTE=*(*info).palette ENDELSE ; If the controls were mapped, unmap them. IF (*info).mapcontrols EQ 1 THEN BEGIN Widget_Control, (*info).controlID, Map=0 (*info).mapcontrols = 0 ENDIF ENDCASE 'MOTION': BEGIN ; 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 TVLCT, (*info).r, (*info).g, (*info).b, *(*info).bottom 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 ; Draw the zoom box. Device, Get_Decomposed=theState Device, Decomposed=1 PlotS, [(*info).xs, (*info).xs, (*info).xd, (*info).xd, (*info).xs], $ [(*info).ys, (*info).yd, (*info).yd, (*info).ys, (*info).ys], $ /Device, Color=cgColor((*info).boxcolor) Device, Decomposed=theState ENDCASE ENDCASE END ; ---------------------------------------------------------------------- ;+ ; Allows the user to interactively zoom into an image. Program controls are available ; by right-clicking in the full-sized image window. Zoom factors from 2x to 16x are ; available. Use the left mouse button to draw a box on the full-sized image to locate ; the region of the image to zoom. ; ; :Params: ; image: in, required, type=any ; A 2D or true-color image of any normal data type. If not a BYTE array, ; cgImage keywords for proper image scaling must be used to provide image ; scaling parameters. ; ; :Keywords: ; beta: in, optional, type=float, default=3.0 ; The beta factor in a Hyperpolic Sine stretch. Available only with 2D images. ; bottom: in, optional, type=integer, default=0 ; If the SCALE keyword is set, the image is scaled before display so that all ; displayed pixels have values greater than or equal to BOTTOM and less than ; or equal to TOP. Available only with 2D images. ; boxcolor: in, optional, type=string, default='gold' ; The name of the color of the rubber-band selection box. ; clip: in, optional, type=float, default=2 ; A number between 0 and 50 that indicates the percentage of pixels to clip ; off either end of the image histogram before performing a linear stretch. ; Available only with 2D images. ; exclude: in, optional, type=numeric ; The value to exclude in a standard deviation stretch. ; exponent: in, optional, type=float, default=4.0 ; The logarithm exponent in a logarithmic stretch. Available only with 2D images. ; filename: in, optional, type=string ; The name of a file that IDL can read with READ_IMAGE (e.g, GEOTIFF, TIF, JPEG, PNG, etc.). ; gamma: in, optional, type=float, default=1.5 ; The gamma factor in a gamma stretch. Available only with 2D images. ; group_leader: in, optional, type=long ; The widget identifier of the group leader for this program. When the group leader ; dies, this program will be destroyed, too. ; interpolate: in, optional, type=boolean, default=0 ; Set this keyword to interpolate with bilinear interpolation the display image as it ; is sized to its final position in the display window. Interpolation will potentially ; create image values that do not exist in the original image. The default is to do no ; interpolation, so that image values to not change upon resizing. Interpolation can ; result in smoother looking final images. ; map: in, optional, type=structure ; A cgMap object for navigating the input image. ; maxvalue: in, optional, type=varies ; If this value is defined, the data is linearly scaled between MINVALUE ; and MAXVALUE. MAXVALUE is set to MAX(image) by default. Setting this ; keyword to a value automatically sets `SCALE` to 1. If the maximum value of the ; image is greater than 255, this keyword is defined and SCALE=1. ; mean: in, optional, type=float, default=0.5 ; The mean factor in a logarithmic stretch. Available only with 2D images. ; minvalue: in, optional, type=varies ; If this value is defined, the data is linearly scaled between MINVALUE ; and `MAXVALUE`. MINVALUE is set to MIN(image) by default. Setting this ; keyword to a value automatically sets SCALE=1. If the minimum value of the ; image is less than 0, this keyword is defined and SCALE=1. ; missing_color: in, optional, type=string, default='white' ; The color name of the missing value. Available only with 2D images. ; missing_index: in, optional, type=integer, default=255 ; The index of the missing color in the final byte scaled image. Available only with 2D images. ; missing_value: in, optional, type=integer ; The number that represents the missing value in the image. Available only with 2D images. ; multiplier: in, optional, type=float ; The multiplication factor in a standard deviation stretch. The standard deviation ; is multiplied by this factor to produce the thresholds for a linear stretch. ; ncolors: in, optional, type=integer, default=256 ; If this keyword is supplied, the `TOP` keyword is ignored and the TOP keyword ; is set equal to NCOLORS-1. This keyword is provided to make cgImage easier ; to use with the color-loading programs such as cgLOADCT:: ; ; cgLoadCT, 5, NColors=100, Bottom=100 ; cgImage, image, NColors=100, Bottom=100 ; ; Setting this keyword to a value automatically sets SCALE=1. Available only with 2D images. ; negative: in, optional, type=boolean, default=0 ; Set this keyword if you want to display the image with a negative or reverse stretch. ; Available only with 2D images. ; palette: in, optional, type=byte ; Set this keyword to a 3x256 or 256x3 byte array containing the RGB color ; vectors to be loaded before the image is displayed. Such vectors can be ; obtained, for example, from cgLoadCT with the RGB_TABLE keyword:: ; ; cgLoadCT, 4, /BREWER, /REVERSE, RGB_TABLE=palette ; cgImage, cgDemoData(7), PALETTE=palette ; scale: in, optional, type=boolean, default=0 ; Set this keyword to byte scale the image before display. If this keyword is not set, ; the image is not scaled before display. This keyword will be set automatically by using ; any of the keywords normally associated with byte scaling an image. Available only with ; 2D images. If set, STRETCH is set to 1, unless it is set to another value. ; stretch: in, optional, type=integer/string, default=1 ; The type of scaling performed prior to display. May be specified as a number ; or as a string (e.g, 3 or "Log"). Available only with 2D images. ; ; Number Type of Stretch ; 0 None No scaling whatsoever is done. ; 1 Linear scaled = BytScl(image, MIN=minValue, MAX=maxValue) ; 2 Clip A histogram stretch, with a percentage of pixels clipped at both the top and bottom ; 3 Gamma scaled = GmaScl(image, MIN=minValue, MAX=maxValue, Gamma=gamma) ; 4 Log scaled = LogScl(image, MIN=minValue, MAX=maxValue, Mean=mean, Exponent=exponent) ; 5 Asinh scaled = AsinhScl(image, MIN=minValue, MAX=maxValue, Beta=beta) ; 6 SquareRoot A linear stretch of the square root histogram of the image values. ; 7 Equalization A linear stretch of the histogram equalized image histogram. ; 8 Gaussian A Gaussian normal function is applied to the image histogram. ; 9 MODIS Scaling done in the differential manner of the MODIS Rapid Response Team ; and implemented in the Coyote Library routine ScaleModis. ; sigma: in, optional, type=float, default=1.0 ; The sigma scale factor in a Gaussian stretch. Available only with 2D images. ; title: in, optional, type=string, default="" ; Set this keyword to the title of the plot window. ; top: in, optional, type=integer, default=255 ; If the SCALE keyword is set, the image is scaled before display so that all ; displayed pixels have values greater than or equal to BOTTOM and less than ; or equal to TOP. Available only with 2D images. ; zoomfactor: in, optional, type=string, default=3 ; Use this keyword to set the starting zoom factor. The values you can use are as follows:: ; 0: 'Actual' ; 1: '2x' ; 2: '3x' ; 3: '4x' ; 4: '5x' ; 5: '6x' ; 6: '7x' ; 7: '8x' ; 8: '12x' ; 9: '16x' ; ;- PRO cgZImage, image, $ BETA=beta, $ BOTTOM=bottom, $ BOXCOLOR=sboxcolor, $ CLIP=clip, $ EXCLUDE=exclude, $ EXPONENT=exponent, $ FILENAME=filename, $ GAMMA=gamma, $ GROUP_LEADER=group_leader, $ INTERPOLATE=interpolate, $ MAP=map, $ MAXVALUE=max, $ MEAN=mean, $ MISSING_COLOR=missing_color, $ MISSING_INDEX=missing_index, $ MISSING_VALUE=missing_value, $ NEGATIVE=negative, $ MINVALUE=min, $ MULTIPLIER=multiplier, $ NCOLORS=ncolors, $ PALETTE=palette, $ SCALE=scale, $ SIGMA=sigma, $ STRETCH=stretch, $ TITLE=title, $ TOP=top, $ ZOOMFACTOR=zoomfactor Compile_Opt idl2 ; Error handling Catch, theError IF theError NE 0 THEN BEGIN Catch, /CANCEL void = cgErrorMsg() RETURN ENDIF ; Was a filename used to pass in an image filename? Check to see if this is a GeoTiff image ; before doing anything else. If it is, use cgGeoMap to read it. Otherwise, read the image ; file with READ_IMAGE. IF N_Elements(filename) NE 0 THEN BEGIN check = Query_Tiff(filename, GEOTIFF=geo) IF (check EQ 1) && (Size(geo, /TNAME) EQ 'STRUCT') THEN BEGIN map = cgGeoMap(filename, IMAGE=image, Palette=palette) createdMap = 1 ENDIF ELSE BEGIN image = Read_Image(filename, r, g, b) IF N_Elements(r) NE 0 THEN palette = [[r],[g],[b]] ENDELSE ENDIF IF N_Elements(createdMap) EQ 0 THEN createdMap = 0 ; Was an image passed into the procedure? ; If not, find one in the IDL examples/data directory. IF N_Elements(image) EQ 0 THEN BEGIN image = ImageSelect(FILENAME='marsglobe.jpg', CANCEL=cancelled, /EXAMPLES) IF cancelled THEN RETURN ENDIF ; Make sure this is a 2D or true-color image. ndims = Size(image, /N_DIMENSIONS) IF (ndims LT 2) || (ndims GT 3) THEN Message, 'Only 2D or True-Color images are allowed in cgZImage.' ; Get image size. dims = Image_Dimensions(image, XSize=ixsize, YSize=iysize, $ XIndex=xindex, YIndex=yindex, TrueIndex=trueindex) IF trueIndex NE -1 THEN nframes = dims[trueIndex] ELSE nframes = 1 IF nframes GT 3 THEN BEGIN Help, image Message, 'Image does not have the correct dimensions for cgZImage.' ENDIF ; Check for keywords. IF N_Elements(sboxcolor) EQ 0 THEN boxcolor = 'gold' ELSE boxcolor = sboxcolor IF N_Elements(stretch) EQ 0 THEN BEGIN maxValue = Max(image, MIN=minValue) IF (minValue LT 0) || (maxValue GT 255) THEN stretch=1 ENDIF IF N_Elements(zoomfactor) EQ 0 THEN zoomfactor = 3 ELSE zoomfactor = 0 > zoomfactor < 9 ; Calculate a window size. Maximum window size is 600. aspect = Float(ixsize)/iysize MAXSIZE = 600 IF ixsize GT MAXSIZE OR iysize GT MAXSIZE THEN BEGIN IF ixsize NE iysize THEN BEGIN aspect = Float(iysize) / ixsize IF aspect LT 1 THEN BEGIN xsize = MAXSIZE ysize = (MAXSIZE * aspect) < MAXSIZE ENDIF ELSE BEGIN ysize = MAXSIZE xsize = (MAXSIZE / aspect) < MAXSIZE ENDELSE ENDIF ELSE BEGIN ysize = MAXSIZE xsize = MAXSIZE ENDELSE ENDIF ELSE BEGIN xsize = ixsize ysize = iysize ENDELSE ; Check cgImage keywords. IF N_Elements(beta) EQ 0 THEN betaptr = Ptr_New(/Allocate_Heap) ELSE betaptr = Ptr_New(beta) IF N_Elements(bottom) EQ 0 THEN bottomptr = Ptr_New(/Allocate_Heap) ELSE bottomptr = Ptr_New(bottom) IF N_Elements(clip) EQ 0 THEN clipptr = Ptr_New(/Allocate_Heap) ELSE clipptr = Ptr_New(clip) IF N_Elements(exclude) EQ 0 THEN excludeptr = Ptr_New(/Allocate_Heap) ELSE excludeptr = Ptr_New(exclude) IF N_Elements(exponent) EQ 0 THEN exponentptr = Ptr_New(/Allocate_Heap) ELSE exponentptr = Ptr_New(exponent) IF N_Elements(gamma) EQ 0 THEN gammaptr = Ptr_New(/Allocate_Heap) ELSE gammaptr = Ptr_New(gamma) IF N_Elements(interpolate) EQ 0 THEN interpolateptr = Ptr_New(/Allocate_Heap) ELSE interpolateptr = Ptr_New(interpolate) IF N_Elements(map) EQ 0 THEN mapptr = Ptr_New(/Allocate_Heap) ELSE mapptr = Ptr_New(map) IF N_Elements(max) EQ 0 THEN maxptr = Ptr_New(/Allocate_Heap) ELSE maxptr = Ptr_New(max) IF N_Elements(mean) EQ 0 THEN meanptr = Ptr_New(/Allocate_Heap) ELSE meanptr = Ptr_New(mean) IF N_Elements(missing_color) EQ 0 THEN missing_colorptr = Ptr_New(/Allocate_Heap) ELSE missing_colorptr = Ptr_New(missing_color) IF N_Elements(missing_index) EQ 0 THEN missing_indexptr = Ptr_New(/Allocate_Heap) ELSE missing_indexptr = Ptr_New(missing_index) IF N_Elements(missing_value) EQ 0 THEN missing_valueptr = Ptr_New(/Allocate_Heap) ELSE missing_valueptr = Ptr_New(missing_value) IF N_Elements(negative) EQ 0 THEN negativeptr = Ptr_New(/Allocate_Heap) ELSE negativeptr = Ptr_New(negative) IF N_Elements(min) EQ 0 THEN minptr = Ptr_New(/Allocate_Heap) ELSE minptr = Ptr_New(min) IF N_Elements(multiplier) EQ 0 THEN multiplierptr = Ptr_New(/Allocate_Heap) ELSE multiplierptr = Ptr_New(multiplier) IF N_Elements(ncolors) EQ 0 THEN ncolorsptr = Ptr_New(/Allocate_Heap) ELSE ncolorsptr = Ptr_New(ncolors) IF N_Elements(palette) EQ 0 THEN paletteptr = Ptr_New(/Allocate_Heap) ELSE paletteptr = Ptr_New(palette) IF N_Elements(scale) EQ 0 THEN scaleptr = Ptr_New(/Allocate_Heap) ELSE scaleptr = Ptr_New(scale) IF N_Elements(sigma) EQ 0 THEN sigmaptr = Ptr_New(/Allocate_Heap) ELSE sigmaptr = Ptr_New(sigma) IF N_Elements(stretch) EQ 0 THEN stretchptr = Ptr_New(/Allocate_Heap) ELSE stretchptr = Ptr_New(stretch) IF N_Elements(top) EQ 0 THEN topptr = Ptr_New(/Allocate_Heap) ELSE topptr = Ptr_New(top) ; Create a top-level base for this program. No resizing of this base. tlb = Widget_Base(TLB_Frame_Attr=1, TITLE=title) ; 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) factorString = ['Actual', '2x', '3x', '4x', '5x', '6x', '7x', '8x', '12x', '16x'] factors = [Indgen(8) + 1, 12, 16] zoomfactorID = Widget_DropList(controlID, Value=factorString, $ Event_Pro='cgZImage_Factor', UValue=factors, Title='Zoom Factor') IF trueindex EQ -1 THEN BEGIN colors = Widget_Button(controlID, Value='Load Image Colors', Event_Pro='cgZImage_LoadColors') ENDIF void = Widget_Button(controlID, Value='Change Selection Box Color', Event_Pro='cgZImage_BoxColor') quitter = Widget_Button(controlID, Value='Exit Program', $ Event_Pro='cgZImage_Quit') drawbase = Widget_Base(tlb, Map=1, Column=1) drawID = Widget_Draw(drawbase, XSize=xsize, YSize=ysize, $ Button_Events=1, Event_Pro='cgZImage_DrawEvents') statusbar = Widget_Label(drawbase, Value="Ready for Zooming", SCR_XSIZE=xsize, /Sunken_Frame) ; Realize the program. Widget_Control, tlb, /Realize ; Set the initial default zoom factor. Widget_Control, zoomfactorID, SET_DROPLIST_SELECT=zoomfactor ; 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, drawID, Get_Value=drawIndex WSet, drawIndex cgImage, image, $ BETA=*betaptr, $ BOTTOM=*bottomptr, $ CLIP=*clipptr, $ EXCLUDE=*excludeptr, $ EXPONENT=*exponentptr, $ GAMMA=*gammaptr, $ INTERPOLATE=*interpolateptr, $ MAXVALUE=*maxptr, $ MEAN=*meanptr, $ MISSING_COLOR=*missing_colorptr, $ MISSING_INDEX=*missing_indexptr, $ MISSING_VALUE=*missing_valueptr, $ NEGATIVE=*negativeptr, $ MINVALUE=*minptr, $ MULTIPLIER=*multiplierptr, $ NCOLORS=*ncolorsptr, $ PALETTE=*paletteptr, $ SCALE=*scaleptr, $ SIGMA=*sigmaptr, $ STRETCH=*stretchptr, $ TOP=*topptr ; Set the title of the window. IF N_Elements(title) EQ 0 THEN BEGIN Widget_Control, tlb, TLB_Set_Title='Full Size Image (' + StrTrim(drawIndex,2) + ') -- ' + $ 'Right Click for Controls.' ENDIF ; Set the current zoom factor factor = factors[zoomfactor] ; 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 cgImage, image, $ BETA=*betaptr, $ BOTTOM=*bottomptr, $ CLIP=*clipptr, $ EXCLUDE=*excludeptr, $ EXPONENT=*exponentptr, $ GAMMA=*gammaptr, $ INTERPOLATE=*interpolateptr, $ MAXVALUE=*maxptr, $ MEAN=*meanptr, $ MISSING_COLOR=*missing_colorptr, $ MISSING_INDEX=*missing_indexptr, $ MISSING_VALUE=*missing_valueptr, $ NEGATIVE=*negativeptr, $ MINVALUE=*minptr, $ MULTIPLIER=*multiplierptr, $ NCOLORS=*ncolorsptr, $ PALETTE=*paletteptr, $ SCALE=*scaleptr, $ SIGMA=*sigmaptr, $ STRETCH=*stretchptr, $ TOP=*topptr ; Get color vectors for this application. IF N_Elements(r) EQ 0 THEN TVLCT, r, g, b, /Get ; Scale the data, because this is what you will show in the zoomed window. scaled = cgImgScl(image, $ BETA=*betaptr, $ BOTTOM=*bottomptr, $ CLIP=*clipptr, $ EXCLUDE=*excludeptr, $ EXPONENT=*exponentptr, $ GAMMA=*gammaptr, $ INTERPOLATE=*interpolateptr, $ MAXVALUE=*maxptr, $ MEAN=*meanptr, $ MISSING_INDEX=*missing_indexptr, $ MISSING_VALUE=*missing_valueptr, $ NEGATIVE=*negativeptr, $ MINVALUE=*minptr, $ MULTIPLIER=*multiplierptr, $ NCOLORS=*ncolorsptr, $ SCALE=*scaleptr, $ SIGMA=*sigmaptr, $ STRETCH=*stretchptr, $ TOP=*topptr) ; Create an info structure to hold information required by the program. info = Ptr_New( { $ image:image, $ ; The original image. scaled:scaled, $ ; The scaled image. zoomedimage:Ptr_New(), $ ; The scaled and resized subimage. xsize:ixsize, $ ; The x size of the image. ysize:iysize, $ ; The y size of the image. drawIndex:drawIndex, $ ; The draw window index number. pixIndex:pixIndex, $ ; The pixmap window index number. boxcolor:boxcolor, $ ; The name of the drawing color. 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. zoomTLB: -1, $ ; The zoom window TLB. statusbar:statusbar, $ ; The statusbar identifier. r:r, $ ; The red color vector. g:g, $ ; The green color vector. b:b, $ ; The blue color vector. beta:betaptr, $ bottom:bottomptr, $ clip:clipptr, $ createdMap: createdmap, $ exclude:excludeptr, $ exponent:exponentptr, $ gamma:gammaptr, $ interpolate:interpolateptr, $ map:mapptr, $ max:maxptr, $ mean:meanptr, $ missing_color:missing_colorptr, $ missing_index:missing_indexptr, $ missing_value:missing_valueptr, $ negative:negativeptr, $ min:minptr, $ multiplier:multiplierptr, $ ncolors:ncolorsptr, $ palette:paletteptr, $ scale:scaleptr, $ sigma:sigmaptr, $ stretch:stretchptr, $ top:topptr, $ xrange: [0,ixsize], $ yrange: [0,iysize], $ zxsize: 0, $ zysize: 0, $ zoomfactor:factor, $ ; The initial zoom factor. mapcontrols:0, $ ; A flag to tell if the controls are mapped. xindex:xindex, $ ; The X size index. yindex:yindex, $ ; The Y size index. trueIndex:trueIndex, $ ; The "true-color" index. 0 if image is 2D. MAXSIZE:800, $ ; The maximum window size. hasScrollBars: 0, $ ; A flag indicating the zoom window has scroll bars. controlID:controlID}, /No_Copy) ; 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 ; Register this program and set up the event loop. XManager, 'cgzimage', tlb, Cleanup='cgZImage_Cleanup', Group_Leader=group_leader, /No_Block END ; ----------------------------------------------------------------------