Drawing a Rubberband Box in a Widget Window
QUESTION: How do I draw a rubberband box on an image in a widget graphics window?
ANSWER: Unlike drawing a rubberband box in a normal IDL graphics window (see the other rubberband box programming tip), you do not want to write a loop in a widget program. Instead, you want to take advantage of the fact that the widget program is itself a loop. Thus, the program has to be written in a way that the event handler for the graphics window has to handle discrete, one-shot events in a way that simulates a loop.
The technique illustrated here uses the Device Copy method of erasing the display. For more information on this technique see the other rubberband box programming tip for drawing boxes in regular IDL graphics windows.
The basic difference in a widget program is that you use different kinds of widget events to direct program action. In this case, I use a draw widget DOWN event, a draw widget UP event, and a draw widget MOTION event as indicators of what should be done in the program event handler.
The essential algorithm is this. On a draw widget DOWN event I create a pixmap and copy the display information into it, set the static corner of the box, and turn motion events on for the draw widget. On a draw widget MOTION event I erase the previous box (with a Device Copy), get the new dynamic coordinates of the box, and draw another box on the display window. On a draw widget UP event I erase the last box drawn, turn motion events off for the draw widget, delete the pixmap, and do whatever it is I want to do with the box information. (Here I simply print out the average value of pixels inside the box.)
The code for this widget program, named DrawBox_Widget, looks like this:
PRO Drawbox_Widget_Events, event ; This is the event handler for the draw widget graphics window. ; Deal only with DOWN, UP, and MOTION events. IF event.type GT 2 THEN RETURN ; Get the info structure. Widget_Control, event.top, Get_UValue=info, /No_Copy ; What kind of event is this? eventTypes = ['DOWN', 'UP', 'MOTION'] thisEvent = eventTypes[event.type] CASE thisEvent OF 'DOWN': BEGIN ; Turn motion events on for the draw widget. Widget_Control, info.drawID, Draw_Motion_Events=1 ; Create a pixmap. Store its ID. Copy window contents into it. Window, /Free, /Pixmap, XSize=info.xsize, YSize=info.ysize info.pixID = !D.Window Device, Copy=[0, 0, info.xsize, info.ysize, 0, 0, info.wid] ; Get and store the static corner of the box. info.sx = event.x info.sy = event.y ENDCASE 'UP': BEGIN ; Erase the last box drawn. Destroy the pixmap. WSet, info.wid Device, Copy=[0, 0, info.xsize, info.ysize, 0, 0, info.pixID] WDelete, info.pixID ; Turn draw motion events off. Clear any events queued for widget. Widget_Control, info.drawID, Draw_Motion_Events=0, Clear_Events=1 ; Order the box coordinates. sx = Min([info.sx, event.x], Max=dx) sy = Min([info.sy, event.y], Max=dy) ; Here is where you do something useful with the box. ; For example purposes, I'll compute the average pixel ; value of the pixels enclosed by the box and print it. Print, 'Average Pixel Value of Pixels Enclosed by Box: ',$ Total(info.image[sx:dx, sy:dy]) / N_Elements(info.image[sx:dx, sy:dy]) ENDCASE 'MOTION': BEGIN ; Here is where the actual box is drawn and erased. ; First, erase the last box. WSet, info.wid Device, Copy=[0, 0, info.xsize, info.ysize, 0, 0, info.pixID] ; Get the coodinates of the new box and draw it. sx = info.sx sy = info.sy dx = event.x dy = event.y PlotS, [sx, sx, dx, dx, sx], [sy, dy, dy, sy, sy], /Device, $ Color=info.boxColor ENDCASE ENDCASE ; Store the info structure. Widget_Control, event.top, Set_UValue=info, /No_Copy END ;------------------------------------------------------------------ PRO Drawbox_Widget ; This is the widget definition module for the program. ; Open an image data set. file = Filepath(SubDirectory=['examples','data'], 'ctscan.dat') OpenR, lun, file, /Get_Lun image = BytArr(256, 256) ReadU, lun, image Free_Lun, lun xsize = (Size(image))[1] ysize = (Size(image))[2] ; Create the TLB. tlb = Widget_Base(Title='Rubberband Box in a Widget Program') ; Create the draw widget graphics window. Turn button events ON. drawID = Widget_Draw(tlb, XSize=xsize, YSize=ysize, Button_Events=1) ; Realize widgets and make draw widget the current window. Widget_Control, tlb, /Realize Widget_Control, drawID, Get_Value=wid WSet, wid ; Load drawing color and display the image. boxColor = !D.N_Colors-1 TVLCT, 255, 255, 0, boxColor TV, BytScl(Image, Top=boxColor-1) ; Create an "info" structure with information to run the program. info = { image:image, $ ; The image data. wid:wid, $ ; The window index number. drawID:drawID, $ ; The draw widget identifier. pixID:-1, $ ; The pixmap identifier (undetermined now). xsize:xsize, $ ; The X size of the graphics window. ysize:ysize, $ ; The Y size of the graphics window. sx:-1, $ ; The X value of the static corner of the box. sy:-1, $ ; The Y value of the static corner of the box. boxColor:boxColor } ; The rubberband box color. ; Store the info structure. Widget_Control, tlb, Set_UValue=info, /No_Copy ; Start the program going. XManager, 'drawbox_widget', tlb, /No_Block, $ Event_Handler='DrawBox_Widget_Events' END
To run this program, simple type this:
IDL> DrawBox_Widget
Click and drag inside the graphics window to draw a rubberband box.
Copyright © 1997-1998 David W. Fanning
Last Updated 19 March 1998