Viewing contents of file '../idllib/contrib/fanning/process.pro'
;+
; NAME:
;       PROCESS
;
; PURPOSE:
;       The purpose of this routine is to demonstrate a simple
;       image processing program that runs as a "color aware"
;       application. The program works on 8-bit, 16-bit, and
;       24-bit displays. The image is displayed in a resizeable
;       graphics window and the window contents can be saved as
;       GIF or JPEG images. The application can "protect" its
;       colors from other applications that change colors. The
;       color protection is implemented via draw widget EXPOSE
;       events or the Refresh Colors button in the menubar.
;
; AUTHOR:
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       2642 Bradbury Court
;       Fort Collins, CO 80521 USA
;       Phone: 970-221-0438
;       E-mail: davidf@dfanning.com
;       Coyote's Guide to IDL Programming: http://www.dfanning.com
;
; CATEGORY:
;       Widgets.
;
; CALLING SEQUENCE:
;       PROCESS
;
; INPUTS:
;       image: An optional 2D image. The image is always scaled.
;
; KEYWORD PARAMETERS:
;       BOTTOM: The lowest color index of the colors to be changed.
;
;       GROUP: The group leader for this program. When the group leader
;       is destroyed, this program will be destroyed.
;
;       NCOLORS: This is the number of colors to load when a color table
;       is selected.
;
; COMMON BLOCKS:
;       None.
;
; SIDE EFFECTS:
;       This is a non-blocking widget.
;
; RESTRICTIONS:
;       The GETIMAGE and XCOLORS programs from the Fanning Consulting
;       web page are required: http://www.dfanning.com/.
;
; EXAMPLE:
;       To run the program, type:
;
;       PROCESS
;
; MODIFICATION HISTORY:
;       Written by:     David Fanning, 13 April 97.
;       Fixed a bug in the TVImage call. 25 June 97. DWF.
;       Extensively modified to incorporate changing ideas
;         about color protection and operation on 8-bit and
;         24-bit displays. 19 Oct 97. DWF.
;       Whoops. Forgot to make *this* draw widget the current
;         graphics window. 15 Nov 97. DWF.
;       IDL 5.1 changed the way color decomposition works. Had
;         to find a fix for this. 25 May 98. DWF.
;-

PRO Process_Quit, event

   ; This event handler destoys the program's TLB.

Widget_Control, event.top, /Destroy
END ;----------------------------------------------------------------------



PRO Process_Cleanup, tlb

   ; Clean up routine for this program. Delete pixmaps
   ; and free pointers. Come here when the program is
   ; destroyed.

Widget_Control, tlb, Get_UValue=info, /No_Copy
IF N_Elements(info) EQ 0 THEN BEGIN
   Heap_GC
   RETURN
ENDIF

WDelete, info.pixid
Ptr_Free, info.image
END ;----------------------------------------------------------------------



PRO Process_Open_File, event

   ; This event handler is responsible for opening new
   ; image files.

Widget_Control, event.top, Get_UValue=info, /No_Copy

Catch, error
IF error NE 0 THEN BEGIN
   IF error EQ -77 THEN BEGIN
      ok = Dialog_Message('Program GETIMAGE required from Coyote Library.')
      Widget_Control, event.top, Set_UValue=info, /No_Copy
      RETURN
   ENDIF
   ok = Dialog_Message(!Err_String)
   Widget_Control, event.top, Set_UValue=info, /No_Copy
   RETURN
ENDIF

   ; Expose events off until after GetImage is destroyed.

Widget_Control, info.drawID, Draw_Expose_Events=0
image = GetImage(Parent=event.top)
Widget_Control, info.drawID, Draw_Expose_Events=1

s = Size(image)
IF s[0] EQ 2 THEN BEGIN
   Ptr_Free, info.image
   info.image = Ptr_New( BytScl(image, Top=info.ncolors-1) + info.bottom )
ENDIF

IF s[0] GT 2 THEN $
   ok = Dialog_Message('Image parameter must be 2D.')

   ; Display in window as last processed.

pseudoEvent = {WIDGET_BUTTON, ID:info.lastid, TOP:event.top, $
      HANDLER:0L, SELECT:1}
Widget_Control, info.lastid, Send_Event=pseudoEvent

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_GIF, event

   ; This event handler is responsible for creating a GIF file.

