TRCViewerImpl.mesa
Mik Lamming - November 9, 1984 3:59:53 pm PST
DIRECTORY Font, Graphics, Hist, Imager, ImagerBridge, ImagerTransform, 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 Font, Graphics, Imager, ImagerBridge, ImagerTransform, 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.ROPENIL,   -- filename or whatever
userXLabel, userYLabel: Rope.ROPENIL,
hasTRC, hasHisto, newData, finished: BOOLEANFALSE,
trc: Hist.TRCVec ← NIL,
histo: Hist.Histogram ← NIL,
histoPath: Imager.Outline,
iContext: Imager.Context ← NIL,
haveSelection: BOOLEANFALSE,
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 ← density,
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 ANYNIL,
mode: EditMode ← pin
];
CallBack: TYPE = REF CallBackRec;
CallBackRec: TYPE = RECORD [proc: Painter] ;
Painter: TYPE = PROC [context: Imager.Context, state: State];
-- CLASS GLOBALS --
newDataAvailable: CONDITION;
blobSize: NAT = 3;
blob: Imager.Outline ← Imager.MakeOutline[
LIST[
Imager.MoveTo[[-blobSize/2.0,0]].ArcTo[[blobSize/2.0, 0], [-blobSize/2.0,0]]
]];
graphOriginX: NAT ← 100;
graphOriginY: NAT ← 100;
graphSize: NAT ← 256;
graphScale: REAL ← 1;
axisWidth: NAT ← 2;
titleFont: Imager.FONT ← Font.Create["Xerox/PressFonts/Classic/MRR", Imager.Scale[14], $Ideal];
labelFont: Imager.FONT ← Font.Create["Xerox/PressFonts/Classic/MRR", Imager.Scale[12], $Ideal];
dataFont: Imager.FONT ← Font.Create["Xerox/PressFonts/Classic/MRR", Imager.Scale[10], $Ideal];
disclaimerFont: Imager.FONT ← Font.Create["Xerox/PressFonts/Tioga/MRR", Imager.Scale[8], $Ideal];
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:BOOLEANTRUE] ~ {
-- 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];
};
PinVertex: PUBLIC PROC [v: ViewerClasses.Viewer, in, out: NAT] ~ {
-- 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: State ← NARROW[v.data];
state.selectedX ← in; state.selectedY ← out;
[state.lb, state.ub] ← SetBounds[state.trc, state.selectedX];
LocalPinVertex[state];
};
UnpinVertex: PUBLIC PROC [v: ViewerClasses.Viewer, in: NAT] ~ {
state: State ← NARROW[v.data];
[state.lb, state.ub] ← SetBounds[state.trc, in];
state.trc[in].pinned ← FALSE;
RecomputeVertices[state];
TRUSTED {
ViewerOps.PaintViewer[
viewer: state.viewer,
hint: client,
clearClient: FALSE,
whatChanged: NEW[CallBackRec ← [proc: WriteGraph]]
];
};
};
RegisterChangeProc: PUBLIC PROC [v: ViewerClasses.Viewer, changeProc: ChangeProc, clientData: REF ANYNIL] ~ {
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: Imager.Trajectory;
max: REAL ← 0;
FOR i: NAT IN [0..255] DO
max ← MAX[max, histo[i]];
ENDLOOP;
traj ← Imager.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 ← Imager.MakeOutline[LIST[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:BOOLEANTRUE] ~ {
-- 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];
startD: REAL ← Density[vertices[pinA].out, state.minROut];
endD: REAL ← Density[vertices[i].out, state.minROut];
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 ← [
iContext: Imager.Create[$LFDisplay],
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.iContext.ScaleT[0.0254/72]; -- set 1 unit = 1 LF pixel
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: ImagerTransform.Pair;
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 ← ImagerTransform.InverseTransform[
p: [x:xy.mouseX, y:xy.mouseY],
transform: context.state.T
];
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]];
IF state.feedbackModeIn=density THEN
context.ShowCharacters[
IO.PutFR[
"%3g (%4.2fD)",
IO.card[x],
IO.real[Density[x, state.maxDIn]]],
IO.real[Density[x, state.minRIn]]],
dataFont]
ELSE
context.ShowCharacters[
IO.PutFR[
"%3g (%4.2fR)",
IO.card[x],
IO.real[Reflectance[x, state.minRIn]]],
dataFont];
context.SetXY[[70, 65]];
IF state.feedbackModeOut=density THEN
context.ShowCharacters[
IO.PutFR[
"%3g (%4.2fD)",
IO.card[y],
IO.real[Density[y, state.minROut]]],
IO.real[Density[y, state.maxDOut]]],
dataFont]
ELSE
context.ShowCharacters[
IO.PutFR[
"%3g (%4.2fR)",
IO.card[y],
IO.real[Reflectance[y, state.minROut]]],
dataFont];
};
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: Graphics.Box ← context.GetBounds[];
sx: REAL ← (box.xmax-box.xmin) / 400;
sy: REAL ← (box.ymax-box.ymin) / 400;
state.scale ← MIN[sx,sy];
IF whatChanged=NIL THEN {
ImagerBridge.SetViewFromGraphicsContext[state.iContext, context];
state.iContext.ScaleT[state.scale];
DrawAll[state.iContext, state];
}
ELSE {
callBack: CallBack ← NARROW[whatChanged, CallBack];
state.iContext.ScaleT[state.scale];
callBack.proc[state.iContext, state];
}
};
state: State ← NARROW[self.data];
state.iContext.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 ~ {
The image is painted in a square 400 units across
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 ← Imager.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.MaskStroke[lines, axisWidth];
context.SetXY[[graphOriginX, 20]];
IF state.title#NIL THEN
context.ShowCharacters[state.title, titleFont];
context.SetXY[[graphOriginX, 10]];
context.ShowCharacters[
"(The management appologises for the font quality)",
disclaimerFont];
context.SetXY[[graphOriginX+graphSize, 80]];
IF state.userXLabel#NIL THEN
context.ShowCharacters[state.userXLabel, labelFont];
context.SetXY[[80, graphOriginY+graphSize+10]];
IF state.userYLabel#NIL THEN
context.ShowCharacters[state.userYLabel, labelFont];
context.SetXY[[40, 80]];
context.ShowCharacters["In: ", dataFont];
context.SetXY[[40, 65]];
context.ShowCharacters["Out: ", dataFont];
};
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:BOOLEANFALSE] ~ {
-- draw the vertices and/or histogram
first: INTEGERMAX[min, 0];
last: INTEGERMIN[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.MaskFill[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:BOOLEANFALSE] ~ {
-- 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:BOOLEANFALSE] ~ {
-- draws single blob in a graphSize square grid origin at 0,0
PaintBlob: PROC ~ {
context.TranslateT[x,y];
IF xor THEN context.SetColor[Imager.XOR] ELSE context.SetColor[Imager.black];
context.MaskFill[blob];
};
context.DoSaveAll[PaintBlob];
};
PaintDotAt: PROC [context: Imager.Context, x,y:NAT, xor:BOOLEANFALSE] ~ {
-- draws single blob in a graphSize square grid origin at 0,0
PaintDot: PROC ~ {
context.TranslateT[x,y];
IF xor THEN context.SetColor[Imager.XOR] 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:BOOLEANFALSE] ~ {
-- joins vertices in a graphSize square grid origin at 0,0
graph: Imager.Trajectory;
IF NOT state.hasTRC THEN RETURN;
graph ← Imager.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[Imager.XOR] ELSE context.SetColor[Imager.black];
context.MaskStroke[graph, 0];
};
-- 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];
TRUSTED {
ViewerOps.PaintViewer[
viewer: state.viewer,
hint: client,
clearClient: FALSE,
whatChanged: NEW[CallBackRec ← [proc: XORGraph]]
];
};
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: INTEGERMAX[target-1, -1];
nextHigher: INTEGERMIN[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: BOOLEANFALSE;
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<distance THEN {
distance ← thisDist;
nearest ← i;
};
foundOne ← TRUE;
};
ENDLOOP;
IF (state.haveSelection ← foundOne) THEN {
state.selected ← nearest;
state.haveSelection ← TRUE
};
};
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 BOOLEANALL[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
};
Density: PROC [pixel: NAT, minR:REAL] RETURNS [density:REAL] ~ {
-- Compute density from pixel value.
density ← -RealFns.Log[10, Reflectance[pixel, minR]];
};
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.