Fanning Software Consulting

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.

Google
 
Web Coyote's Guide to IDL Programming