; $Id: morph.pro,v 1.10 1995/04/25 00:14:53 billo Exp $ ; ; Copyright (c) 1995, Research Systems, Inc. All rights reserved. ; Unauthorized reproduction prohibited. ;+ ; NAME: Morph ; ; PURPOSE: This example demonstrates the image processing capabilities of IDL. ; ; MAJOR TOPICS: Image Porcessing. ; ; CALLING SEQUENCE: Morph ; ; PROCEDURE: Morphing ... ; ; MAJOR FUNCTIONS and PROCEDURES: ; ; COMMON BLOCKS and STRUCTURES: ; ; MODIFICATION HISTORY: Written by: WSO, RSI, January 1995 ;- PRO ReadPeopleIndex, names, offsets, USE_CURRENT=use_current filename = "people.idx" IF (KEYWORD_SET(use_current) EQ 0) THEN $ filename = FILEPATH(filename, SUBDIRECTORY=['examples','data']) ; Openr the file with the JPEG photos and get the logical unit no. OPENR, lun, filename, /GET_LUN ; Create a variable for the total number of people to be returned in imageCount = 0L ; Read in the JPEG photos count READF, lun, imageCount ; Create a structure to read indexStruct = REPLICATE({ position: 0L, name: ""}, imageCount+2) ; Read in the index structure - position and name of each photo READF, lun, indexStruct ; Remove trailing and leading blanks from the names names = STRTRIM(indexStruct(0:imageCount-1).name, 2) ; Return offsets array with position of each image offsets = indexStruct.position ; Free the logical unit number and close the index file FREE_LUN, lun END FUNCTION ReadPeopleImage, index, lun, offsets, MAXVALUE=maxValue, WINFOTEXT=wInfoText IF KEYWORD_SET(wInfoText) THEN $ WIDGET_CONTROL, wInfoText, SET_VALUE="Reading JPEG compressed image" ; Set the file position to point to the desired image POINT_LUN, lun, offsets(index) ; Read the JPEG photo READ_JPEG, UNIT = lun, image ; If the keyword MAXVALUE is not defined - create it IF (N_ELEMENTS(maxValue) LE 0) THEN $ maxValue = 255 ; Rescale the image values between zero and maxValue to ; zero and !D.TABLE_SIZE-2 - last color used for control points image = BYTSCL(image, MIN=0, MAX=maxValue, TOP=!D.TABLE_SIZE-2) ; Erase the info if the wInfoText widget was updated above IF KEYWORD_SET(wInfoText) THEN $ WIDGET_CONTROL, wInfoText, SET_VALUE=" " RETURN, image END PRO DisplayGroupShot, imageLun, imageOffsets, WINFOTEXT=infoText COMMON morphCommon, cLeftImageWindow, cRightImageWindow, cControlPtCount, $ cNames, cGroupCount, cwStepSlider, cControlPointsX, cControlPointsY, $ cwInfoText, cImageAreaSize, cImageIconSize, cwClearButton, $ cImageLun, cImageOffsets, cGroupAreaWidth, cGroupAreaHeight, $ cCurrentImagePosition, cLeftImage, cRightImage, cwMorphButton, $ cColorTable, cwLeftImageArea, cwRightImageArea, cwGroupArea, cwInfoButton ; Read an Group image groupImage = ReadPeopleImage(cGroupCount, imageLun, imageOffsets, WINFOTEXT=infoText) ; Trim group image of 378x378 from 384x384 image groupImage = groupImage(0:377, 6:383) ; The group shot is stored as an NxN array of individual images ; Get the dimensions of the group shot dimension = CEIL(SQRT(cGroupCount)) ; Reform the image in order to pluck the individuals from the group shot ; Each individual is a 54x54pixel image within the group shot groupImage = reform(groupImage, 54, dimension, 54, dimension) ; Initialize indexes in order to spin through the NxN group shot row = dimension - 1 column = 0 ; Save the current draw window groupDrawWindow = !D.WINDOW ; Create an off-screen pixmap to blast the individual images into WINDOW, /FREE, /PIXMAP, XSIZE=cGroupAreaWidth, YSIZE=cGroupAreaHeight groupPixmap = !D.WINDOW WSET, groupPixmap ; Set the pixmap to draw group shot FOR index = 0, cGroupCount-1 DO BEGIN ; Grab each individual from the group shot individualImage = reform(groupImage(*, column, *, row), 54, 54) ; Compress the images to iconic bitmap images individualImage = REBIN(individualImage, cImageIconSize, cImageIconSize) ; Display the image in the group window TV, individualImage, index ; Increment to point to next individual in group shot column = column + 1 ; If last individual in row - goto next row IF column EQ dimension THEN BEGIN row = row - 1 column = 0 ENDIF ENDFOR ; Make the group shot display window the current graph port WSET, groupDrawWindow ; Copy pixmap of reconfigured group to the display window DEVICE, COPY=[0, 0, cGroupAreaWidth, cGroupAreaHeight, 0, 0, groupPixmap] ; Remove the pixmap - no longer needed WDELETE, groupPixmap END PRO DisplayIndividual, imageIndex COMMON morphCommon IF imageIndex LT cGroupCount THEN BEGIN ; Reset the count of control points when new image selected ClearControlPts ; Set the correct graphics window ; If the current image position is zero - IF cCurrentImagePosition EQ 0 THEN $ WSET, cLeftImageWindow $ ; left image window ELSE $ WSET, cRightImageWindow ; right image window anImage = ReadPeopleImage(imageIndex(0), $ cImageLun, cImageOffsets, WINFOTEXT=cwInfoText) ; Compress or expand the image to fit into cImageAreaSize anImage = CONGRID(anImage, cImageAreaSize, cImageAreaSize) ; Display the image at the current position - left or right TV, anImage ; If the current image position is zero - IF cCurrentImagePosition EQ 0 THEN $ cLeftImage = anImage $ ; Update left image area ELSE $ cRightImage = anImage ; Update right image area ; Set name label ; WIDGET_CONTROL, cwInfoText, SET_VALUE=cNames(imageIndex) ; Toggle to the next image position in the image window - 0 or 1 cCurrentImagePosition = (cCurrentImagePosition+1) MOD 2 ENDIF END PRO ClearControlPts COMMON morphCommon ; Reset the count of control points cControlPtCount = 0 ; If cLeftImage contains a valid image (2 dimensions)... IF (SIZE(cLeftImage))(0) EQ 2 THEN BEGIN ; Set left image window to be the current graphics window WSET, cLeftImageWindow ; Redisplay the left image without control points TV, cLeftImage ENDIF ; If cRightImage contains a valid image (2 dimensions)... IF (SIZE(cRightImage))(0) EQ 2 NE 0 THEN BEGIN ; Set right image window to be the current graphics window WSET, cRightImageWindow ; Redisplay the right image without control points TV, cRightImage ENDIF ; Disable the button until a control point has been added WIDGET_CONTROL, cwClearButton, SENSITIVE=0 END PRO CleanUpMorph, wMorphWindow COMMON morphCommon ; Reset these common block variables as scalar zeros to free memory cLeftImage = 0 cRightImage = 0 cControlPointsX = 0 cControlPointsY = 0 cNames = 0 ; Restore the previous color table. TVLCT, cColorTable ; Close the images file and free the logical unit number FREE_LUN, cImageLun END PRO MorphImages, leftImage, rightImage, x0ControlPts, y0ControlPts, $ x1ControlPts, y1ControlPts, numberOfSteps, QUINTIC = quint ; This procedure actually does the morphing..... If the control point arrays ; are undefined, a simple blending is performed. totalControlPts = N_ELEMENTS(x0ControlPts) IF totalControlPts NE N_ELEMENTS(y0ControlPts) OR $ totalControlPts NE N_ELEMENTS(x1ControlPts) OR $ totalControlPts NE N_ELEMENTS(y1ControlPts) THEN BEGIN buttonPushed = WIDGET_MESSAGE(/ERROR, "The number of control points doesn't match.") RETURN ENDIF leftImageSize = SIZE(leftImage) rightImageSize = SIZE(rightImage) ; check to see if both images are the same dimensions IF (leftImageSize(0) NE 2) OR (rightImageSize(0) NE 2) OR $ (leftImageSize(1) NE rightImageSize(1)) OR $ (leftImageSize(2) NE rightImageSize(2)) THEN BEGIN errorText = ["Image dimensions", "left:" + STRING(leftImageSize(0)) + $ "," + STRING(leftImageSize(1)), "right:" + $ STRING(rightImageSize(0)) + "," + STRING(rightImageSize(1)),$ "are inconsistent. They need to have the same dimensions."] buttonPushed = WIDGET_MESSAGE(errorText, /ERROR) RETURN ENDIF imageWidth = leftImageSize(1) ; Image size in "x" direction imageHeight = leftImageSize(2) ; Image size in "y" direction IF totalControlPts GT 0 THEN BEGIN ; Perform a Delauney triangulation on the points TRIANGULATE, x0ControlPts, y0ControlPts, triangles0 TRIANGULATE, x1ControlPts, y1ControlPts, triangles1 gridSpacing = [1, 1] gridLimits = [0, 0, imageWidth-1, imageHeight-1] ENDIF IF N_ELEMENTS(quint) EQ 0 THEN $ quint = 0 ; Setup the morphing animation XINTERANIMATE, SET=[imageWidth, imageHeight, numberOfSteps], /SHOWLOAD, $ /CYCLE, TITLE="Morphing Animation" FOR i = 0, numberOfSteps-1 DO BEGIN ;Each step percentComplete = FLOAT(i) / FLOAT(numberOfSteps-1) ;From 0.0 to 1.0 ; From 2nd to 1st IF totalControlPts GT 0 THEN BEGIN xt1 = x1ControlPts + (x0ControlPts-x1ControlPts) * percentComplete yt1 = y1ControlPts + (y0ControlPts-y1ControlPts) * percentComplete im1 = INTERPOLATE(rightImage, $ TRIGRID(x0ControlPts, y0ControlPts, xt1, triangles0, $ gridSpacing, gridLimits, QUINT=quint), $ TRIGRID(x0ControlPts, y0ControlPts, yt1, triangles0, $ gridSpacing, gridLimits, QUINT=quint)) im0 = INTERPOLATE(leftImage, $ TRIGRID(x1ControlPts, y1ControlPts, xt1, triangles1, $ gridSpacing, gridLimits, QUINT=quint), $ TRIGRID(x1ControlPts, y1ControlPts, yt1, triangles1, $ gridSpacing, gridLimits, QUINT=quint)) im = BYTE(percentComplete * im1 + (1.0-percentComplete) * im0) ENDIF ELSE $ ;no CP's, simply blend im = BYTE(percentComplete * rightImage + $ (1.0-percentComplete) * leftImage) ; Load each frame of the morphing animation XINTERANIMATE, IMAGE=im, FRAME=i ENDFOR END PRO AddControlPt, x, y, imageXSize, imageYSize, imageWindow COMMON morphCommon ; Set the current graphics port WSET, imageWindow ; Draw a symbol to represent a control point, ; where the user clicked the mouse PLOTS, x, y, /DEVICE, COLOR=!D.TABLE_SIZE-1, PSYM=6 ; Determine the x,y location for the control point controlX = x * imageXSize / cImageAreaSize controlY = y * imageYSize / cImageAreaSize IF cControlPtCount EQ 0 THEN BEGIN ; First control point? cControlPointsX = controlX cControlPointsY = controlY ENDIF ELSE BEGIN ; Concatenate onto the control point array cControlPointsX = [cControlPointsX, controlX] cControlPointsY = [cControlPointsY, controlY] ENDELSE ; Enable the clear button when a control point has been added WIDGET_CONTROL, cwClearButton, SENSITIVE=1 ; Increment the total of control points cControlPtCount = cControlPtCount + 1 END PRO DisplayMorphInfo, parent infoText = [ $ " We have included the pictures of the employees of Research Systems so "+ $ "that you can see the people who develop and support IDL. This example "+ $ "also illustrates image morphing (the gradual changing of one "+ $ "image to another), and compression using IDL.", "", $ " The pictures are stored in JPEG compressed image format, which is "+ $ "fully supported by IDL. Each 384 by 384 image, containing 144K "+ $ "bytes, is stored using JPEG in less than 9K bytes, resulting in "+ $ "approximately 16 to 1 compression.", "", $ " To experiment with image morphing, first select two people from "+ $ "the group shot by clicking on those individuals. This will add their "+ $ "photograph to one of the two image locations at the bottom of the window. ", "", $ "Next you will need "+ $ "to add control points. Control points are used to specify how the "+ $ "images are to be morphed together. They are represented by green "+ $ "squares when you click on an image. First add a control point to the "+ $ "left hand image by clicking in that image. Next add a corresponding "+ $ "control point to the right hand image. In order for these images to "+ $ "morph smoothly together pick locations that correspond. For example, "+ $ "click the left eye of the left hand image then click the left eye of "+ $ "the right hand image. The more control points you add the smoother "+ $ "the morph will be.", "", $ "When you're done adding control points, click the Morph button."] ShowInfo, TITLE="Morphing Example Information", GROUP=parent, WIDTH=80, HEIGHT=24, $ INFOTEXT=infoText END PRO MorphMouseHelp, widgetID, ENTERING=entering COMMON morphCommon ; Initialize the mouse help line to be empty helpText = "" CASE widgetID OF cwGroupArea: BEGIN IF KEYWORD_SET(entering) THEN BEGIN IF cCurrentImagePosition EQ 0 THEN $ helpText = "Click on an individual to select the left-hand image to morph" $ ELSE $ helpText = "Click on an individual to select the right-hand image to morph" ENDIF ELSE BEGIN IF cCurrentImagePosition EQ 0 THEN $ helpText = "Click on an individual to select the left-hand image to morph" $ ELSE $ helpText = "Click on an individual to select the right-hand image to morph" ENDELSE ENDCASE cwStepSlider: helpText = "Set the number of steps in the morphing process" cwClearButton: BEGIN IF KEYWORD_SET(entering) THEN BEGIN IF cControlPtCount GT 0 THEN $ helpText = "Click this button to remove all control points" $ ELSE $ helpText = "This button removes control points. Currently none are set." ENDIF ELSE $ helpText = "All control points are removed" ENDCASE cwMorphButton: BEGIN helpText = "Click this button to start the morphing process" ENDCASE cwInfoButton: helpText = 'Click this button to display "How To" information about morphing' cwLeftImageArea: BEGIN ; Update the text on an enter event. IF KEYWORD_SET(entering) THEN BEGIN ; Is the mouse in the correct image to select a control point... ; Left-hand image and an even numbered control point IF (NOT (cControlPtCount AND 1)) THEN $ helpText = "Click in this area to set a primary control point" $ ELSE $ helpText = "First click in the right-hand image to select the corresponding point" ENDIF ELSE BEGIN ; The user clicked in this widget - update help line correspondingly IF (NOT (cControlPtCount AND 1)) THEN $ helpText = "First click in the right-hand image to select the corresponding point" $ ELSE $ helpText = "Now click in the right-hand image to select corresponding point" ENDELSE ENDCASE cwRightImageArea: BEGIN ; Update the text on an enter event. IF KEYWORD_SET(entering) THEN BEGIN ; Is the mouse in the correct image to select a control point... ; Right-hand image and an odd numbered control point IF (cControlPtCount AND 1) THEN $ helpText = "Click in this area to set a corresponding control point" $ ELSE $ helpText = "First click in the left-hand image to select a primary control point" ENDIF ELSE BEGIN ; The user clicked in this widget - update help line correspondingly IF (cControlPtCount AND 1) THEN $ helpText = "First click in the left-hand image to select a primary control point" $ ELSE $ helpText = 'Now click in the left-hand image to select another point or click "Morph"' ENDELSE ENDCASE cwInfoText: helpText = "Move the mouse over different widgets to get information" ELSE: helpText = "" ENDCASE ; Set the text in the help line if the event corresponds to ; a help line event id. WIDGET_CONTROL, cwInfoText, SET_VALUE=helpText END PRO MorphEvents, event COMMON morphCommon eventType = TAG_NAMES(event, /STRUCTURE_NAME) IF eventType EQ "WIDGET_TRACKING" THEN BEGIN IF event.enter EQ 1 THEN $ MorphMouseHelp, event.id, /ENTERING $ ELSE $ WIDGET_CONTROL, cwInfoText, SET_VALUE="" RETURN ENDIF WIDGET_CONTROL, event.id, GET_UVALUE=theControl ;Get the event's value CASE theControl OF "GROUP_AREA": $ ; If mouse pressed... IF (event.press NE 0) THEN BEGIN ; Calculate the which person in the group was clicked on imageIndex = (event.x / cImageIconSize) + $ (cGroupAreaWidth/cImageIconSize) * $ ((cGroupAreaHeight-event.y) / cImageIconSize) ; If the user clicked on a face... DisplayIndividual, imageIndex MorphMouseHelp, event.id ENDIF "LEFT_IMAGE": BEGIN ; Marking control points on the images IF event.press NE 0 THEN $ RETURN ;Only presses ; Was the mouse press in the correct image... ; lefthand image and an even numbered control point IF (NOT (cControlPtCount AND 1)) THEN BEGIN ; Add a control point AddControlPt, event.x, event.y, (SIZE(cLeftImage))(1), $ (SIZE(cLeftImage))(2), cLeftImageWindow MorphMouseHelp, event.id ENDIF ENDCASE "RIGHT_IMAGE": BEGIN ; Marking control points on the images IF event.press NE 0 THEN $ RETURN ;Only presses ; Was the mouse press in the correct image... ; the righthand image and an odd numbered control point IF (cControlPtCount AND 1) THEN BEGIN ; Add a control point AddControlPt, event.x, event.y, (SIZE(cRightImage))(1), $ (SIZE(cRightImage))(2), cRightImageWindow MorphMouseHelp, event.id ENDIF ENDCASE "CLEAR": BEGIN ; Reset the count of control points when new image selected ClearControlPts MorphMouseHelp, event.id ENDCASE "COLORS": xloadct, GROUP = event.top ; Display color table selector "STEP_SLIDER": ; ignore changes - value will be retreived during "MORPH" "QUIT": BEGIN ;Done? ; Close the window WIDGET_CONTROL, event.top, /DESTROY ENDCASE "INFO" : BEGIN DisplayMorphInfo, event.top ENDCASE "MORPH": BEGIN IF (cControlPtCount AND 1) THEN $ buttonPushed = WIDGET_MESSAGE(/ERROR, $ "Both images must have the same number of control points. " + $ "Please add another control point to the right hand image.") $ ELSE BEGIN IF XREGISTERED("XInterAnimate") THEN $ RETURN WIDGET_CONTROL, cwStepSlider, GET_VALUE=numberOfSteps IF cControlPtCount ge 2 THEN BEGIN i2 = INDGEN(cControlPtCount/2) * 2 ; Alternate CPs i1 = (SIZE(cLeftImage))(1) -1 ; Add corners to CPs x0ControlPoints = [cControlPointsX(i2), 0, i1, i1, 0] x1ControlPoints = [cControlPointsX(i2+1), 0, i1, i1, 0] y0ControlPoints = [cControlPointsY(i2), 0, 0, i1, i1] y1ControlPoints = [cControlPointsY(i2+1), 0, 0, i1, i1] ENDIF WIDGET_CONTROL, event.top, /HOURGLASS ;Set busy cursor MorphImages, cLeftImage, cRightImage, $ x0ControlPoints, y0ControlPoints, $ x1ControlPoints, y1ControlPoints, numberOfSteps XINTERANIMATE, 40, GROUP = event.top ENDELSE ENDCASE ELSE: HELP, /structure, event ;Dunno... ENDCASE ;String value RETURN END PRO Morph, GROUP = group, USE_CURRENT=use_current ;Main people procedure ;+ ; USE_CURRENT = use files in current dir. ;- COMMON morphCommon ; If this example is already running - return. Only one copy of this ; example can run at any one time IF XREGISTERED("MorphDemo") THEN $ RETURN ; Get the current color vectors to restore when this application is exited. TVLCT, savedR, savedG, savedB, /GET ; Build color table from color vectors cColorTable = [[savedR],[savedG],[savedB]] ; Determine hardware display size. DEVICE, GET_SCREEN_SIZE = screenSize ; Determine the maximum group shot size to be 80% of the horizontal ; screen size maxGroupSize = FLOOR(screenSize(0) * .8) ; On larger monitors use a larger group shot IF maxGroupSize GE 640 THEN BEGIN cImageIconSize = 54 ; iconic images of individuals 54x54 pixels groupRows = 4 ; Number of rows in the group shot groupColumns = 12 ; Number of columns in the group shot cImageAreaSize = 256 ENDIF ELSE BEGIN cImageIconSize = 27 ; Iconic images of individuals 27x27 pixels groupRows = 3 ; Number of rows in the group shot groupColumns = 16 ; Number of columns in the group shot cImageAreaSize = 192 ENDELSE cControlPtCount = 0 ; Initialize count of morphing control points ; Drawable area width (number columns times image size) for group shot cGroupAreaWidth = groupColumns * cImageIconSize ; Drawable area height (number rows times image size) for group shot cGroupAreaHeight = groupRows * cImageIconSize ; Read the index file "people.idx" to get the structure ; for the image file "people.jpg" ReadPeopleIndex, cNames, cImageOffsets, USE_CURRENT=use_current ; This is the file where all the jpeg'ged photos reside filename = "people.jpg" ; If not using the current directory for the image file ; - use the "examples/data" directory IF KEYWORD_SET(use_current) EQ 0 THEN $ filename = FILEPATH(filename, SUBDIRECTORY=['examples','data']) ; Open the images file for reading and get its logical unit number OPENR, cImageLun, filename, /STREAM, /GET ;For VMS... ; Count how many individuals are in the images file cGroupCount = N_ELEMENTS(cNames) cLeftImage = 0 cRightImage = 0 ; Create the main window wMorphWindow = WIDGET_BASE(title="Morphing", /COLUMN) wGroupBase = WIDGET_BASE(wMorphWindow, /COLUMN, /ALIGN_CENTER) wGroupLabel = WIDGET_LABEL(wGroupBase, /ALIGN_LEFT, $ VALUE="Select individuals to morph by clicking on them.") ; Create the draw widget to display the group shot cwGroupArea = WIDGET_DRAW(wGroupBase, XSIZE=cGroupAreaWidth, $ YSIZE=cGroupAreaHeight, RETAIN=2, /BUTTON_EVENTS, $ UVALUE = "GROUP_AREA", /TRACKING_EVENTS) ; Create the base to display the two images to morph wMorphArea = WIDGET_BASE(wMorphWindow, /ROW, /ALIGN_CENTER) ; Create the base to hold the controls for the window wControlPanel = WIDGET_BASE(wMorphArea, /COLUMN) ; Create the label for the number of steps slider wStepLabel = WIDGET_LABEL(wControlPanel, VALUE="Number Of Steps:") ; Create slider to allow the user to select the number of steps in morph cwStepSlider = WIDGET_SLIDER(wControlPanel, MINIMUM=3, MAXIMUM=32, $ VALUE=16, UVALUE = "STEP_SLIDER", /TRACKING_EVENTS) ; Create button to clear control points cwClearButton = WIDGET_BUTTON(wControlPanel, VALUE="Clear Points", $ UVALUE="CLEAR", /TRACKING_EVENTS) ; disable the button until control points have been added WIDGET_CONTROL, cwClearButton, SENSITIVE=0 ; Create button to start morph cwMorphButton = WIDGET_BUTTON(wControlPanel, VALUE="Morph", $ UVALUE="MORPH", /TRACKING_EVENTS) ; Create button to display information about morphing cwInfoButton = WIDGET_BUTTON(wControlPanel, VALUE="Info...", $ UVALUE="INFO", /TRACKING_EVENTS) ; Create the left image area cwLeftImageArea = WIDGET_DRAW(wMorphArea, XSIZE=cImageAreaSize, $ YSIZE=cImageAreaSize, RETAIN=2, /BUTTON_EVENTS, $ UVALUE = "LEFT_IMAGE", /TRACKING_EVENTS) ; Create the right image area cwRightImageArea = WIDGET_DRAW(wMorphArea, XSIZE=cImageAreaSize, $ YSIZE=cImageAreaSize, RETAIN=2, /BUTTON_EVENTS, $ UVALUE = "RIGHT_IMAGE", /TRACKING_EVENTS) ; Create a text widget to display this example's information cwInfoText = WIDGET_TEXT(wMorphWindow, XSIZE=24, YSIZE = 1, $ VALUE=STRING(REPLICATE(32B,24)), /TRACKING_EVENTS) ; Make the window visible WIDGET_CONTROL, /REALIZE, wMorphWindow, /HOURGLASS ; Default the draw widget cursors to an arrow DEVICE, /CURSOR_ORIGINAL, DECOMPOSED=0 ; Get the graphics window numbers for use with WSET WIDGET_CONTROL, cwLeftImageArea, GET_VALUE = cLeftImageWindow WIDGET_CONTROL, cwRightImageArea, GET_VALUE = cRightImageWindow ; The first image will be displayed in position zero - left side ; right side = 1 cCurrentImagePosition = 0 WIDGET_CONTROL, cwGroupArea, GET_VALUE = cGroupWindow ; Display photos in shades of gray (load grayscale color table) LOADCT, 0, /SILENT ; Set the last color index to green to make the control points stand out TVLCT, 0, 255, 0, !D.TABLE_SIZE-1 ; Set which draw widget to display into WSET, cGroupWindow ; Display group shot of everyone in the group shot draw widget DisplayGroupShot, cImageLun, cImageOffsets, WINFOTEXT=cwInfoText ; Generate initial seed - try to use the lesser significant digits for ; the actual seeds index1 = RANDOMU(seed) * 10000 index1 = index1 - FLOOR(index1) index2 = RANDOMU(seed) * 10000 index2 = index2 - FLOOR(index2) ; Display initial two random individuals for morphing DisplayIndividual, FLOOR(index1 * cGroupCount) DisplayIndividual, FLOOR(index2 * cGroupCount) ; Register this widget application with XManager and tell it to pass ; any events to the "MorphEvents" eventhandler XManager, "MorphDemo", wMorphWindow, EVENT_HANDLER = "MorphEvents", $ GROUP = group, CLEANUP="CleanUpMorph" END