thisFile = Dialog_Pickfile(/Write, File='idl.gif')
IF thisFile EQ '' THEN RETURN

Widget_Control, event.top, Get_UValue=info, /No_Copy
WSet, info.wid
thisImage = TVRD()
TVLCT, r, g, b, /Get
Write_GIF, thisFile, thisImage, r, g, b
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_JPEG, event

   ; This event handler is responsible for creating a JPEG file.

thisFile = Dialog_Pickfile(/Write, File='idl.jpg')
IF thisFile EQ '' THEN RETURN

Widget_Control, event.top, Get_UValue=info, /No_Copy
WSet, info.wid
thisImage = TVRD()
TVLCT, r, g, b, /Get

   ; Create 24-bit color image.

image24 = BytArr(3, info.xsize, info.ysize)
image24(0,*,*) = r(thisImage)
image24(1,*,*) = g(thisImage)
image24(2,*,*) = b(thisImage)

   ; Write file. Normal image quality in this lossy format.

Write_JPEG, thisFile, image24, True=1, Quality=75
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_Refresh_Colors, event

   ; This event handler is responsible for refreshing
   ; colors in the event they do not refresh from EXPOSE events.

Widget_Control, event.top, Get_UValue=info, /No_Copy

WSet, info.wid
TVLCT, info.r, info.g, info.b, info.bottom
IF info.true THEN BEGIN
   Device, Decomposed=0
   pseudoEvent = {WIDGET_BUTTON, ID:info.lastid, TOP:event.top, $
      HANDLER:0L, SELECT:1}
   Widget_Control, info.lastid, Send_Event=pseudoEvent
ENDIF
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_Draw_Events, event

   ; This event handler is responsible for color processing
   ; and EXPOSE events.

Widget_Control, event.top, Get_UValue=info, /No_Copy

WSet, info.wid
Device, Decomposed=0

thisEvent = Tag_Names(event, /Structure)

   ; Reload colors for this widget. This is necessary
   ; if, for example, colors are changed from the command line.
   ; If this is a 24-bit display, the graphic must also be
   ; redisplayed by generating a button event.

IF thisEvent EQ 'XCOLORS_LOAD' THEN BEGIN
   WSET, info.wid
   info.r = event.r(info.bottom:info.ncolors-1+info.bottom)
   info.g = event.g(info.bottom:info.ncolors-1+info.bottom)
   info.b = event.b(info.bottom:info.ncolors-1+info.bottom)
   IF info.true THEN BEGIN
      Device, Decomposed=0
      pseudoEvent = {WIDGET_BUTTON, ID:info.lastid, TOP:event.top, $
         HANDLER:0L, SELECT:1}
      Widget_Control, info.lastid, Send_Event=pseudoEvent
   ENDIF
ENDIF

   ; Expose events processed here.

IF thisEvent EQ 'WIDGET_DRAW' THEN BEGIN ; Button events

   TVLCT, info.r, info.g, info.b, info.bottom

   IF event.type EQ 4 THEN BEGIN ; Expose events only

      IF NOT info.true THEN BEGIN ; 8-bit
         WSet, info.wid
         DEVICE, COPY=[0, 0,info.xsize, info.ysize, 0, 0, info.pixid]

      ENDIF ELSE BEGIN ; 24-bit
         Device, Decomposed=0
         pseudoEvent = {WIDGET_BUTTON, ID:info.lastid, TOP:event.top, $
            HANDLER:0L, SELECT:1}
         Widget_Control, info.lastid, Send_Event=pseudoEvent
      ENDELSE

   ENDIF
ENDIF

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_Resize, event

   ; This event handler is responsible for resize events.

Widget_Control, event.top, Get_UValue=info, /No_Copy

   ; Resize the draw widget. Update info sizes.

Widget_Control, info.drawID, XSize=event.x, YSize=event.y
info.xsize = event.x
info.ysize = event.y

   ; Delete and recreate the pixmap if necessary.

IF NOT info.true THEN BEGIN
   WDelete, info.pixid
   Window, /Pixmap, /Free, XSize=event.x, YSize=event.y
   info.pixid = !D.Window
ENDIF

   ; Execute the last command by building an image processing
   ; event to send to the Image Processing event handler.

pseudoEvent = {WIDGET_BUTTON, ID:info.lastid, TOP:event.top, $
   HANDLER:0L, SELECT:1}
