Drawing a Line in a Draw Widget Window
QUESTION: How do I draw a free-hand line in a draw widget window?
ANSWER: This question was asked recently on the IDL newsgroup. I modified some code that I wrote earlier to explain how to draw a rubberband box in a widget window. The example here can easily be extended to answer the question, "How to I create an irregular region of interest (ROI) in a widget program?"
The technique illustrated here uses the Device Copy method of erasing the display. For more information on this technique, see the rubberband box programming tip for regular IDL windows.
In the code below, use the LEFT mouse button to draw a free-hand line in the draw widget window. Use the RIGHT mouse button to draw a straight line in the draw widget window.
The essential algorithm is this. On a draw widget DOWN event I create a pixmap and copy the display information into it, set the starting coordinate of the line, and turn motion events on for the draw widget. On a draw widget MOTION event I erase the previous line (with a Device Copy), get the next line point, and draw another line in the display window. On a draw widget UP event I erase the last line drawn, turn motion events off for the draw widget, delete the pixmap, and do whatever it is I want to do with the line. (Here I simply leave it drawn on the image. You can erase it with the Erase Window button under the File menu item.)
The code for this widget program, named DrawLine, looks like this:
PRO Drawline_Button_Events, event ; All program button events handled here. Widget_Control, event.top, Get_UValue=info, /No_Copy ; Which button caused this event? Widget_Control, event.id, Get_Value=thisButtonValue CASE thisButtonValue OF 'Quit': BEGIN Widget_Control, event.top, /Destroy RETURN ENDCASE 'Erase Lines': BEGIN WSet, info.wid TV, BytScl(info.image, Top=info.drawColor-1) ENDCASE ENDCASE Widget_Control, event.top, Set_UValue=info, /No_Copy END ;------------------------------------------------------------------ PRO Drawline_Draw_Events, event ; All program draw widget events handled here. ; 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] whichButton = ['NONE', 'LEFT', 'MIDDLE', 'NONE', 'RIGHT'] CASE thisEvent OF 'DOWN': BEGIN ; Which button was used? LEFT or RIGHT? info.buttonUsed = whichButton(event.press) ; 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] ; Initialize the starting coordinates of the line. IF info.buttonUsed EQ 'RIGHT' THEN BEGIN info.xstart = event.x info.ystart = event.y ENDIF ELSE BEGIN info.xvalues = Ptr_New([event.x]) info.yvalues = Ptr_New([event.y]) ENDELSE ENDCASE 'UP': BEGIN ; Erase the last line 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 events queued for widget. Widget_Control, info.drawID, Draw_Motion_Events=0, Clear_Events=1 ; Draw the final line. IF info.buttonUsed EQ 'RIGHT' THEN BEGIN PlotS, [info.xstart, event.x], [info.ystart, event.y], $ /Device, Color=info.drawColor ; Reinitialize the line starting coordinates. info.xstart = -1 info.ystart = -1 ENDIF ELSE BEGIN PlotS, *info.xvalues, *info.yvalues, /Device, $ Color=info.drawColor ; Reinitialize the pointers. Ptr_Free, info.xvalues Ptr_Free, info.yvalues info.xvalues = Ptr_New() info.yvalues = Ptr_New() ENDELSE ENDCASE 'MOTION': BEGIN ; Here is where the actual line is drawn and erased. ; First, erase the last line. WSet, info.wid Device, Copy=[0, 0, info.xsize, info.ysize, 0, 0, info.pixID] IF info.buttonUsed EQ 'RIGHT' THEN BEGIN ; Draw a straight line. PlotS, [info.xstart, event.x], [info.ystart, event.y], /Device, $ Color=info.drawColor ENDIF ELSE BEGIN ; Get the points of the new free-hand line and draw it. *info.xvalues = [*info.xvalues, event.x] *info.yvalues = [*info.yvalues, event.y] PlotS, *info.xvalues, *info.yvalues, /Device, Color=info.drawColor ENDELSE ENDCASE ENDCASE ; Store the info structure. Widget_Control, event.top, Set_UValue=info, /No_Copy END ;------------------------------------------------------------------ PRO Drawline ; 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='Free-Hand Lines in a Widget Program', $ MBar=menubase) ; Create some menu bar buttons. fileID = Widget_Button(menubase, Value='File', $ Event_Pro='Drawline_Button_Events') eraseID = Widget_Button(fileID, Value='Erase Lines') quitID = Widget_Button(fileID, Value='Quit') ; Create the draw widget graphics window. Turn button events ON. drawID = Widget_Draw(tlb, XSize=xsize, YSize=ysize, Button_Events=1, $ Event_Pro='Drawline_Draw_Events') ; 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. drawColor = !D.N_Colors-1 TVLCT, 255, 255, 0, drawColor TV, BytScl(Image, Top=drawColor-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. xsize:xsize, $ ; The X size of the graphics window. ysize:ysize, $ ; The Y size of the graphics window. xstart:-1, $ ; The starting X coordinate of the line. ystart:-1, $ ; The starting Y coordinate of the line. xvalues:Ptr_New(), $ ; The X coordinates of the free-hand line. yvalues:Ptr_New(), $ ; The Y coordinates of the free-hand line. buttonUsed:'NONE', $ ; A flag to indicate which button is used. drawColor:drawColor } ; The rubberband box color. ; Store the info structure. Widget_Control, tlb, Set_UValue=info, /No_Copy ; Start the program going. XManager, 'drawline', tlb, /No_Block END
To run this program, simple type this:
IDL> Drawline
Click and drag the LEFT button inside the graphics window to draw a freehand line. Click and drag the RIGHT button inside the graphics window to draw a straight line.
Copyright © 1997-1998 David W. Fanning
Last Updated 31 March 1998