ColorEditViewersImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Eric Nickell, January 7, 1986 8:25:08 pm PST
DIRECTORY
ColorEditViewers,
ColorEditViewersPrivate,
AdjustColorStd USING [CurrentTRC, NormalizeAdjustments, TRC],
Atom USING [GetPName],
ColorDisplay USING [GetColorDisplayStatus],
ColorEditOps USING [ApplyLog, LogApply, ReadExtentOfMask, SaveExtentOfMask],
Commander USING [CommandProc, Register],
CommandTool USING [CurrentWorkingDirectory, ParseToList],
Cursors USING [CursorHandle, CursorInfo, CursorRec, CursorType, NewCursor, SetCursor],
FS USING [Error, FileInfo, StreamOpen],
Icons USING [IconFlavor],
ImagerPixelMap USING [DeviceRectangle, Intersect],
ImagerTransformation USING [Cat, Concat, Create, Invert, Scale, Transform, Transformation, Translate],
InputFocus USING [GetInputFocus, SetInputFocus],
Intime USING [EventTime, ReadEventTime],
IO USING [Close, GetChar, EndOfStream, PutChar, PutRope, ROS, RopeFromROS, STREAM],
MessageWindow USING [Append],
PixelGraphics USING [InterpolateLines, Point],
PopUpMenu USING [RequestSelection],
Process USING [Detach],
Real USING [RoundC, RoundI],
RealOps USING [RoundLI, RoundI],
Rope USING [Cat, Concat, ROPE],
SampleArrayDisplay USING [BuildSampler, Display, Screen, ViewerInformation],
SampleArrayIcons USING [NewIcon],
SampleArrays USING [FileNameRoot, FromAIS, GetSample, Join3AIS, SampleArray, Save],
SampleMapOps USING [Create, Clear, CVEC, GetSample, PutSample, SampleMap],
SelectRegions USING [ApplyTRC, Differences, DiscriminateColor, Fill, FillRectangle, Intersect, MatchColor, MatchColorFill, nullRectangle, Operation, PaintBrush, PaintPixel, Rectangle, RGB, TRCSequence, TRCSequenceRep],
TajoMenus USING [CreateTajoSystem, Select, TajoSystem],
Terminal USING [Current, SetColorCursorPresentation, Virtual],
TIPTables USING [TIPTime],
TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec],
Vector2 USING [VEC],
ViewerClasses USING [Column, HScrollProc, NotifyProc, PaintProc, ScrollProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec],
ViewerOps;
ColorEditViewersImpl: CEDAR MONITOR
IMPORTS AdjustColorStd, Atom, ColorDisplay, ColorEditOps, Commander, CommandTool, Cursors, FS, ImagerPixelMap, ImagerTransformation, InputFocus, Intime, IO, MessageWindow, PixelGraphics, PopUpMenu, Process, Real, RealOps, Rope, SampleArrayDisplay, SampleArrayIcons, SampleArrays, SampleMapOps, SelectRegions, TajoMenus, Terminal, TIPUser, ViewerOps
EXPORTS ColorEditViewers
~ BEGIN
OPEN ColorEditViewers, ColorEditViewersPrivate;
ROPE: TYPE ~ Rope.ROPE;
TRC: TYPE ~ AdjustColorStd.TRC;
VEC: TYPE ~ Vector2.VEC;
Viewer: TYPE ~ ViewerClasses.Viewer;
Separation: TYPE ~ {red, green, blue}; --Color separations
ColorEditViewerData: TYPE ~ REF ColorEditViewerDataRec;
ColorEditViewerDataRec: PUBLIC TYPE ~ ColorEditViewersPrivate.ColorEditViewerDataRec;
Quit: SIGNAL ~ CODE;
term: Terminal.Virtual ~ Terminal.Current[];
op: ARRAY Button OF Operation;
Viewer Creation and AIS File attachment
CantFindFile: PUBLIC ERROR ~ CODE;
Create: PUBLIC PROCEDURE [info: ViewerClasses.ViewerRec ← [], clientData: REF ANYNIL] RETURNS [aisViewer: ColorEditViewer] ~ {
info.scrollable ← info.hscrollable ← TRUE;
[info.data, info.name, info.icon] ← Rope2ColorEditViewerData[info.name, clientData
! CantFindFile => {
MessageWindow.Append[Rope.Concat[info.name, " not found."], TRUE];
GOTO Failed;
}
];
aisViewer ← ViewerOps.CreateViewer[flavor: $ColorEditViewer, info: info];
[] ← AISVScroll[self: aisViewer, op: up, amount: 0, shift: TRUE, control: TRUE];
EXITS Failed => NULL;
};
CurrentViewer: PUBLIC ENTRY PROC RETURNS [ColorEditViewer] ~ {
ENABLE UNWIND => NULL;
v: ColorEditViewer ~ InputFocus.GetInputFocus[].owner;
RETURN [IF v=NIL OR v.class.flavor#$ColorEditViewer THEN NIL ELSE v];
};
SAFileInfo: TYPE ~ RECORD [sa: SampleArrays.SampleArray, name: ROPE];
nullSAInfo: SAFileInfo ~ [NIL, NIL];
Rope2ColorEditViewerData: PROC [file: ROPE, clientData: REFNIL] RETURNS [aisViewerData: ColorEditViewerData, name: ROPE, icon: Icons.IconFlavor] ~ {
saInfo: SAFileInfo;
aisViewerData ← NEW[ColorEditViewerDataRec];
SELECT TRUE FROM
(saInfo ← Open3Windows[file, ["-red.ais", "-grn.ais", "-blu.ais"]])#nullSAInfo => NULL;
(saInfo ← Open3Windows[file, ["-red.ais", "-green.ais", "-blue.ais"]])#nullSAInfo => NULL;
(saInfo ← Open1Window[file, ".ais"])#nullSAInfo => NULL;
(saInfo ← Open1Window[file, "-8bit.ais"])#nullSAInfo => NULL;
(saInfo ← Open1Window[file, ""])#nullSAInfo => NULL;
ENDCASE => {
ERROR CantFindFile[];
};
aisViewerData.sa ← saInfo.sa;
name ← saInfo.name;
icon ← SampleArrayIcons.NewIcon[sa: aisViewerData.sa, layer: MIN[1, aisViewerData.sa.n-1]]; --Get green for rgb, otherwise just the black
aisViewerData.sm ← SampleMapOps.Create[sSize: aisViewerData.sa.sSize, fSize: aisViewerData.sa.fSize, bitsPerSample: 1];
SampleMapOps.Clear[sampleMap: aisViewerData.sm];
};
Open1Window: PROC [file: ROPE, windowName: ROPE] RETURNS [saInfo: SAFileInfo] ~ {
OpenWindowsWithoutCatchingErrorsButUnwinding: PROC ~ {
saInfo.name ← FS.FileInfo[
name: Rope.Cat[file, windowName],
wDir: CommandTool.CurrentWorkingDirectory[]
].fullFName;
saInfo.sa ← SampleArrays.FromAIS[name: saInfo.name];
};
OpenWindowsWithoutCatchingErrorsButUnwinding[! ANY => GOTO ReturnNoWindows];
EXITS
ReturnNoWindows => RETURN [nullSAInfo]
};
Open3Windows: PROC [file: ROPE, windowNames: ARRAY Separation OF ROPE] RETURNS [saInfo: SAFileInfo] ~ {
OpenWindowsWithoutCatchingErrorsButUnwinding: PROC ~ {
files: ARRAY Separation OF ROPE;
FOR s: Separation IN Separation DO--Check the existence of files, and remember names
files[s] ← FS.FileInfo[
name: Rope.Cat[file, windowNames[s]],
wDir: CommandTool.CurrentWorkingDirectory[]
].fullFName;
ENDLOOP;
saInfo.name ← Rope.Cat[file, "-*.ais"];
saInfo.sa ← SampleArrays.Join3AIS[name1: files[red], name2: files[green], name3: files[blue]];
};
OpenWindowsWithoutCatchingErrorsButUnwinding[! ANY => GOTO ReturnNoWindows];
EXITS
ReturnNoWindows => RETURN [nullSAInfo]
};
AdjustTransformation: PROC [data: ColorEditViewerData, m: Transformation] ~ {
SetTransformation[data, ImagerTransformation.Concat[data.m, m]];
};
SetTransformation: PROC [data: ColorEditViewerData, m: Transformation] ~ {
corner1: VEC ~ ImagerTransformation.Transform[m: m, v: [0, 0]];
corner2: VEC ~ ImagerTransformation.Transform[m: m, v: [data.sa.fSize, data.sa.sSize]];
data.picture ← [
sMin: Real.RoundI[MIN[corner1.y, corner2.y]],
fMin: Real.RoundI[MIN[corner1.x, corner2.x]],
sSize: Real.RoundI[MAX[corner1.y, corner2.y]-MIN[corner1.y, corner2.y]],
fSize: Real.RoundI[MAX[corner1.x, corner2.x]-MIN[corner1.x, corner2.x]]
];
data.m ← m;
data.mInverse ← ImagerTransformation.Invert[m: m];
data.mInverse.a ← 1.0/m.a; --Clean up errors from ImagerTransformation
data.mInverse.e ← 1.0/m.e;
data.picture ← SampledByRectangle[data.mInverse, [sMin: 0, fMin: 0, sMax: data.sa.sSize-1, fMax: data.sa.fSize-1], 0, 0];
data.fast ← SampleArrayDisplay.BuildSampler[min: MAX[data.picture.fMin, 0], size: 640, m: data.mInverse.a, b: data.mInverse.c];
data.slow ← SampleArrayDisplay.BuildSampler[min: MAX[data.picture.sMin, 0], size: 640, m: data.mInverse.e, b: data.mInverse.f];
};
AdjustRect: PROC [relative, absolute: DeviceRectangle] RETURNS [result: DeviceRectangle] ~ INLINE {
result ← ImagerPixelMap.Intersect[absolute, [sMin: relative.sMin+absolute.sMin, fMin: relative.fMin+absolute.fMin, sSize: relative.sSize, fSize: relative.fSize]];
};
ColorEditViewersPaint: PRIVATE ViewerClasses.PaintProc ~ {
[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL ← FALSE]
aisViewerData: ColorEditViewerData ~ NARROW[self.data];
screen: SampleArrayDisplay.Screen;
rect: ImagerPixelMap.DeviceRectangle;
[screen, rect] ← SampleArrayDisplay.ViewerInformation[self];
SampleArrayDisplay.Display[screen: screen, rect: rect, sa: aisViewerData.sa, sm: aisViewerData.sm, slow: aisViewerData.slow, fast: aisViewerData.fast, picRect: AdjustRect[aisViewerData.picture, rect]];
};
FitTransformation: PROC [data: ColorEditViewerData] RETURNS [m: Transformation] ~ {
scale: REALMIN[640.0/data.sa.fSize, 480.0/data.sa.sSize];
scale ← IF scale>=1.0 THEN (REAL[RealOps.RoundLI[scale, [round: rm]]]) ELSE (1.0/REAL[RealOps.RoundLI[1.0/scale, [round: rp]]]);
m ← ImagerTransformation.Cat[
m1: ImagerTransformation.Translate[t: [-data.sa.fSize/2.0, -data.sa.sSize/2.0]],
m2: ImagerTransformation.Scale[s: scale],
m3: ImagerTransformation.Translate[t: [640.0/2, 480.0/2]]
];
};
AISVScroll: ViewerClasses.ScrollProc = {
[self: ViewerClasses.Viewer, op: ViewerClasses.ScrollOp, amount: INTEGER, shift: BOOL ← FALSE, control: BOOL ← FALSE] RETURNS [top: INTEGER, bottom: INTEGER]
data: ColorEditViewerData ~ NARROW[self.data];
min: REAL ~ ImagerTransformation.Transform[m: data.m, v: [0,0]].y;
max: REAL ~ ImagerTransformation.Transform[m: data.m, v: [data.sa.fSize, data.sa.sSize]].y;
size: REAL ~ (max-min)/100.0;
SELECT op FROM
query => {
top ← MIN[100, MAX[0, Real.RoundI[-min/size]]];
bottom ← MIN[100, MAX[0, Real.RoundI[(self.ch-min)/size]]];
RETURN;
};
thumb => {
scale: REAL ~ data.m.a;
tx: REAL ~ data.m.c;
ty: REAL ~ -amount/100.0*data.sa.sSize;
SetTransformation[data, ImagerTransformation.Create[a: scale, b: 0, c: tx, d: 0, e: scale, f: ty]];
};
up => AdjustTransformation[data, ImagerTransformation.Translate[t: [0, -amount]]];
down => AdjustTransformation[data, ImagerTransformation.Translate[t: [0, amount]]];
ENDCASE => ERROR;
SELECT TRUE FROM
shift AND control => SetTransformation[data, FitTransformation[data]];
control => AdjustTransformation[data, ImagerTransformation.Scale[s: 5]];
shift => AdjustTransformation[data, ImagerTransformation.Scale[s: 2]];
ENDCASE;
ViewerOps.PaintViewer[viewer: self, hint: client];
};
AISHScroll: ViewerClasses.HScrollProc = {
[self: ViewerClasses.Viewer, op: ViewerClasses.HScrollOp, amount: INTEGER, shift: BOOL ← FALSE, control: BOOL ← FALSE] RETURNS [left: INTEGER, right: INTEGER]
data: ColorEditViewerData ~ NARROW[self.data];
min: REAL ~ ImagerTransformation.Transform[m: data.m, v: [0,0]].x;
max: REAL ~ ImagerTransformation.Transform[m: data.m, v: [data.sa.fSize, data.sa.sSize]].x;
size: REAL ~ (max-min)/100.0;
SELECT op FROM
query => {
left ← MIN[100, MAX[0, Real.RoundI[-min/size]]];
right ← MIN[100, MAX[0, Real.RoundI[(self.cw-min)/size]]];
RETURN;
};
thumb => {
scale: REAL ~ data.m.a;
tx: REAL ~ -amount/100.0*data.sa.fSize;
ty: REAL ~ data.m.f;
SetTransformation[data, ImagerTransformation.Create[a: scale, b: 0, c: tx, d: 0, e: scale, f: ty]];
};
left => AdjustTransformation[data, ImagerTransformation.Translate[t: [-amount, 0]]];
right => AdjustTransformation[data, ImagerTransformation.Translate[t: [amount, 0]]];
ENDCASE => ERROR;
SELECT TRUE FROM
shift AND control => SetTransformation[data, ImagerTransformation.Scale[s: 1]];
control => AdjustTransformation[data, ImagerTransformation.Scale[s: 5]];
shift => AdjustTransformation[data, ImagerTransformation.Scale[s: 2]];
ENDCASE;
ViewerOps.PaintViewer[viewer: self, hint: client];
};
Cursors
cursorHandles: ARRAY Cursors.CursorType OF Cursors.CursorHandle ← ALL[NIL];
cursorSampleMaps: ARRAY Cursors.CursorType OF SampleMap ← ALL[NIL];
CursorArray: TYPE ~ ARRAY [0..16) OF PACKED ARRAY [0..16) OF [0..1];
EnsureSampleMapForCursor: PROC [cursor: Cursors.CursorType] ~ {
IF cursorSampleMaps[cursor]=NIL THEN ERROR;
};
CreateCursor: PROC [hotX, hotY: INTEGER, bits: CursorArray] RETURNS [t: Cursors.CursorType] ~ {
h: Cursors.CursorHandle ← NEW[Cursors.CursorRec];
h.bits ← LOOPHOLE[bits];
h.info ← [
type: Cursors.NewCursor[h.bits, hotX, hotY],
hotX: hotX,
hotY: hotY
];
t ← h.info.type;
cursorHandles[t] ← h;
cursorSampleMaps[t] ← SampleMapOps.Create[sSize: 16, fSize: 16, bitsPerSample: 1];
SampleMapOps.Clear[cursorSampleMaps[t]];
FOR s: [0..16) IN [0..16) DO
FOR f: [0..16) IN [0..16) DO
IF bits[s][f]=1 THEN SampleMapOps.PutSample[cursorSampleMaps[t], [s, f], 1];
ENDLOOP;
ENDLOOP;
};
contourCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0],
[0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0],
[1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
[0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0],
[0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0],
[0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,0],
[0,0,1,0,0,0,1,0,0,1,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,1],
[0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0],
[0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0]
]];
selBrushCursor: Cursors.CursorType ~ CreateCursor[hotX: 0, hotY: 0, bits: [
[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0],
[1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
matchColorCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0],
[0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0],
[0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0],
[0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0],
[0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0],
[0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0],
[0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0],
[0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0],
[0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
edgeBrushCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0],
[0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0],
[0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,1,0,1,1,0,0],
[0,1,0,1,0,0,0,0,0,0,0,1,0,1,0,0],
[1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0],
[1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0],
[1,0,1,0,0,0,0,1,0,0,0,0,1,0,1,0],
[1,1,0,0,0,0,0,0,0,0,0,0,0,1,1,0],
[1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0],
[0,1,0,1,0,0,0,0,0,0,0,1,0,1,0,0],
[0,1,1,0,1,0,0,0,0,0,1,0,1,1,0,0],
[0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,0],
[0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0],
[0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
selColorBrushCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0],
[0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0],
[0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,0],
[0,1,1,0,1,0,1,0,1,0,1,0,1,1,0,0],
[0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0],
[1,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0],
[1,1,0,1,0,0,0,0,0,0,0,1,0,1,1,0],
[1,0,1,0,0,0,0,1,0,0,0,0,1,0,1,0],
[1,1,0,1,0,0,0,0,0,0,0,1,0,1,1,0],
[1,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0],
[0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0],
[0,1,1,0,1,0,1,0,1,0,1,0,1,1,0,0],
[0,0,1,1,0,1,0,1,0,1,0,1,1,0,0,0],
[0,0,0,1,1,0,1,0,1,0,1,1,0,0,0,0],
[0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
rectCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
selPixelCursor: Cursors.CursorType ~ CreateCursor[hotX: -7, hotY: -7, bits: [
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]];
TIP Notify and Commands
The following procedures, when coupled with an appropriate TIP table, implement the user interface for the AIS Viewer as: follows:
Left Mouse Button: Controls the ``current'' operation, which is selectable from a wide variety. Each operation contains procedure to be called when the button is pressed, when it is dragged, when the operation is aborted, and when the button is released (see fuller details in Operations section). When the mouse button is pressed the state of the shift and control keys is remembered, and whether or not the button was double-clicked.
Middle Mouse Button: Middle-clicking will bring up the AIS Viewer pop-up menus. (Double-clicking brings up the top-level menu, while single clicking tries to be smart about which menu the user is likely to want.) The menu system should return either a procedure to call, or an operation to install on the left mouse button.
Right Mouse Button: Currently implemented like the left button, except that no provision has been made to change the operation found there.
Notify: ENTRY ViewerClasses.NotifyProc = {
[self: ViewerClasses.Viewer, input: LIST OF REF ANY]
ENABLE UNWIND => NULL;
data: ColorEditViewerData ~ NARROW[self.data];
ts: TIPState ~ data.ts;
FOR each: LIST OF REF ANY ← input, each.rest UNTIL each=NIL DO
IF ts.state=menu THEN {
Here, to recover from a PopUp menu
data.ts.state ← ready;  --Kluge!!!
};
WITH each.first SELECT FROM
atom: ATOM => {
SELECT atom FROM
$Drag => Drag[self, data];
$Press => Press[self, data];
$Abort => Abort [self, data];
$Release => Release[self, data];
$CheckAbort => CheckAbort[self, data];
$Menu => TRUSTED {Process.Detach[FORK CallUpMenu[NIL, self, data]]};
$Shift => data.ts.shift ← TRUE;
$Ctrl => data.ts.ctrl ← TRUE;
$Double => data.ts.double ← TRUE;
$Ignore => NULL;
ENDCASE => MessageWindow.Append[Rope.Concat["Unknown atom given to AISView: ", Atom.GetPName[atom]], TRUE];
};
coords: TIPUser.TIPScreenCoords => {
data.ts.pos ← [
mouseX: MAX[data.picture.fMin, MIN[data.picture.fMin+data.picture.fSize-1, coords.mouseX]],
mouseY: MAX[data.picture.sMin, MIN[data.picture.sMin+data.picture.sSize-1, self.ch - coords.mouseY]],
color: coords.color
];
};
z: TIPTables.TIPTime => NULL;
ENDCASE => MessageWindow.Append["Unknown input given to ColorEditViewer.", TRUE];
ENDLOOP;
};
Drag: TIPCmdProc ~ {
IF data.ts.state#mouseDown THEN RETURN;  --I.e. something has gone wrong
IF op[data.ts.button].drag#NIL THEN op[data.ts.button].drag[self, data ! Quit => GOTO Abort];
EXITS
Abort => Abort[self, data];
};
Press: TIPCmdProc ~ {
IF data.ts.state#ready THEN RETURN;  --Why shouldn't it be ready?
InputFocus.SetInputFocus[self: self];
IF op[data.ts.button].press#NIL THEN op[data.ts.button].press[self, data ! Quit => GOTO Abort];
data.ts.state ← mouseDown;
EXITS
Abort => Abort[self, data];
};
Abort: TIPCmdProc ~ {
IF data.ts.state=abort THEN RETURN;  --Only Abort once
IF op[data.ts.button].abort#NIL THEN op[data.ts.button].abort[self, data ! Quit => CONTINUE];
data.ts.state ← abort;
};
Release: TIPCmdProc ~ {
IF data.ts.state=mouseDown THEN {
IF op[data.ts.button].release#NIL THEN op[data.ts.button].release[self, data ! Quit => CONTINUE];
IF data.ts.button=left THEN data.ts.toUndo ← op[data.ts.button];   --Remember for Undo
};
Prespare for next Press
data.ts.state ← ready;
data.ts.double ← data.ts.shift ← data.ts.ctrl ← FALSE;
data.ts.button ← left;
};
CheckAbort: TIPCmdProc ~ {
IF data.ts.state#ready THEN {
Abort[self, data];  --Abort previous action
data.ts.state ← ready;  --And become ready
};
};
UndoProc: REF TIPCmdProc ← NEW[TIPCmdProc ← Undo];
Undo: TIPCmdProc ~ {
IF data.ts.state#ready THEN ERROR;
IF data.ts.toUndo=NIL OR data.ts.toUndo.undo=NIL THEN {
For some reason, we can't Undo that last action
[] ← PopUpMenu.RequestSelection[NIL, LIST["Can't Undo."], 1];
}
ELSE {
data.ts.toUndo.undo[self, data ! Quit => CONTINUE];
};
};
Operations
destination: SelectRegions.Operation ← on;
InvertIfDouble: PROC [d: SelectRegions.Operation, double: BOOL] RETURNS [SelectRegions.Operation] ~ INLINE {
RETURN [IF double THEN
SELECT d FROM on => off, off => on, flip => flip ENDCASE => ERROR
ELSE d]
};
Select Rectangle Operation
rectTitle: ROPE ~ "by Rectangle";
RectPress: TIPCmdProc ~ {
tip: TIPUser.TIPScreenCoordsRec ~ data.ts.from ← data.ts.pos;
data.ts.rect ← SamplesRectangle[data.mInverse, tip.mouseX, tip.mouseY, tip.mouseX, tip.mouseY];
};
RectDrag: TIPCmdProc ~ {
tip: TIPUser.TIPScreenCoordsRec ~ data.ts.pos;
rect: Rectangle ~ SamplesRectangle[data.mInverse, data.ts.from.mouseX, data.ts.from.mouseY, tip.mouseX, tip.mouseY];
DoDifferences: PROC [r: Rectangle] ~ {
SelectRegions.FillRectangle[sm: data.sm, rect: r, op: flip];
PaintRectangle[self, data, r];
};
SelectRegions.Differences[a: data.ts.rect, b: rect, action: DoDifferences];
data.ts.rect ← rect;
};
RectRelease: TIPCmdProc ~ TRUSTED {
op: SelectRegions.Operation ~ InvertIfDouble[destination, data.ts.double];
SELECT op FROM
on, flip => data.extent ← ExtendExtent[data.extent, data.ts.rect];
off => data.extent ← DeextendExtent[data.extent, data.ts.rect];
ENDCASE => ERROR;
IF op#flip THEN { --If op=flip, then we're already done
SelectRegions.FillRectangle[sm: data.sm, rect: data.ts.rect, op: op];
PaintRectangle[self, data, data.ts.rect];
};
};
RectAbort: TIPCmdProc ~ TRUSTED {
SelectRegions.FillRectangle[sm: data.sm, rect: data.ts.rect, op: flip];
PaintRectangle[self, data, data.ts.rect];
};
selectRectOperation: Operation ← InstallOperation[op: [
press: RectPress,
drag: RectDrag,
release: RectRelease,
abort: RectAbort,
cursor: rectCursor
]];
Select Contour Operation
contourTitle: ROPE ~ "by Contour";
ContourPress: TIPCmdProc ~ {
Action: PROC ~ {
extent: Rectangle;
loc: CVEC;
[x: loc.f, y: loc.s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
extent ← SelectRegions.Fill[sm: data.sm, loc: loc];
PaintRectangle[self, data, extent];
data.extent ← ExtendExtent[data.extent, extent];
};
WaitFor[Action, self];
};
selectContourOperation: Operation ← InstallOperation[op: [
press: ContourPress,
undo: ContourUndo,
cursor: contourCursor
]];
Select by Color Operation
MatchColorPress: TIPCmdProc ~ {
Action: PROC ~ {
extent: Rectangle;
loc: CVEC;
[x: loc.f, y: loc.s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
extent ← SelectRegions.MatchColorFill[sm: data.sm, sa: data.sa, loc: loc, colorDistance: colorBrushColorDistance];
PaintRectangle[self, data, extent];
data.extent ← ExtendExtent[data.extent, extent];
};
WaitFor[Action, self];
};
MatchColorUndo: TIPCmdProc ~ TRUSTED {
SelectRegions.UndoFillOrMatchColor[];
};
selectMatchColorOperation: Operation ← InstallOperation[op: [
press: MatchColorPress,
undo: MatchColorUndo,
cursor: matchColorCursor
]];
Select by Edge Brush Operation
EdgeBrushPress: TIPCmdProc ~ {
GetColor: PROC RETURNS [rgb: SelectRegions.RGB] ~ {
RETURN [[
r: SampleArrays.GetSample[data.sa, 0, [s: s, f: f]],
g: SampleArrays.GetSample[data.sa, 1, [s: s, f: f]],
b: SampleArrays.GetSample[data.sa, 2, [s: s, f: f]]
]];
};
s, f: CARDINAL;
[x: f, y: s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
SELECT TRUE FROM
data.ts.double => ContourPress[self, data];
data.ts.shift OR data.ts.ctrl => data.referentColor ← GetColor[];
ENDCASE => {  --Normal case
data.ts.rect ← [sMin: Lo[s], fMin: Lo[f], sMax: s+brushSize, fMax: f+brushSize];
SelectRegions.DiscriminateColor[
sm: data.sm,
sa: data.sa,
near: GetColor[],
far: data.referentColor,
bounds: data.ts.rect
];
PaintRectangle[self, data, data.ts.rect];
data.extent ← ExtendExtent[data.extent, data.ts.rect];
};
};
EdgeBrushDrag: TIPCmdProc ~ {
SELECT TRUE FROM
data.ts.double OR data.ts.shift OR data.ts.ctrl => NULL; --Ignore because a double is a fill, and shift or control select the color to ignore
ENDCASE => {
Action: PROC [r: Rectangle] ~ {
SelectRegions.DiscriminateColor[sm: data.sm, sa: data.sa, near: [r: red, g: green, b: blue], far: data.referentColor, bounds: r];
PaintRectangle[self, data, r];
data.extent ← ExtendExtent[data.extent, r];
};
red, green, blue: CARDINAL;
s, f: CARDINAL;
rectangle: Rectangle;
[x: f, y: s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
red ← SampleArrays.GetSample[data.sa, 0, [s: s, f: f]];
green ← SampleArrays.GetSample[data.sa, 1, [s: s, f: f]];
blue ← SampleArrays.GetSample[data.sa, 2, [s: s, f: f]];
rectangle ← [sMin: Lo[s], fMin: Lo[f], sMax: s+brushSize, fMax: f+brushSize];
SelectRegions.Differences[a: rectangle, b: SelectRegions.Intersect[rectangle, data.ts.rect], action: Action];
data.ts.rect ← rectangle;
};
};
edgeBrushOperation: Operation ← InstallOperation[op: [
press: EdgeBrushPress,
drag: EdgeBrushDrag,
cursor: edgeBrushCursor
]];
Select by Color Brush Operation
brushSize: PUBLIC CARDINAL ← 16;
colorBrushColorDistance: PUBLIC CARDINAL ← 16;
Lo: PROC [val: CARDINAL] RETURNS [CARDINAL] ~ INLINE {
RETURN [MAX[val, brushSize]-brushSize]
};
SelColorBrushPress: TIPCmdProc ~ {
SELECT TRUE FROM
data.ts.double => ContourPress[self, data];
ENDCASE => {  --Normal case
s, f: CARDINAL;
[x: f, y: s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
data.ts.rect ← [sMin: Lo[s], fMin: Lo[f], sMax: s+brushSize, fMax: f+brushSize];
SelectRegions.MatchColor[
sm: data.sm,
sa: data.sa,
rgb: [
r: SampleArrays.GetSample[data.sa, 0, [s: s, f: f]],
g: SampleArrays.GetSample[data.sa, 1, [s: s, f: f]],
b: SampleArrays.GetSample[data.sa, 2, [s: s, f: f]]
],
bounds: data.ts.rect,
colorDistance: colorBrushColorDistance
];
PaintRectangle[self, data, data.ts.rect];
data.extent ← ExtendExtent[data.extent, data.ts.rect];
};
};
SelColorBrushDrag: TIPCmdProc ~ {
SELECT TRUE FROM
data.ts.double => NULL;  --Ignore because a double is a fill
ENDCASE => {
Action: PROC [r: Rectangle] ~ {
SelectRegions.MatchColor[sm: data.sm, sa: data.sa, rgb: [r: red, g: green, b: blue], bounds: r, colorDistance: colorBrushColorDistance];
PaintRectangle[self, data, r];
data.extent ← ExtendExtent[data.extent, r];
};
red, green, blue: CARDINAL;
s, f: CARDINAL;
rectangle: Rectangle;
[x: f, y: s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
red ← SampleArrays.GetSample[data.sa, 0, [s: s, f: f]];
green ← SampleArrays.GetSample[data.sa, 1, [s: s, f: f]];
blue ← SampleArrays.GetSample[data.sa, 2, [s: s, f: f]];
rectangle ← [sMin: Lo[s], fMin: Lo[f], sMax: s+brushSize, fMax: f+brushSize];
SelectRegions.Differences[a: rectangle, b: SelectRegions.Intersect[rectangle, data.ts.rect], action: Action];
data.ts.rect ← rectangle;
};
};
selectColorBrushOperation: Operation ← InstallOperation[op: [
press: SelColorBrushPress,
drag: SelColorBrushDrag,
cursor: selColorBrushCursor
]];
Select by Brush Operation
SelBrushPress, SelBrushDrag: TIPCmdProc ~ TRUSTED {
op: SelectRegions.Operation ~ InvertIfDouble[destination, data.ts.double];
current: Cursors.CursorType ~ selectBrushOperation.cursor;
info: Cursors.CursorInfo ~ cursorHandles[current].info;
loc: CVEC;
extent: Rectangle;
[x: loc.f, y: loc.s] ← Samples[data.mInverse, data.ts.pos.mouseX-info.hotX, data.ts.pos.mouseY-info.hotY];
EnsureSampleMapForCursor[current];
SelectRegions.PaintBrush[sm: data.sm, loc: loc, op: op, brush: cursorSampleMaps[current]];
extent ← [sMin: loc.s, fMin: loc.f, sMax: loc.s+16, fMax: loc.f+16];
PaintRectangle[self, data, extent];
IF op#off THEN data.extent ← ExtendExtent[data.extent, extent];
};
selectBrushOperation: Operation ← InstallOperation[op: [
press: SelBrushPress,
drag: SelBrushDrag,
cursor: selBrushCursor
]];
Select by Pixel Operation
SelPixelPress: TIPCmdProc ~ TRUSTED {
op: SelectRegions.Operation ~ InvertIfDouble[destination, data.ts.double];
loc: CVEC;
[x: loc.f, y: loc.s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
SelectRegions.PaintPixel[sm: data.sm, loc: loc, op: op];
data.ts.loc ← loc;
PaintRectangle[self, data, [sMin: loc.s, fMin: loc.f, sMax: loc.s+1, fMax: loc.f+1]];
IF op#off THEN data.extent ← ExtendExtent[data.extent, [sMin: loc.s, fMin: loc.f, sMax: loc.s+1, fMax: loc.f+1]];
};
SelPixelDrag: TIPCmdProc ~ {
PaintPoint: PROC [p: PixelGraphics.Point] ~ {
loc: CVEC ~ [s: p.y, f: p.x];
SelectRegions.PaintPixel[sm: data.sm, loc: loc, op: op];
};
op: SelectRegions.Operation ~ InvertIfDouble[destination, data.ts.double];
to: CVEC;
extent: Rectangle;
fFrom: CARDINAL ~ data.ts.loc.f;
sFrom: CARDINAL ~ data.ts.loc.s;
[x: to.f, y: to.s] ← Samples[data.mInverse, data.ts.pos.mouseX, data.ts.pos.mouseY];
PixelGraphics.InterpolateLines[LIST[[fFrom, sFrom], [to.f, to.s]], PaintPoint]; --Interpolate intermediate position
extent ← [sMin: MIN[sFrom, to.s], fMin: MIN[fFrom, to.f], sMax: MAX[sFrom, to.s]+1, fMax: MAX[fFrom, to.f]+1];
PaintRectangle[self, data, extent];
IF op#off THEN data.extent ← ExtendExtent[data.extent, extent];
data.ts.loc ← to;
};
selectPixelOperation: Operation ← InstallOperation[op: [
press: SelPixelPress,
drag: SelPixelDrag,
cursor: selPixelCursor
]];
Install the various operations
InstallOperation: PROC [op: OperationRec, fastChar: CHAR ← ' ] RETURNS [ref: Operation] ~ {
title is the ROPE found in the menu system which will activate the operation on the left button
ref ← NEW[OperationRec ← op];
};
InstallOperations: PROC ~ {
Called at start-up
op[left] ← selectMatchColorOperation;
op[right] ← selectPixelOperation;
};
Menus and such
In this section, we install all the menus, and those menu items which immediately activate procedures (as opposed to setting up the operation hung on the left mouse button).
ts: TajoMenus.TajoSystem;
CallUpMenu: PROC [title: ROPE, self: ColorEditViewer, data: ColorEditViewerData] ~ {
ref: REF ← TajoMenus.Select[ts, title];
IF ref=NIL THEN RETURN;
WITH ref SELECT FROM
operation: Operation => {   --I.e. something to install
op[data.ts.button] ← operation;  --Install the operation
self.class.cursor ← operation.cursor;  --Set the cursor type
};
proc: REF TIPCmdProc => proc[self, data];
ENDCASE => ERROR;
data.ts.state ← menu;
TRUSTED {data.ts.ignoreUntil ← Intime.ReadEventTime[];}
};
InstallMenus: PROC ~ {
ts ← TajoMenus.CreateTajoSystem[LIST[
Comment
[title: "Title", options: LIST[
[rope: "Menu entry", action: REF],
[rope: "Menu entry", action: REF],
[rope: "Menu entry", action: REF],
]],
SELECT
[title: "SELECT", options: LIST[
[rope: "by Rectangle", action: selectRectOperation],
[rope: "by Hue Match", action: selectMatchColorOperation],
[rope: "by Color Brush", action: selectColorBrushOperation],
[rope: "by Edge Brush", action: edgeBrushOperation],
[rope: "by Filling", action: selectContourOperation],
[rope: "by Brush", action: selectBrushOperation],
[rope: "by Pixel", action: selectPixelOperation],
[rope: "Select all", action: SelectAllProc],
[rope: "Clear all", action: ClearAllProc]
]],
EDIT
[title: "EDIT", options: LIST[
[rope: "Apply", action: ApplyProc],
[rope: "Save", action: SaveProc],
[rope: "Save Mask", action: SaveMaskProc],
[rope: "Load Mask", action: LoadMaskProc],
[rope: "Save Log", action: SaveLogProc],
[rope: "Recover Log", action: LoadLogProc]
]],
DESTINATION
[title: "DESTINATION", options: LIST[
[rope: "to Foreground", action: ToForegroundProc],
[rope: "to Background", action: ToBackgroundProc],
[rope: "to Opposite", action: ToOtherProc]
]],
OPTIONS
[title: "OPTIONS", options: LIST[
[rope: "Undo", action: UndoProc],
[rope: "White Cursor", action: MakeCursorWhiteProc],
[rope: "Black Cursor", action: MakeCursorBlackProc]
]]
]];
};
ClearAllProc: REF TIPCmdProc ← NEW[TIPCmdProc ← ClearAll];
ClearAll: TIPCmdProc ~ {
rect: Rectangle ~ [0, 0, data.sa.sSize, data.sa.fSize];
SelectRegions.FillRectangle[sm: data.sm, rect: rect, op: InvertIfDouble[destination, TRUE]]; --i.e. invert the destination
PaintRectangle[self, data, rect];
data.extent ← SelectRegions.nullRectangle;
};
SelectAllProc: REF TIPCmdProc ← NEW[TIPCmdProc ← SelectAll];
SelectAll: TIPCmdProc ~ {
rect: Rectangle ~ [0, 0, data.sa.sSize, data.sa.fSize];
SelectRegions.FillRectangle[sm: data.sm, rect: SelectRegions.nullRectangle, op: destination];
PaintRectangle[self, data, rect];
data.extent ← [sMin: 0, fMin: 0, sMax: data.sm.sSize-1, fMax: data.sm.fSize-1];
};
SaveProc: REF TIPCmdProc ← NEW[TIPCmdProc ← Save];
Save: TIPCmdProc ~ {
Action: PROC ~ {
SampleArrays.Save[data.sa];
data.changeLog ← NIL;  --Erase the change log
};
WaitFor[Action, self];
};
SaveLogProc: REF TIPCmdProc ← NEW[TIPCmdProc ← SaveLog];
SaveLog: TIPCmdProc ~ {
Action: PROC ~ {
fileName: ROPE ~ Rope.Cat[SampleArrays.FileNameRoot[data.sa], ".changeLog"];
fileStream: IO.STREAM ~ FS.StreamOpen[fileName, create];
IO.PutRope[self: fileStream, r: data.changeLog];
IO.Close[self: fileStream];
};
WaitFor[Action, self];
};
LoadLogProc: REF TIPCmdProc ← NEW[TIPCmdProc ← LoadLog];
LoadLog: TIPCmdProc ~ {
Action: PROC ~ {
IndicateFailure: PROC ~ {
[] ← PopUpMenu.RequestSelection[NIL, LIST[Rope.Cat["Can't find file ", fileName, " or it's not an extent file of appropriate dimensions."]], 1];
};
fileName: ROPE ~ Rope.Cat[SampleArrays.FileNameRoot[data.sa], ".changeLog"];
fileStream: IO.STREAM ~ FS.StreamOpen[fileName];
ropeStream: IO.STREAM ~ IO.ROS[];
DO
IO.PutChar[self: ropeStream, char: IO.GetChar[self: fileStream ! IO.EndOfStream => EXIT]];
ENDLOOP;
data.changeLog ← IO.RopeFromROS[self: ropeStream];
ColorEditOps.ApplyLog[data.changeLog, data.sa];
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE];
};
WaitFor[Action, self];
};
SaveMaskProc: REF TIPCmdProc ← NEW[TIPCmdProc ← SaveMask];
SaveMask: TIPCmdProc ~ {
Action: PROC ~ {
fileName: ROPE ~ Rope.Cat[SampleArrays.FileNameRoot[data.sa], "-mask.extent"];
data.extent ← ClipExtent[extent: data.extent, sSize: data.sa.sSize, fSize: data.sa.fSize];
ColorEditOps.SaveExtentOfMask[data.sm, data.extent, fileName];
};
WaitFor[Action, self];
};
LoadMaskProc: REF TIPCmdProc ← NEW[TIPCmdProc ← LoadMask];
LoadMask: TIPCmdProc ~ {
Action: PROC ~ {
IndicateFailure: PROC ~ {
[] ← PopUpMenu.RequestSelection[NIL, LIST[Rope.Cat["Can't find file ", fileName, " or it's not an extent file of appropriate dimensions."]], 1];
};
fileName: ROPE ~ Rope.Cat[SampleArrays.FileNameRoot[data.sa], "-mask.extent"];
data.extent ← ColorEditOps.ReadExtentOfMask[data.sm, fileName ! FS.Error => {IndicateFailure[]; CONTINUE}];
PaintRectangle[self, data, [sMin: 0, sMax: data.sm.sSize-1, fMin: 0, fMax: data.sm.fSize-1]];
};
WaitFor[Action, self];
};
ApplyProc: REF TIPCmdProc ← NEW[TIPCmdProc ← Apply];
Apply: TIPCmdProc ~ {
Action: PROC ~ {
trcs: SelectRegions.TRCSequence ~ NEW[SelectRegions.TRCSequenceRep[data.sa.n]];
trc: REF TRC ~ AdjustColorStd.CurrentTRC[];
FOR i: NAT IN [0..trcs.n) DO
trcs[i] ← trc;
ENDLOOP;
data.extent ← ClipExtent[extent: data.extent, sSize: data.sa.sSize, fSize: data.sa.fSize];
SelectRegions.ApplyTRC[data.sm, data.sa, trcs, data.extent];
data.changeLog ← data.changeLog.Cat[ColorEditOps.LogApply[trc, data.sm, data.extent]];
data.extent ← SelectRegions.nullRectangle;
AdjustColorStd.NormalizeAdjustments[];
SampleMapOps.Clear[sampleMap: data.sm];
ViewerOps.PaintViewer[viewer: self, hint: client, clearClient: FALSE];
};
WaitFor[Action, self];
};
MakeCursorWhiteProc: REF TIPCmdProc ← NEW[TIPCmdProc ← MakeCursorWhite];
MakeCursorWhite: TIPCmdProc ~ {
[] ← Terminal.SetColorCursorPresentation[term, onesAreWhite];
};
MakeCursorBlackProc: REF TIPCmdProc ← NEW[TIPCmdProc ← MakeCursorBlack];
MakeCursorBlack: TIPCmdProc ~ {
[] ← Terminal.SetColorCursorPresentation[term, onesAreBlack];
};
ToForegroundProc: REF TIPCmdProc ← NEW[TIPCmdProc ← ToForeground];
ToForeground: TIPCmdProc ~ {
destination ← on;
};
ToBackgroundProc: REF TIPCmdProc ← NEW[TIPCmdProc ← ToBackground];
ToBackground: TIPCmdProc ~ {
destination ← off;
};
ToOtherProc: REF TIPCmdProc ← NEW[TIPCmdProc ← ToOther];
ToOther: TIPCmdProc ~ {
destination ← flip;
};
Extents
ExtendExtent: PROC [extent, plus: Rectangle] RETURNS [result: Rectangle] ~ {
IF extent=SelectRegions.nullRectangle THEN RETURN [plus];
RETURN [[
sMin: MIN[extent.sMin, plus.sMin],
fMin: MIN[extent.fMin, plus.fMin],
sMax: MAX[extent.sMax, plus.sMax],
fMax: MAX[extent.fMax, plus.fMax]
]]
};
DeextendExtent: PROC [extent, minus: Rectangle] RETURNS [result: Rectangle] ~ {
result ← extent;
IF result=SelectRegions.nullRectangle THEN RETURN;
SELECT TRUE FROM
minus.fMin>extent.fMin => IF minus.sMax~<extent.sMax AND minus.fMax~<extent.fMax AND minus.sMin~>extent.sMin THEN result.fMax ← minus.fMin;
minus.sMin>extent.sMin => IF minus.sMax~<extent.sMax AND minus.fMax~<extent.fMax THEN result.sMax ← minus.sMin;
minus.fMax<extent.fMax => IF minus.sMax~<extent.sMax THEN result.fMin ← minus.fMax;
minus.sMax<extent.sMax => result.sMin ← minus.sMax;
ENDCASE => result ← SelectRegions.nullRectangle;
};
ClipExtent: PROC [extent: Rectangle, sSize, fSize: CARDINAL] RETURNS [clipped: Rectangle] ~ {
clipped ← extent;
clipped.sMax ← MIN[clipped.sMax, sSize-1];
clipped.fMax ← MIN[clipped.fMax, fSize-1];
};
Other procedures
WaitFor: PROC [action: PROC, viewer: Viewer, cursorInViewerNow: BOOLTRUE] ~ {
cursor: Cursors.CursorType ~ viewer.class.cursor;
viewer.class.cursor ← hourGlass;
IF cursorInViewerNow THEN Cursors.SetCursor[hourGlass];
action[];
viewer.class.cursor ← cursor;
IF cursorInViewerNow THEN Cursors.SetCursor[cursor];
};
Samples: PROC [m: Transformation, cx, cy: INTEGER] RETURNS [x, y: CARDINAL] ~ {
Given user tip coords, tells which location in the pixel map will be sampled.
RETURN [x: Real.RoundC[m.a*cx + m.c], y: Real.RoundC[m.e*cy + m.f]];
};
SamplesRectangle: PROC [m: Transformation, cx1, cy1, cx2, cy2: INTEGER] RETURNS [r: Rectangle] ~ {
temp: CARDINAL;
[r.fMin, r.sMin] ← Samples[m, cx1, cy1];
[r.fMax, r.sMax] ← Samples[m, cx2, cy2];
IF r.sMin>r.sMax THEN {temp ← r.sMin; r.sMin ← r.sMax; r.sMax ← temp};
IF r.fMin>r.fMax THEN {temp ← r.fMin; r.fMin ← r.fMax; r.fMax ← temp};
};
SampledBy: PROC [m: Transformation, x, y: CARDINAL] RETURNS [cxlo, cylo, cxhi, cyhi: INTEGER] ~ {
x = Round[m.a*cxlo + m.c], and x = Round[m.a*(cxhi) + m.c], with cxlo being as small, and cxhi as large, as possible. Same, obviously, for y. Round[x] = RealOps.RoundC[x+0.5, [round: rm]], to be precise.
Which is the same as:
x IN [m.a*cx + m.c - 0.5, m.a*cx + m.c + 0.5)
Which is the same as:
x e m.a*cx + m.c - 0.5
x < m.a*cx + m.c + 0.5
Which is the same as (assuming m.a>0):
cx d (x + 0.5 - m.c)/m.a
cx > (x - 0.5 - m.c)/m.a
cxlo ← RealOps.RoundI[a: (x-0.5-m.c)/m.a, m: [round: rp]];
cxhi ← RealOps.RoundI[a: (x+0.5-m.c)/m.a, m: [round: rm]];
cylo ← RealOps.RoundI[a: (y-0.5-m.f)/m.e, m: [round: rp]];
cyhi ← RealOps.RoundI[a: (y+0.5-m.f)/m.e, m: [round: rm]];
};
SampledByRectangle: PROC [m: Transformation, rect: Rectangle, offsetX, offsetY: CARDINAL] RETURNS [device: DeviceRectangle] ~ {
cxlo1, cylo1, cxhi1, cyhi1, cxlo2, cylo2, cxhi2, cyhi2: INTEGER;
minx, miny: INTEGER;
[cxlo1, cylo1, cxhi1, cyhi1] ← SampledBy[m, rect.fMin, rect.sMin];
[cxlo2, cylo2, cxhi2, cyhi2] ← SampledBy[m, rect.fMax, rect.sMax];
minx ← MIN[cxlo1, cxlo2];
miny ← MIN[cylo1, cylo2];
device ← [fMin: minx+offsetX, fSize: MAX[cxhi1, cxhi2]-minx, sMin: miny+offsetY, sSize: MAX[cyhi1, cyhi2]-miny];
};
PaintRectangle: PROC [self: ColorEditViewer, data: ColorEditViewerData, r: Rectangle] ~ {
screen: SampleArrayDisplay.Screen;
bounds, region: DeviceRectangle;
[screen, bounds] ← SampleArrayDisplay.ViewerInformation[self];
region ← SampledByRectangle[data.mInverse, r, bounds.fMin, bounds.sMin];
region ← [sMin: region.sMin-1, fMin: region.fMin-1, sSize: region.sSize+2, fSize: region.fSize+2]; --Be sloppy, but make sure we get it!
SampleArrayDisplay.Display[screen: screen, rect: bounds, sa: NIL, sm: data.sm, slow: data.slow, fast: data.fast, picRect: AdjustRect[data.picture, bounds], onlyPaintIn: region];
};
Show: Commander.CommandProc ~ {
Creates an ColorEditViewer for each file listed
files: LIST OF ROPE ← CommandTool.ParseToList[cmd].list;
column: ViewerClasses.Column ← IF ColorDisplay.GetColorDisplayStatus[].on THEN color ELSE left;
FOR each: LIST OF ROPE ← files, each.rest UNTIL each=NIL DO
[] ← Create[[name: each.first, iconic: TRUE, column: column]];
ENDLOOP;
};
blink: ATOMNIL;
Init: PROC ~ {
aisViewerClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: ColorEditViewersPaint,
tipTable: TIPUser.InstantiateNewTIPTable["ColorEdit.tip"],
notify: Notify,
scroll: AISVScroll,
hscroll: AISHScroll,
topDownCoordSys: TRUE,
cursor: matchColorCursor
]];
Register the new viewer class
ViewerOps.RegisterViewerClass[$ColorEditViewer, aisViewerClass];
InstallOperations[];  --Sets up left mouse-button operations
InstallMenus[];  --Sets up default menu system
Commander.Register[key: "ColorEdit", proc: Show, doc: "ColorEdit fileName — open an AIS viewer"];
};
Init[];
END.