Widget_Control, info.lastid, Send_Event=pseudoEvent

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_Load_Colors, event

   ; This event handler responds to the Data Colors button
   ; by calling XCOLORS. A "callback" ID is furnished with the
   ; NOTIFYID keyword.

Catch, error
IF error NE 0 THEN BEGIN
   IF error EQ -77 THEN BEGIN
      ok = Dialog_Message('Program XCOLORS required from Coyote Library.')
      Widget_Control, event.top, Set_UValue=info, /No_Copy
      RETURN
   ENDIF
   ok = Dialog_Message(!Err_String)
   Widget_Control, event.top, Set_UValue=info, /No_Copy
   RETURN
ENDIF

Widget_Control, event.top, Get_UValue=info, /No_Copy
XColors, Group=event.top, NColors=info.ncolors, Title='Process Colors', $
   Bottom=info.bottom, NotifyID=[info.drawID, event.top]
Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO Process_Image_Processing, event

   ; This event handler responds to Image Processing button
   ; events.

Widget_Control, event.top, Get_UValue=info, /No_Copy

   ; Make the pixmap active if in 8-bit. Otherwise,
   ; the display window is the active window.

IF NOT info.true THEN WSet, info.pixid ELSE BEGIN
    WSet, info.wid
    Device, Decomposed=0
ENDELSE

   ; Button events handled here. Branch on button value.

xsize = info.xsize
ysize = info.ysize
Widget_Control, event.id, Get_Value=buttonValue
CASE buttonValue OF
     'Sobel'   : TV, Sobel(Congrid(*info.image, xsize, ysize, /Interp))
     'Roberts' : TV, Roberts(Congrid(*info.image, xsize, ysize, /Interp))
     'Boxcar'  : TV, Smooth(Congrid(*info.image, xsize, ysize, /Interp), 7)
     'Median'  : TV, Median(Congrid(*info.image, xsize, ysize, /Interp), 7)
     'Original Image': TV, Congrid(*info.image, xsize, ysize, /Interp)
ENDCASE

   ; If 8-bit copy from pixmap to display window.

IF NOT info.true THEN BEGIN
   WSet, info.wid
   DEVICE, Copy=[0, 0, xsize, ysize, 0, 0, info.pixid]
ENDIF

   ; Update last event ID. This is how you know what to
   ; put in the display window when you resize it.

info.lastID = event.id

Widget_Control, event.top, Set_UValue=info, /No_Copy
END ;----------------------------------------------------------------------



PRO PROCESS, image, NColors=ncolors, Bottom=bottom, Group=group

   ; This program demonstrates simple image processing capabilities.

On_Error, 1

   ; Must have IDL 5 because of pointers and other functionality.

thisRelease = StrMid(!Version.Release, 0, 1)
IF thisRelease NE '5' THEN BEGIN
   ok = Widget_Message('This program requires IDL 5 functionality. Sorry.')
   RETURN
ENDIF

   ; Load an image if necessary.

IF N_Params(image) EQ 0 THEN BEGIN
  filename = Filepath(Subdir=['examples','data'], 'worldelv.dat')
  OPENR, lun, filename, /Get_LUN
  image = BytArr(360,360)
  ReadU, lun, image
  FREE_LUN, lun
ENDIF

   ; Is the image 2D?

s = SIZE(image)
IF s[0] NE 2 THEN Message, 'Image parameter must be 2D'
xsize = s[1]
ysize = s[2]

   ; 8-bit color or something else?

Window, XSize=10, YSize=10, /Pixmap, /Free
WDelete, !D.Window
IF !D.N_Colors GE 256 THEN BEGIN
    true = 1
    Device, Decomposed=0
ENDIF ELSE true = 0

   ; Check keywords.

IF N_Elements(ncolors) EQ 0 THEN ncolors = (256 < !D.N_Colors)
IF N_Elements(bottom) EQ 0 THEN bottom = 0

   ; Scale the image and load it into a pointer.

thisImage = Ptr_New(BytScl(image, Top=ncolors-1) + bottom)

   ; Create a top-level base for this program. Give it a title.
   ; Create a menubase to hold menu buttons.

tlb = Widget_Base(Title='Resizeable Image Processing Program', Column=1, $
   /TLB_Size_Events, MBar=menubase)

   ; Create a "File" pull-down menu to hold file operation processes.

filer = Widget_Button(menubase, Menu=1, Value='File')

