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 ;----------------------------------------------------------------------