<> <> <> <<>> DIRECTORY ImagerBackdoor, ImagerFont, ImagerPath, Hist, Imager, ImagerTransformation, IO, Process, Real, RealFns, Rope, TIPUser, TRCViewer, ViewerClasses, ViewerOps; TRCViewerImpl: CEDAR MONITOR <<-- This module presents user interface for creating and editing tone reproduction curves (TRC). >> <<-- It allows the user to input and move points on the curve which it will connect with straight>> <<-- lines. >> <<-- The module also presents a client interface by means of which the vertex data may >> <<-- be accessed and modified >> <<>> <<>> IMPORTS ImagerBackdoor, ImagerFont, ImagerPath, Imager, ImagerTransformation, IO, Process, Real, RealFns, TIPUser, ViewerOps EXPORTS TRCViewer = BEGIN OPEN TRCViewer; --TYPES-- EditMode: TYPE = {sketch, pin}; State: TYPE = REF StateRec; StateRec: TYPE = RECORD [ viewer: ViewerClasses.Viewer, scale: REAL _ 1.0, title: Rope.ROPE _ NIL, -- filename or whatever userXLabel, userYLabel: Rope.ROPE _ NIL, hasTRC, hasHisto, newData, finished: BOOLEAN _ FALSE, trc: Hist.TRCVec _ NIL, histo: Hist.Histogram _ NIL, histoPath: ImagerPath.Trajectory, haveSelection: BOOLEAN _ FALSE, latestX, latestY: NAT _ 0, selected, selectedX, selectedY: NAT _ 0, lb, ub: INTEGER _ 0, -- index of the left and right neigbours of selected feedbackModeIn, feedbackModeOut: FeedBackMode _ reflectance, connectMode: ConnectMode _ linear, maxDIn, maxDOut: REAL _ 2.5, -- maximum I & O densities used to calc feedback data minRIn, minROut: REAL _ 0.0, -- minimum input/output refelectances changeProc: TRCViewer.ChangeProc _ NIL, cPClientData: REF ANY _ NIL, mode: EditMode _ pin ]; CallBack: TYPE = REF CallBackRec; CallBackRec: TYPE = RECORD [proc: Painter] ; Painter: TYPE = PROC [context: Imager.Context, state: State]; -- CLASS GLOBALS -- newDataAvailable: CONDITION; graphOriginX: NAT _ 100; graphOriginY: NAT _ 100; graphSize: NAT _ 256; graphScale: REAL _ 1; axisWidth: NAT _ 2; titleImagerFont: ImagerFont.Font _ ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/TimesRoman-MRR"], 14]; labelImagerFont: ImagerFont.Font _ ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/TimesRoman-MRR"], 12]; dataImagerFont: ImagerFont.Font _ ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/TimesRoman-MRR"], 10]; disclaimerImagerFont: ImagerFont.Font _ ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/TimesRoman-MRR"], 8]; defaultMaxDensity: REAL = 2.0; -- pretty arbitrary <<-- PUBLIC PROCEDURES>> Create: PUBLIC PROC [] RETURNS [ViewerClasses.Viewer] ~ { <<-- Creates a new TRC edit viewer and initial trc. >> RETURN[ViewerOps.CreateViewer[flavor: $TRCViewer, info: [name:"TRC Viewer"]]]; }; Get: PUBLIC PROC [v: ViewerClasses.Viewer] RETURNS [trc: Hist.TRCVec] ~ { -- Get the trc structure trc _ NEW[Hist.TRCVecRec]; trc^ _ NARROW[v.data, State].trc^; }; Set: PUBLIC PROC [v: ViewerClasses.Viewer, trc: Hist.TRCVec, paint:BOOLEAN _ TRUE] ~ { -- Set the trc structure state: State _ NARROW[v.data]; state.trc _ trc; state.ub _ 256; state.lb _ -1; IF paint THEN TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: WriteGraph]] ]; }; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; <<>> <> <<-- Insert a pin at (x:in, y:out) - can also be used to move existing pin>> <<-- at (x:in) to new position (y:out)>> <> <> <<[state.lb, state.ub] _ SetBounds[state.trc, state.selectedX];>> <> <<};>> <<>> <> <> <<[state.lb, state.ub] _ SetBounds[state.trc, in];>> <> <> <> <> <> <> <> <> <<];>> <<};>> <<};>> <<>> RegisterChangeProc: PUBLIC PROC [v: ViewerClasses.Viewer, changeProc: ChangeProc, clientData: REF ANY _ NIL] ~ { state: State _ NARROW[v.data]; state.changeProc _ changeProc; state.cPClientData _ clientData; }; SetTitle: PUBLIC PROC [v: ViewerClasses.Viewer, rope: Rope.ROPE] ~ { <<-- set the title displayed below the axes>> state: State _ NARROW[v.data]; state.title _ rope; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client] }; }; SetMinReflectanceIn: PUBLIC PROC [v: ViewerClasses.Viewer, minR:REAL] ~ { <<-- Set the minimum input reflectance to be display during feedback>> state: State _ NARROW[v.data]; state.minRIn _ minR; state.maxDIn _ RefToDens[minR]; }; SetMinReflectanceOut: PUBLIC PROC [v: ViewerClasses.Viewer, minR:REAL] ~ { <<-- Set the minimum output reflectance to be display during feedback>> state: State _ NARROW[v.data]; state.minROut _ minR; state.maxDOut _ RefToDens[minR]; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; SetMaxDensityIn: PUBLIC PROC [v: ViewerClasses.Viewer, maxD:REAL] ~ { <<-- Set the maximum input density to be display during feedback>> state: State _ NARROW[v.data]; state.maxDIn _ maxD; state.minRIn _ DensToRef[maxD]; }; SetMaxDensityOut: PUBLIC PROC [v: ViewerClasses.Viewer, maxD:REAL] ~ { <<-- Set the maximum output density to be display during feedback>> state: State _ NARROW[v.data]; state.maxDOut _ maxD; state.minROut _ DensToRef[maxD]; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; SetFeedBackModes: PUBLIC PROC [v: ViewerClasses.Viewer, in,out: FeedBackMode] ~ { <<-- set the feedback mode on the IN axis>> state: State _ NARROW[v.data]; state.feedbackModeIn _ in; state.feedbackModeOut _ out; }; DensToRef: PUBLIC PROC [density:REAL] RETURNS [reflectance:REAL] = { reflectance _ RealFns.Power[10, -MIN[density, 10]]; }; RefToDens: PUBLIC PROC [reflectance:REAL] RETURNS [density:REAL] = { density _ -RealFns.Log[10, reflectance]; }; <<>> SetConnectMode: PUBLIC PROC [v: ViewerClasses.Viewer, mode: ConnectMode] ~ { <<-- set the interpolation mode>> SetOutput: EnumerateProc ~ { xIndex: NAT _ Real.RoundC[x]; IF ~state.trc[xIndex].pinned THEN state.trc[xIndex].out _ Real.RoundC[y]; }; state: State _ NARROW[v.data]; state.connectMode _ mode; state.lb _ -1; state.ub _ 256; Enumerate[state.viewer, SetOutput, 0, 255]; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: WriteGraph]] ]; }; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; <<>> SetHistogram: PUBLIC PROC [v: ViewerClasses.Viewer, histo: Hist.Histogram _ NIL] ~ { <<-- Set the histogram. If histo=NIL then nothing gets shown at all>> ToggleHisto: Painter ~ { TransformToGraphSpace[context, state]; PaintGraphData[context, state, -1, 256]; }; state: State _ NARROW[v.data]; IF histo#NIL THEN { traj: ImagerPath.Trajectory; max: REAL _ 0; FOR i: NAT IN [0..255] DO max _ MAX[max, histo[i]]; ENDLOOP; traj _ ImagerPath.MoveTo[[0, 0]]; FOR i: NAT IN [0..255] DO traj _ traj.LineTo[[i, 255*histo[i]/max]].LineTo[[i+1, 255*histo[i]/max]]; ENDLOOP; traj _ traj.LineTo[[256, 0]].LineTo[[0, 0]]; state.histoPath _ traj; state.histo _ histo; }; state.hasHisto _ (histo#NIL); TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: ToggleHisto]] ]; }; }; <<>> <<>> Enumerate: PUBLIC PROC [v: ViewerClasses.Viewer, proc: EnumerateProc, first,last:NAT] ~ { <<-- Calls the client enumerate proc for each input value in the range [first..last] supplying the>> <<-- current corresponding output value and pin flag.>> Line: PROC [x0,y0,x1,y1:INTEGER, doFirst:BOOLEAN_TRUE] ~ { <<-- ouput all x-points on the line between pin 0 and pin 1>> <<-- pin 0 and pin 1 should probably be pinned for this to work>> <<-- doFirst=FALSE is used to stop duplicating the endpoints in a contiguous sequence of lines>> xRange: REAL _ x1-x0; yRange: REAL _ y1-y0; IF doFirst THEN proc[x0, y0, vertices[x0].pinned]; FOR i: NAT IN (x0..x1) DO proc[i, Real.RoundC[y0+yRange*(i-x0)/xRange], vertices[i].pinned]; ENDLOOP; IF x0#x1 THEN proc[x1, y1, vertices[x1].pinned]; }; <<>> state: State _ NARROW[v.data]; vertices: Hist.TRCVec _ state.trc; IF NOT state.hasTRC THEN RETURN; SELECT state.connectMode FROM linear => { pinA: NAT _ first; proc[pinA,vertices[pinA].out, vertices[pinA].pinned]; FOR i: NAT IN (first..last] DO -- now linear interp to next pin IF vertices[i].pinned OR i=last THEN { Line[pinA, vertices[pinA].out, i, vertices[i].out, FALSE]; pinA _ i; }; ENDLOOP; }; log => { pinA: NAT _ first; proc[pinA, vertices[pinA].out, vertices[pinA].pinned]; FOR i: NAT IN (first..last] DO IF vertices[i].pinned OR i=last THEN { pinB: NAT _ i; intervalV: NAT _ pinB-pinA; startD: REAL _ Density[vertices[pinA].out, state.maxDOut]; endD: REAL _ Density[vertices[i].out, state.maxDOut]; <> <> intervalD: REAL _ startD - endD; startR: REAL _ DensToRef[startD]; intervalR: REAL _ DensToRef[endD] - startR; IF intervalD#0 THEN FOR j: NAT IN [1..intervalV] DO density: REAL _ RefToDens[startR+(intervalR*j)/intervalV]; position: REAL _ vertices[pinA].out + ((startD - density) / intervalD) * (vertices[i].out - vertices[pinA].out); proc[pinA+j, position, vertices[pinA+j].pinned]; ENDLOOP ELSE Line[pinA, vertices[pinA].out, pinB, vertices[pinB].out, FALSE]; pinA _ pinB; }; ENDLOOP; }; ENDCASE => ERROR; }; <<>> <<-- VIEWER PROCEDURES>> Init: ViewerClasses.InitProc ~ { <<-- initialise the viewer>> SetOutput: EnumerateProc ~ { state.trc[Real.RoundC[x]].out _ Real.RoundC[y]; }; state: State _ NEW[StateRec _ [ maxDIn: defaultMaxDensity, maxDOut: defaultMaxDensity, minRIn: DensToRef[defaultMaxDensity], minROut: DensToRef[defaultMaxDensity], trc: NEW[Hist.TRCVecRec], hasTRC: TRUE, viewer: self, title: "Tone reproduction curve", userXLabel: "In", userYLabel: "Out" ]]; state.trc[0].out _ 0; state.trc[0].pinned _ TRUE; state.trc[255].out _ 255; state.trc[255].pinned _ TRUE; self.data _ state; Enumerate[self, SetOutput, 0, 255]; }; Notify: ViewerClasses.NotifyProc ~ { <<-- viewer notifier>> <<>> FindUserXY: Painter ~ { <<-- Transform mouse coordinates into graph space>> clientPair: ImagerTransformation.VEC; Clip: PROC [r:REAL] RETURNS [n:NAT] ~ INLINE { -- truncate real to [0..255] RETURN [Real.RoundC[MIN[MAX[r, 0], 255]]] }; TransformToGraphSpace[context, state]; clientPair _ ImagerBackdoor.ClientFromView[ context: context, p: [x:xy.mouseX, y:xy.mouseY] ]; x _ Clip[clientPair.x]; y _ Clip[clientPair.y]; }; xy:TIPUser.TIPScreenCoords _ NARROW[input.first]; state: State _ NARROW[self.data]; x,y:NAT; -- converted mouse coordinates TRUSTED { -- convert mouse coords to graph coords ViewerOps.PaintViewer[ viewer: self, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: FindUserXY]] ]}; FOR input _ input.rest, input.rest WHILE input#NIL DO -- process action tokens SELECT input.first FROM $Feedback => ShowFeedback[state, x, y]; $InitPosition => InitPosition[state, x, y]; $QueuePosition => QueuePosition[state, x, y]; $AddSelected => LocalPinVertex[state]; $FindNeighboringPins => FindNeighboringPins[state]; $ClearPinSelection => ClearPinSelection[state]; $SelectNearest => SelectNearestPin[state]; $ShiftSelected => MovePin[state]; $SelectVertexX => SelectVertexX[state]; $DeleteSelected => DeleteSelectedPin[state]; $Refresh => Refresh[state]; $StartFeedBackProcess => StartFeedBack[state]; $StopFeedBack => state.finished _ TRUE; $SetSketchMode => { state.mode _ sketch; state.lb _ state.ub _ state.selectedX }; $SetVertexMode => state.mode _ pin; ENDCASE => ERROR; ENDLOOP; }; ShowFeedback: PROC [state:State, x,y:NAT] ~ { Feedback : Painter ~ { <<-- generate position feedback to screen>> context.SetColor[Imager.white]; context.MaskRectangle[[70, 60, 120, 35]]; context.SetColor[Imager.black]; context.SetXY[[70, 80]]; context.SetFont[dataImagerFont]; IF state.feedbackModeIn=density THEN context.ShowRope[ IO.PutFR[ "%3g (%4.2fD)", IO.card[x], IO.real[Density[x, state.maxDIn]]]] ELSE context.ShowRope[ IO.PutFR[ "%3g (%4.2fR)", IO.card[x], IO.real[Reflectance[x, state.minRIn]]]]; context.SetXY[[70, 65]]; IF state.feedbackModeOut=density THEN context.ShowRope[ IO.PutFR[ "%3g (%4.2fD)", IO.card[y], IO.real[Density[y, state.maxDOut]]]] ELSE context.ShowRope[ IO.PutFR[ "%3g (%4.2fR)", IO.card[y], IO.real[Reflectance[y, state.minROut]]]]; }; TRUSTED { -- convert mouse coords to graph coords ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: Feedback]] ]}; }; Paint: ENTRY ViewerClasses.PaintProc ~ { <<-- Regular viewers paintproc>> <<-- Default action is to paint everything>> <<-- Otherwise call clients paint routine pointed at by whatChanged>> CallAndRestore: PROC ~ { <<-- calling client procs this way makes sure the context gets reset>> box: Imager.Rectangle _ ImagerBackdoor.GetBounds[context]; sx: REAL _ (box.w) / 400; sy: REAL _ (box.h) / 400; state.scale _ MIN[sx,sy]; <<>> IF whatChanged=NIL THEN { context.ScaleT[state.scale]; DrawAll[context, state]; } ELSE { callBack: CallBack _ NARROW[whatChanged, CallBack]; context.ScaleT[state.scale]; callBack.proc[context, state]; } }; state: State _ NARROW[self.data]; context.DoSaveAll[CallAndRestore]; }; <<>> <<-- PAINTERS>> <<>> WriteGraph: Painter ~ { -- writes the section of the graph that lies between [state.lb..state.ub] TransformToGraphSpace[context, state]; PaintGraphData[context, state, state.lb, state.ub]; }; <<>> XORGraph: Painter ~ { <<-- XOR the section of the graph that lies between [state.lb..state.ub]>> TransformToGraphSpace[context, state]; PaintGraphData[context, state, state.lb, state.ub, TRUE]; }; <<>> XORPin: Painter ~ { <<-- XOR the pin state.selectedX>> TransformToGraphSpace[context, state]; IF state.trc[state.selected].pinned THEN PaintBlobAt[context, state.selected, state.trc[state.selected].out, TRUE]; }; <<>> <<>> XORVertex: Painter ~ { <<-- XOR the pin state.selectedX>> TransformToGraphSpace[context, state]; PaintDotAt[context, state.selectedX, state.trc[state.selectedX].out, TRUE]; }; <<>> <<>> DrawAll: Painter ~ { <> PaintAxesAndLabels[context, state]; TransformToGraphSpace[context, state]; PaintGraphData[context, state, 0, 255]; }; <<>> <<-- DISPLAY PROCEDURES USED BY PAINTERS>> PaintAxesAndLabels: PROC [context: Imager.Context, state:State] ~ { -- draw the graph's axes lines: Imager.Trajectory _ ImagerPath.MoveTo[[ graphOriginX-axisWidth/2-1, graphOriginY+graphSize ]]; lines _ lines.LineTo[[ graphOriginX-axisWidth/2-1, graphOriginY-axisWidth/2-1 ]]; lines _ lines.LineTo[[ graphOriginX+graphSize, graphOriginY-axisWidth/2-1 ]]; context.SetColor[Imager.black]; context.SetStrokeWidth[axisWidth]; context.MaskStrokeTrajectory[lines]; context.SetXY[[graphOriginX, 20]]; IF state.title#NIL THEN { context.SetFont[titleImagerFont]; context.ShowRope[state.title]; }; <> <> <<"(The management appologises for the font quality)",>> <> context.SetXY[[graphOriginX+graphSize, 80]]; IF state.userXLabel#NIL THEN { context.SetFont[labelImagerFont]; context.ShowRope[state.userXLabel]; }; context.SetXY[[80, graphOriginY+graphSize+10]]; IF state.userYLabel#NIL THEN { context.SetFont[labelImagerFont]; context.ShowRope[state.userYLabel]; }; context.SetFont[dataImagerFont]; context.SetXY[[40, 80]]; context.ShowRope["In: "]; context.SetXY[[40, 65]]; context.ShowRope["Out: "]; }; <<>> TransformToGraphSpace: PROC [context: Imager.Context, state:State] ~ { -- transform context to area of graph context.TranslateT[[graphOriginX, graphOriginY]]; context.ScaleT[graphScale]; context.ClipRectangle[[0, 0, graphSize, graphSize]]; }; <<>> PaintGraphData: PROC [context: Imager.Context, state:State, min,max:INTEGER, xor:BOOLEAN _ FALSE] ~ { -- draw the vertices and/or histogram first: INTEGER _ MAX[min, 0]; last: INTEGER _ MIN[max, 255]; IF NOT xor THEN { context.SetColor[Imager.white]; context.MaskRectangle[[first, 0, last+1-first, graphSize]]; IF state.hasHisto THEN { context.SetColor[Imager.MakeGray[0.5]]; context.MaskFillTrajectory[state.histoPath]; }; PaintVertices[context, state, MAX[first-1, 0], MIN[last+1,255], xor]; }; PaintPlot[context, state, MAX[first-1, 0], MIN[last+1,255], xor]; }; <<>> PaintVertices: PROC [context: Imager.Context, state:State, first,last:INTEGER, xor:BOOLEAN _ FALSE] ~ { <<-- draws vertices in a graphSize square grid origin at 0,0>> vertices: Hist.TRCVec _ state.trc; IF NOT state.hasTRC THEN RETURN; FOR i: NAT IN [first..last] DO IF vertices[i].pinned THEN PaintBlobAt[context, i, vertices[i].out, xor]; ENDLOOP; }; <<>> PaintBlobAt: PROC [context: Imager.Context, x,y:NAT, xor:BOOLEAN_FALSE] ~ { <<-- draws single blob in a graphSize square grid origin at 0,0>> PaintBlob: PROC ~ { context.TranslateT[[x,y]]; IF xor THEN context.SetColor[ImagerBackdoor.invert] ELSE context.SetColor[Imager.black]; context.MaskRectangle[[-2, -2, 4, 4]]; }; context.DoSaveAll[PaintBlob]; }; <<>> PaintDotAt: PROC [context: Imager.Context, x,y:NAT, xor:BOOLEAN_FALSE] ~ { <<-- draws single blob in a graphSize square grid origin at 0,0>> PaintDot: PROC ~ { context.TranslateT[[x,y]]; IF xor THEN context.SetColor[ImagerBackdoor.invert] ELSE context.SetColor[Imager.black]; context.MaskRectangle[[0, 0, 1, 1]]; }; context.DoSaveAll[PaintDot]; }; <<>> PaintPlot: PROC [context: Imager.Context, state:State, first,last:INTEGER, xor:BOOLEAN _ FALSE] ~ { <<-- joins vertices in a graphSize square grid origin at 0,0>> graph: Imager.Trajectory; IF NOT state.hasTRC THEN RETURN; graph _ ImagerPath.MoveTo[[first, state.trc[first].out]]; FOR i: NAT IN (first..last] DO graph _ graph.LineTo[[i, state.trc[i].out]]; ENDLOOP; IF xor THEN context.SetColor[ImagerBackdoor.invert] ELSE context.SetColor[Imager.black]; context.SetStrokeWidth[0]; context.MaskStrokeTrajectory[graph]; }; <<>> <<>> <<-- NOTIFY SERVICES>> InitPosition: PROC [state:State, x,y:NAT] ~ { <<-- place coordinate on the input queue for feedback and initialize selected for first shot>> QueuePosition[state, x, y]; state.selectedX _ x; state.selectedY _ y; }; QueuePosition: ENTRY PROC [state:State, x,y:NAT] ~ { <<-- save the latest mouse position (in graph coords) and wake up any waiters>> state.latestX _ x; state.latestY _ y; state.newData _ TRUE; BROADCAST newDataAvailable }; <<>> GetLatestRecordedMousePos: ENTRY PROC [state:State] ~ { <<-- get the latest recorded mouse position>> WHILE NOT state.newData DO WAIT newDataAvailable ENDLOOP; state.selectedX _ state.latestX; state.selectedY _ state.latestY; state.newData _ FALSE; }; <<>> LocalPinVertex: PROC [state: State] ~ { <<-- Local version of PinVertex user XOR graphics to change shape of line. This is intended >> <<-- for interactive use. >> <<-- Move a pin to the position specified by the GetLatestRecordedMousePos procedure >> <<-- position to move to is in state.latestX state.lastestY - obtained under monitor lock!>> IF ~state.hasTRC THEN state.trc _ NEW[Hist.TRCVecRec]; GetLatestRecordedMousePos[state]; <> <> <> <> <> <> <<];>> <<};>> state.trc[state.selected].out _ state.selectedY; state.trc[state.selected].pinned _ TRUE; state.hasTRC _ TRUE; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: XORPin]] ]; }; }; <<>> FindNeighboringPins: PROC [state:State] ~ { <<-- find the indices of the left and right neigbours of selected pin>> <<-- N.B. returns values in range [-1..256]>> [state.lb, state.ub] _ SetBounds[state.trc, state.selected] }; SetBounds: PROC [trc: Hist.TRCVec, target:NAT] RETURNS[lb: INT _ -1, ub:INT _ 256] ~ { <<-- finds the indices of the pins to the left and right of the target pin>> nextLower: INTEGER _ MAX[target-1, -1]; nextHigher: INTEGER _ MIN[target+1, 256]; IF trc#NIL THEN { WHILE nextLower>=0 DO IF trc[nextLower].pinned THEN EXIT; nextLower _ nextLower - 1; ENDLOOP; WHILE nextHigher<=255 DO IF trc[nextHigher].pinned THEN EXIT; nextHigher _ nextHigher + 1; ENDLOOP; RETURN [nextLower, nextHigher] }; }; ClearPinSelection: PROC [state:State] ~ { <<-- invalidate the current selection>> state.haveSelection _ FALSE }; <<>> SelectNearestPin: PROC [state:State] ~ { <<-- find the index of the pin closest to the selected position>> nearest: NAT; distance:REAL; x:REAL _ state.selectedX; y:REAL _ state.selectedY; foundOne: BOOLEAN _ FALSE; IF state.hasTRC THEN FOR i: NAT IN [0..255] DO IF state.trc[i].pinned THEN { thisDist: REAL _ (x-i)*(x-i) + (y-state.trc[i].out)*(y-state.trc[i].out); IF NOT foundOne OR thisDist> MovePin: PROC [state:State] ~ { <<-- Move a pin to the position specified by the GetLatestRecordedMousePos procedure >> <<-- pin to be moved is is state.selected>> <<-- position to move to is in state.latestX state.lastestY - obtained under monitor lock!>> <<-- bounds of move are in state.lb state.ub>> IF state.haveSelection THEN { GetLatestRecordedMousePos[state]; -- copy latest into selected TRUSTED { -- XOR out the appropriate pin ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: XORPin]] ]; }; state.trc[state.selected].pinned _ FALSE; state.selected _ MIN[MAX[state.selectedX, state.lb+1], state.ub-1]; state.trc[state.selected].pinned _ TRUE; state.trc[state.selected].out _ state.selectedY; TRUSTED { -- redisplay the appropriate pin ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: XORPin]] ]; }; ShowFeedback[state, state.selectedX, state.selectedY]; }; }; <<>> MoveVertex: PROC [state:State] ~ { <<-- Move a pin to the position specified by the GetLatestRecordedMousePos procedure >> <<-- pin to be moved is is state.selected>> <<-- position to move to is in state.latestX state.lastestY - obtained under monitor lock!>> <<-- bounds of move are in state.lb state.ub>> GetLatestRecordedMousePos[state]; -- copy latest into selected TRUSTED { -- XOR out the appropriate pin ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: XORVertex]] ]; }; state.trc[state.selectedX].out _ state.selectedY; state.lb _ MIN[state.lb, state.selectedX]; state.ub _ MAX[state.ub, state.selectedX]; TRUSTED { -- redisplay the appropriate pin ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: XORVertex]] ]; }; ShowFeedback[state, state.selectedX, state.selectedY]; }; <<>> SelectVertexX: PROC [state:State] ~ { <<-- Select vertex whose index is in state.selectedX - Vertex may not exist yet!>> state.selected _ state.selectedX; state.haveSelection _ TRUE }; <<>> DeleteSelectedPin: PROC [state:State] ~ { <<-- pin to be deleted is is state.selected>> <<-- neighbors of selected are in state.lb state.ub>> IF state.haveSelection THEN { SetOutput: EnumerateProc ~ { xIndex: NAT _ Real.RoundC[x]; IF ~state.trc[xIndex].pinned THEN state.trc[xIndex].out _ Real.RoundC[y]; }; state.trc[state.selected].pinned _ FALSE; state.haveSelection _ FALSE; Enumerate[state.viewer, SetOutput, MAX[state.lb, 0], MIN[state.ub, 255]]; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: WriteGraph]] ]; }; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; }; Refresh: PROC [state:State] ~ { <<-- pin to be refreshed is is state.selected>> <<-- neighbors of selected are in state.lb state.ub>> IF state.haveSelection THEN { SetOutput: EnumerateProc ~ { xIndex: NAT _ Real.RoundC[x]; IF ~state.trc[xIndex].pinned THEN state.trc[xIndex].out _ Real.RoundC[y]; }; state.trc[state.selected].pinned _ TRUE; Enumerate[state.viewer, SetOutput, MAX[state.lb, 0], MIN[state.ub, 255]]; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: WriteGraph]] ]; }; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; }; <<>> StartFeedBack: PROC [state:State] ~ { <<-- starts the feedback process>> state.finished _ FALSE; TRUSTED {Process.Detach[FORK DoFeedBack[state]]}; }; <<>> DoFeedBack: PROC [state:State] ~ { <<-- keep processing last known mouse position until finished flag gets set>> SetOutput: EnumerateProc ~ { xIndex: NAT _ Real.RoundC[x]; IF ~state.trc[xIndex].pinned THEN state.trc[xIndex].out _ Real.RoundC[y]; }; IF state.mode=pin THEN { DO IF state.finished THEN EXIT; MovePin[state]; ENDLOOP; Enumerate[state.viewer, SetOutput, MAX[state.lb, 0], MIN[state.ub, 255]]; } ELSE { pinned: ARRAY [0..255] OF BOOLEAN _ ALL[FALSE]; DO IF state.finished THEN EXIT; MoveVertex[state]; pinned[state.selectedX] _ TRUE; ENDLOOP; FOR i: NAT IN [state.lb..state.ub] DO state.trc[i].pinned _ pinned[i]; ENDLOOP; Enumerate[state.viewer, SetOutput, MAX[state.lb, 0], MIN[state.ub, 255]]; FOR i: NAT IN [state.lb..state.ub] DO state.trc[i].pinned _ FALSE; ENDLOOP; state.trc[MAX[state.lb-1, 0]].pinned _ state.trc[MIN[state.ub+1, 255]].pinned _ TRUE; }; TRUSTED { ViewerOps.PaintViewer[ viewer: state.viewer, hint: client, clearClient: FALSE, whatChanged: NEW[CallBackRec _ [proc: WriteGraph]] ]; }; IF state.changeProc#NIL THEN state.changeProc[state.cPClientData]; }; <<>> <<>> Density: PROC [pixel: NAT, maxD:REAL] RETURNS [density:REAL] ~ { <<-- Compute density from pixel value. >> density _ maxD * (255 - pixel) / 255.0 }; <> <<-- Compute density from pixel value. >> <> <<};>> Reflectance: PROC [pixel:NAT, minR:REAL] RETURNS [reflectance:REAL] ~ { <<-- computes a reflectance from a pixel value>> reflectance _ minR + pixel * (1-minR) / 255.0; }; <<>> CreateClass: PROC ~ { <<-- Set up the viewer class structure and register it>> TRCViewerClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: Paint, tipTable: TIPUser.InstantiateNewTIPTable["TRCViewer.tip"], notify: Notify, init: Init ]]; ViewerOps.RegisterViewerClass[$TRCViewer, TRCViewerClass]; }; CreateClass[]; END.