openit = Widget_Button(filer, Value='Open...', Event_Pro='Process_Open_File')
gif = Widget_Button(filer, Value='Save as GIF file...', $
   Event_Pro='Process_GIF')
gif = Widget_Button(filer, Value='Save as JPEG file...', $
   Event_Pro='Process_JPEG')
quitter = Widget_Button(filer, Value='Quit', /Separator, $
   Event_Pro='Process_Quit')

   ; Create a "Colors" button. The Refresh Button is necessary because
   ; it is not possible to "protect" program colors in every instance.
   ; In cases in which program colors are not protected, this button
   ; will do the job.

colors = Widget_Button(menubase, Menu=2, Value='Colors')
dataColors = Widget_Button(colors, Value='Data Colors', $
   Event_Pro='Process_Load_Colors')
refreshColors = Widget_Button(colors, Value='Refresh Colors', $
   Event_Pro='Process_Refresh_Colors')

   ; Create an "Image Processing" pull-down menu. Separate event
   ; handler for all image processing buttons.

processing = Widget_Button(menubase, Menu=1, Value='Image Processing', $
   Event_Pro='Process_Image_Processing')

   edge = Widget_Button(processing, Menu=1, Value='Edge Enhancement')
      sobel = Widget_Button(edge, Value='Sobel')
      robert = Widget_Button(edge, Value='Roberts')

   smooth = Widget_Button(processing, Menu=1, Value='Image Smoothing')
      boxcar = Widget_Button(smooth, Value='Boxcar')
      median = Widget_Button(smooth, Value='Median')

   orig = Widget_Button(processing, Value='Original Image')

   ; Create a drawbase as a child of the top-level base.

drawbase = Widget_Base(tlb, Row=1)

   ; Create a draw widget to hold the image. Separate event handler.
   ; Retain equals 0 to facility expose events. Expose event is not
   ; set until AFTER graphic is displayed in the draw widget to avoid
   ; having two events upon program startup.

drawID = Widget_Draw(drawbase, XSize=xsize, YSize=ysize, $
   Retain=0, Event_Pro='Process_Draw_Events')

   ; Realize the widget program

Widget_Control, tlb, /Realize

   ; Get the value of the draw widget, which is its window index number.
   ; Can only be done AFTER the draw widget is realized. Also, turn
   ; expose events on.

Widget_Control, drawID, Get_Value=wid, Draw_Expose_Events=1

   ; Make the draw widget the current graphics window

WSet, wid
Device, Decomposed=0

   ; Load a colortable and display the image

LoadCT, 4, NColors=ncolors, Bottom=bottom, /Silent

   ; Get color vectors.

TVLCT, r, g, b, /Get

   ; Display scaled image by sending an event to the
   ; button event handler.

pseudoEvent = {WIDGET_BUTTON, ID:orig, TOP:tlb, $
   HANDLER:0L, SELECT:1}
Widget_Control, orig, Send_Event=pseudoEvent

   ; If 8-bit, create a pixmap for expose event recovery.

IF NOT true THEN BEGIN
   Window, /Pixmap, /Free, XSize=xsize, YSize=ysize
   pixid = !D.Window
   Device, Copy=[0, 0, xsize, ysize, 0, 0, wid]
ENDIF ELSE pixid = -1

   ; Create info structure with program information.

info = {  image:thisImage , $             ; The scaled image data.
          wid:wid, $                      ; The window index number.
          pixid:pixid, $                  ; The pixmap window index number.
          true:true, $                    ; True-color flag.
          r:r(bottom:ncolors-1+bottom), $ ; The red colors.
          g:g(bottom:ncolors-1+bottom), $ ; The green colors
          b:b(bottom:ncolors-1+bottom), $ ; The blue colors
          xsize:xsize, $                  ; Current xsize of draw widget.
          ysize:ysize, $                  ; Current ysize of draw widget.
          ncolors:ncolors, $              ; Length of color vectors.
          bottom:bottom, $                ; Starting color index.
          drawID:drawID, $                ; Draw widget ID.
          lastID:orig}                    ; ID of last command button.

Widget_Control, tlb, Set_UValue=info, /No_Copy

   ; Call XManager to set up the Event Loop and manage the program.

XManager, 'process', tlb, Event_Handler='Process_Resize', $
      /No_Block, Group=group
END ;----------------------------------------------------------------------