SampledImageTransformImpl.mesa
Copyright (C) 1983, Xerox Corporation. All rights reserved.
Michael Plass, December 18, 1984 10:00:17 am PST
DIRECTORY Atom, InputFocus, ImagerBridge, TIPUser, ViewerClasses, ViewerOps, Process, MessageWindow, Menus, Imager, ImagerAISUtil, Rope, IO, FS, Commander, RuntimeError, Basics, Complex, ImagerPixelMaps, ImagerPixelRow, ImagerBasic, ImagerTransform, ViewRec, GalleryOps, Random, AIS, RealFns;
SampledImageTransformImpl: CEDAR MONITOR
IMPORTS Atom, InputFocus, ImagerBridge, TIPUser, ViewerOps, Process, MessageWindow, Menus, Imager, ImagerAISUtil, Rope, IO, FS, Commander, RuntimeError, Basics, Complex, ImagerPixelMaps, ImagerPixelRow, ImagerTransform, ViewRec, GalleryOps, AIS, Random, RealFns
~ BEGIN
Pair: TYPE ~ Imager.Pair;
ROPE: TYPE ~ Rope.ROPE;
Viewer: TYPE ~ ViewerClasses.Viewer;
PixelMap: TYPE ~ ImagerPixelMaps.PixelMap;
Transformation: TYPE ~ Imager.Transformation;
Degrees: TYPE ~ REAL;
FactoredTransformation: TYPE ~ RECORD [
preRotate: Degrees,
scale2: Pair,
postRotate: Degrees,
translate: Pair
];
Represents the transformation
Rotate[preRotate]
.Concat[Scale2[scale2.x,scale2.y]]
.Concat[Rotate[postRotate]]
.Concat[Translate[translate.x,translate.y]]
NumericalInstability: SIGNAL [real: REAL] ~ CODE;
maxRelError: REAL ← 1.0e-5;
RestrictAbsTo: PROC [limit: REAL, real: REAL] RETURNS [REAL] ~ {
Like a MOD, but almost symmetric around zero.
limit ← ABS[limit];
WHILE real < -limit DO real ← real + 2*limit ENDLOOP;
WHILE real >= limit DO real ← real - 2*limit ENDLOOP;
RETURN [real]
};
TestAnalyze: PROC [pre, sx, sy, post, tx, ty: REAL] RETURNS [Transformation] ~ {
RETURN [Imager.Rotate[pre].Concat[Imager.Scale2[sx,sy]].Concat[Imager.Rotate[post]].Concat[Imager.Translate[tx, ty]]]
};
Analyze: PROC [transformation: Transformation] RETURNS [FactoredTransformation] ~ {
t: ImagerTransform.TransformationRec ← transformation.Contents;
theta: Degrees ~ RestrictAbsTo[45,
0.5*RealFns.ArcTanDeg[2*(t.a*t.b+t.d*t.e), (t.a*t.a-t.b*t.b+t.d*t.d-t.e*t.e)]
];
cosTheta: REAL ~ RealFns.CosDeg[theta];
sinTheta: REAL ~ RealFns.SinDeg[theta];
aa: REAL ~ cosTheta*t.a + sinTheta*t.b;
bb: REAL ~ -sinTheta*t.a + cosTheta*t.b;
dd: REAL ~ cosTheta*t.d + sinTheta*t.e;
ee: REAL ~ -sinTheta*t.d + cosTheta*t.e;
phi: Degrees ~ RestrictAbsTo[90, IF ABS[aa] > ABS[ee] THEN RealFns.ArcTanDeg[-dd, aa] ELSE RealFns.ArcTanDeg[bb, ee]];
cosPhi: REAL ~ RealFns.CosDeg[phi];
sinPhi: REAL ~ RealFns.SinDeg[phi];
a: REAL ~ aa*cosPhi - dd*sinPhi;
b: REAL ~ bb*cosPhi - ee*sinPhi;
d: REAL ~ aa*sinPhi + dd*cosPhi;
e: REAL ~ bb*sinPhi + ee*cosPhi;
mag: REAL ~ ABS[a]+ABS[d];
IF ABS[b]+ABS[d] > maxRelError*mag THEN SIGNAL NumericalInstability[ABS[b]+ABS[d]];
RETURN [[preRotate: -theta, scale2: [a, e], postRotate: -phi, translate: [t.c, t.f]]]
};
tinyDegrees: REAL ← 8.742907e-4;
Chosen to make less than 1/2 pixel difference on a surface 32767 pixels wide
RopeFromTransformation: PROC [transformation: Transformation] RETURNS [ROPE] ~ {
nComp: NAT ← 0;
f: FactoredTransformation ← Analyze[transformation];
s: IO.STREAMIO.ROS[];
IF ABS[f.preRotate] > tinyDegrees THEN {
nComp ← nComp + 1;
s.Put[IO.real[f.preRotate], IO.rope[" ROTATE "]];
};
IF f.scale2.x#1.0 OR f.scale2.y#1.0 THEN {
nComp ← nComp + 1;
s.Put[IO.real[f.scale2.x], IO.char[' ]];
IF f.scale2.y = f.scale2.x THEN s.PutRope[" SCALE "]
ELSE s.Put[IO.real[f.scale2.y], IO.rope[" SCALE2 "]];
};
IF ABS[f.postRotate] > tinyDegrees THEN {
nComp ← nComp + 1;
s.Put[IO.real[f.postRotate], IO.rope[" ROTATE "]];
};
IF f.translate.x # 0.0 OR f.translate.y # 0.0 THEN {
nComp ← nComp + 1;
s.Put[IO.real[f.translate.x], IO.char[' ], IO.real[f.translate.y]];
s.PutRope[" TRANSLATE "];
};
WHILE nComp > 1 DO s.PutRope["CONCAT "]; nComp ← nComp-1 ENDLOOP;
IF nComp = 0 THEN RETURN ["1.0 SCALE "] ELSE RETURN [IO.RopeFromROS[s]];
};
Sqr: PROC [real: REAL] RETURNS [REAL] ~ {RETURN [real*real]};
ViewRef: TYPE ~ REF ViewRep;
ViewRep: TYPE ~ RECORD [
outputName: ROPE,
height: NAT ← 100,
width: NAT ← 200,
transRope: ROPENIL
];
MoveMode: TYPE ~ {isoRectangle, translate, scaleAndRotate};
Tool: TYPE = REF ToolRec;
ToolRec: TYPE = RECORD [
actionQueue: LIST OF REF,
actionQueueEmpty: CONDITION,
image: Imager.Color,
lineColor: Imager.Color,
context: Imager.Context,
moveMode: MoveMode,
currentPoint: [0..4) ← 0,
user: ViewRef,
gallery: GalleryOps.Gallery ← NIL,
recordViewer: ViewRec.RecordViewer,
p: ARRAY [0..4) OF Pair,
savep: ARRAY [0..4) OF Pair
];
DisplayTrans: PROC [tool: Tool] = {
sampledColor: ImagerBasic.SampledColor ← NARROW[tool.image];
IF sampledColor # NIL THEN {
ENABLE RuntimeError.UNCAUGHT => GOTO Oops;
TXV: Transformation ~ sampledColor.m;
TUV: Transformation ~ Imager.MakeT[
tool.p[1].x-tool.p[0].x, tool.p[2].x-tool.p[0].x, tool.p[0].x,
tool.p[1].y-tool.p[0].y, tool.p[2].y-tool.p[0].y, tool.p[0].y
];
TVU: Transformation ~ Imager.Invert[TUV];
TUY: Transformation ~ Imager.Scale2[tool.user.width, tool.user.height];
tool.user.transRope ← RopeFromTransformation[TXV.Concat[TVU].Concat[TUY]];
EXITS Oops => {tool.user.transRope ← NIL};
};
};
InitViewer: PROC [self: Viewer] = {
tool: Tool ← NEW[ToolRec];
tool.context ← Imager.Create[$LFDisplay];
tool.user ← NEW[ViewRep];
tool.user.outputName ← FS.ExpandName["Samples.ais"].fullFName;
tool.recordViewer ← ViewRec.ViewRef[agg: tool.user, viewerInit: [name: self.file.Concat[" Parameters"]]];
tool.p ← tool.savep ← [[0,0], [tool.user.width, 0], [0, tool.user.height], [tool.user.width, tool.user.height]];
tool.moveMode ← translate;
tool.lineColor ← Imager.black;
self.column ← left;
self.data ← tool;
IF self.file.Length > 0 THEN {
tool.image ← ImagerAISUtil.AISToColor[self.file];
};
};
DrawLines: PROC [viewer: Viewer] = {
tool: Tool ← NARROW[viewer.data];
Add: PROC [a, b: Imager.Pair] RETURNS [Imager.Pair] ~ {RETURN [[a.x+b.x, a.y+b.y]]};
tool.context.SetStrokeWidth[0.2];
tool.context.MaskStrokeClosed[Imager.MoveTo[tool.p[0]].LineTo[tool.p[1]].LineTo[tool.p[3]].LineTo[tool.p[2]]];
};
PaintProc: ViewerClasses.PaintProc
PROC [self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]
= {
tool: Tool ← NARROW[self.data];
ImagerBridge.SetViewFromGraphicsContext[tool.context, context];
tool.context.state.T ← Imager.Scale[1];
SELECT whatChanged FROM
NIL => Notify[self, LIST[$Refresh]];
$PaintImage => {
tool.context.SetColor[IF tool.image # NIL THEN tool.image ELSE Imager.white];
tool.context.MaskRectangle[0, 0, self.cw, self.ch];
tool.context.SetColor[tool.lineColor];
DrawLines[self];
};
$PutUpLines => {
tool.context.SetColor[tool.lineColor];
DrawLines[self];
};
$TakeDownLines => {
tool.context.SetColor[IF tool.image # NIL THEN tool.image ELSE Imager.white];
DrawLines[self];
};
ENDCASE => ERROR;
}; 
ActionWithPoint: TYPE = REF ActionWithPointRep;
ActionWithPointRep: TYPE = RECORD [
atom: ATOM,
point: Pair
];
nullPoint: Pair = [-999999, 999999];
Notify: ENTRY ViewerClasses.NotifyProc = {
ENABLE {UNWIND => NULL; RuntimeError.UNCAUGHT => {MessageWindow.Append["SampledImageTransform UNCAUGHT ERROR in Notify", TRUE]; MessageWindow.Blink[]; GOTO Quit}};
tool: Tool ← NARROW[self.data];
viewer: Viewer ~ self;
GrabInputFocus: PROC ~ {
IF InputFocus.GetInputFocus[].owner # viewer THEN InputFocus.SetInputFocus[viewer];
};
Unknown: PROC ~ {
stream: IO.STREAMIO.ROS[];
stream.PutRope["Unknown input: "];
stream.Put[IO.refAny[input]];
MessageWindow.Append[IO.RopeFromROS[stream], TRUE];
stream.Close[];
};
ActionKind: TYPE ~ {Ordinary, Inverse, Idempotent};
Queue: PROC [action: REF, inverse: REF, kind: ActionKind ← $Ordinary, point: Pair ← nullPoint] ~ {
LastAction: PROC RETURNS [REF] ~ {
RETURN [WITH ultimate.first SELECT FROM
actionWithPoint: ActionWithPoint => actionWithPoint.atom,
ENDCASE => ultimate.first
]
};
penultimate: LIST OF REFNIL;
ultimate: LIST OF REF ← tool.actionQueue;
IF ultimate = NIL THEN TRUSTED {
ultimate ← tool.actionQueue ← LIST[NIL];
List head is present iff DispatchProcess is alive.
Process.Detach[FORK DispatchProcess[viewer]];
};
FOR q: LIST OF REF ← tool.actionQueue, q.rest UNTIL q = NIL DO
penultimate ← ultimate;
ultimate ← q;
ENDLOOP;
SELECT kind FROM
$Ordinary => NULL;
$Inverse => IF LastAction[] = inverse THEN {penultimate.rest ← NIL; RETURN};
$Idempotent => IF LastAction[] = action THEN {
WITH ultimate.first SELECT FROM
actionWithPoint: ActionWithPoint => actionWithPoint.point ← point;
ENDCASE => NULL;
RETURN
};
ENDCASE => ERROR;
ultimate.rest ← LIST[
IF point = nullPoint THEN action
ELSE NEW[ActionWithPointRep ← [NARROW[action], point]]
];
};
QueuePaintAll: PROC ~ {
q: LIST OF REF ← tool.actionQueue;
IF q # NIL THEN {
WHILE q.rest#NIL DO
SELECT q.rest.first FROM
$PaintImage, $PutUpLines, $TakeDownLines => q.rest ← q.rest.rest;
ENDCASE => q ← q.rest;
ENDLOOP;
};
Queue[$PaintImage, NIL, $Ordinary];
Queue[$PutUpLines, NIL, $Ordinary];
};
WITH input.first SELECT FROM
atom: ATOM => {
SELECT atom FROM
$Refresh => QueuePaintAll[];
$Undo => {
Queue[$TakeDownLines, $PutUpLines, $Inverse];
Queue[$Undo, $Undo, $Inverse];
Queue[$PutUpLines, $TakeDownLines, $Inverse];
};
$IsoMode, $TranslateMode, $ScaleRotateMode => Queue[atom, NIL, $Ordinary];
$InvertLineColor => {
Queue[$InvertLineColor, $InvertLineColor, $Inverse];
Queue[$PutUpLines, NIL, $Idempotent];
};
$Save => {
Queue[$Save, NIL, $Idempotent];
};
$Resize => {
GrabInputFocus[];
Queue[$TakeDownLines, $PutUpLines, $Inverse];
Queue[$Resize, NIL, $Idempotent];
Queue[$PutUpLines, $TakeDownLines, $Inverse];
};
$Bigger, $Smaller => {
GrabInputFocus[];
Queue[atom, NIL, $Ordinary];
QueuePaintAll[];
};
$GetGallery => {
GrabInputFocus[];
Queue[atom, NIL, $Idempotent];
QueuePaintAll[];
};
ENDCASE => {
Unknown[];
};
};
mousePlace: TIPUser.TIPScreenCoords => {
point: Pair ← [mousePlace.mouseX, mousePlace.mouseY];
GrabInputFocus[];
SELECT input.rest.first FROM
$GrabPt => {
Queue[$GrabNearestPoint, NIL, $Ordinary, point];
Queue[$TakeDownLines, $PutUpLines, $Inverse];
Queue[$MovePoint, NIL, $Idempotent, point];
Queue[$PutUpLines, $TakeDownLines, $Inverse];
};
$MovePt => {
Queue[$TakeDownLines, $PutUpLines, $Inverse];
Queue[$MovePoint, NIL, $Idempotent, point];
Queue[$PutUpLines, $TakeDownLines, $Inverse];
};
ENDCASE => Unknown[];
};
ENDCASE => Unknown[];
EXITS Quit => NULL;
};
Dequeue: ENTRY PROC [tool: Tool] RETURNS [ref: REF] ~ {
ENABLE UNWIND => NULL;
IF tool.actionQueue.rest = NIL THEN {
tool.actionQueue ← NIL;
Remove list head so Notify knows that DispatchProcess need restarting
ref ← NIL;
Returning NIL will cause this DispatchProcess to go away.
NOTIFY tool.actionQueueEmpty;
In case an external client was waiting for access to the outline.
}
ELSE {
ref ← tool.actionQueue.rest.first;
tool.actionQueue.rest ← tool.actionQueue.rest.rest;
};
};
StorePixelMap: PROC [aisFileName: ROPE, source: ImagerPixelMaps.PixelMap, bitmap: BOOLEANTRUE, comment: ROPE ← NIL] ~ TRUSTED {
output: AIS.FRef ← AIS.CreateFile[name: aisFileName, raster: NEW[AIS.RasterPart ← [
scanCount: source.sSize,
scanLength: source.fSize,
scanMode: rd,
bitsPerPixel: IF source.refRep.lgBitsPerPixel = 0 AND bitmap THEN 0 ELSE Basics.BITSHIFT[1, source.refRep.lgBitsPerPixel],
linesPerBlock: -1,
paddingPerBlock: 65535
]]];
outputWindow: AIS.WRef ← AIS.OpenWindow[output];
lineMap: ImagerPixelMaps.PixelMap ← ImagerPixelMaps.Create[source.refRep.lgBitsPerPixel, [source.sOrigin+source.sMin, source.fOrigin+source.fMin, 1, source.fSize]];
lineBufferDesc: AIS.Buffer ← [length: lineMap.refRep.words, addr: lineMap.refRep.pointer];
AIS.WriteComment[output, comment];
FOR i: NAT IN [0..source.sSize) DO
lineMap.Clear;
lineMap.Transfer[source];
lineMap.sOrigin ← lineMap.sOrigin + 1;
AIS.UnsafeWriteLine[outputWindow, lineBufferDesc, i];
ENDLOOP;
AIS.CloseFile[output];
};
DispatchProcess: PROC [viewer: Viewer] ~ {
tool: Tool ← NARROW[viewer.data];
ref: REF;
WHILE (ref ← Dequeue[tool]) # NIL DO
action: ATOM ← $NothingMoreToDo;
point: Pair;
WITH ref SELECT FROM
atom: ATOM => action ← atom;
actionWithPoint: ActionWithPoint => {
action ← actionWithPoint.atom;
point ← actionWithPoint.point;
};
ENDCASE => ERROR;
SELECT action FROM
$NothingMoreToDo => NULL;
$GrabNearestPoint => {
d: REAL ← 9999999;
b: INTEGER ← -1;
FOR i: [0..4) IN [0..4) DO
p: Pair ← tool.p[i];
dd: REAL ← Sqr[p.x-point.x]+Sqr[p.y-point.y];
IF dd < d THEN {d ← dd; b ← i};
ENDLOOP;
tool.currentPoint ← b;
tool.savep ← tool.p;
};
$Undo => {
t: ARRAY [0..4) OF Pair ← tool.p;
tool.p ← tool.savep;
tool.savep ← t;
DisplayTrans[tool];
};
$GetGallery => {
selection: GalleryOps.Selection;
selection ← GalleryOps.GetSelection[! RuntimeError.UNCAUGHT => CONTINUE];
IF selection = NIL THEN {
MessageWindow.Append["Unable to get Gallery selection", TRUE];
MessageWindow.Blink[];
}
ELSE {
o: Pair ← tool.p[0];
tool.image ← NEW[ImagerBasic.ColorRep.sampled ← selection.vp.color^];
viewer.name ← Rope.Concat["Sampled Image Transform ", selection.vp.fileName];
viewer.file ← selection.vp.fileName;
tool.savep ← tool.p;
IF selection.sx#0 AND selection.sy#0 THEN {
tool.user.width ← ABS[selection.sx];
tool.user.height ← ABS[selection.sy];
};
tool.p ← [o, [tool.user.width+o.x, o.y], [o.x, tool.user.height+o.y], [tool.user.width+o.x, tool.user.height+o.y]];
DisplayTrans[tool];
tool.gallery ← selection.vp.gallery;
ViewerOps.PaintViewer[viewer, caption];
};
};
$Resize => {
o: Pair ← tool.p[0];
tool.savep ← tool.p;
tool.p ← [o, [tool.user.width+o.x, o.y], [o.x, tool.user.height+o.y], [tool.user.width+o.x, tool.user.height+o.y]];
DisplayTrans[tool];
};
$Bigger => {
sampledColor: ImagerBasic.SampledColor ← NARROW[tool.image];
IF sampledColor # NIL THEN
sampledColor.m ← Imager.Concat[Imager.Scale[2], sampledColor.m];
DisplayTrans[tool];
Process.Pause[Process.MsecToTicks[500]];
};
$Smaller => {
sampledColor: ImagerBasic.SampledColor ← NARROW[tool.image];
IF sampledColor # NIL THEN
sampledColor.m ← Imager.Concat[Imager.Scale[0.5], sampledColor.m];
DisplayTrans[tool];
Process.Pause[Process.MsecToTicks[500]];
};
$Save => IF tool.image # NIL THEN {
sampledColor: ImagerBasic.SampledColor ← NARROW[tool.image];
Coordinate space definitions:
S = source pixelmap coordinates
X = source pixelarray coordinates
V = view coordinates
U = unit-square coordinates
Y = dest pixelarray coordinates
T = dest pixelmap coordinates
TSX: Transformation ~ sampledColor.pa.m;
TXV: Transformation ~ sampledColor.m;
TUV: Transformation ~ Imager.MakeT[
tool.p[1].x-tool.p[0].x, tool.p[2].x-tool.p[0].x, tool.p[0].x,
tool.p[1].y-tool.p[0].y, tool.p[2].y-tool.p[0].y, tool.p[0].y
];
TVU: Transformation ~ Imager.Invert[TUV];
TUY: Transformation ~ Imager.Scale2[tool.user.width, tool.user.height];
TYT: Transformation ~ Imager.MakeT[
0, -1, tool.user.height,
1, 0, 0
];
transformation: Imager.Transformation ← TSX.Concat[TXV].Concat[TVU].Concat[TUY].Concat[TYT];
source: REF PixelMap ← NARROW[sampledColor.pa.data];
dest: PixelMap ← ImagerPixelMaps.Create[source.refRep.lgBitsPerPixel, [0, 0, tool.user.height, tool.user.width]];
ImagerPixelRow.TransferSamples[dest, source^, transformation, TRUE];
StorePixelMap[tool.user.outputName, dest, FALSE, viewer.name];
IF tool.gallery # NIL THEN {
[] ← GalleryOps.Create[gallery: tool.gallery, fileName: tool.user.outputName, x: Random.Choose[0, MAX[INT[600]-tool.user.width, 0]], y: Random.Choose[0, MAX[INT[400]-tool.user.height, 0]], sx: tool.user.width, sy: tool.user.height];
};
};
$InvertLineColor => {
tool.lineColor ← IF tool.lineColor = Imager.black THEN Imager.white ELSE Imager.black;
};
$MovePoint => {
c: NAT ← tool.currentPoint;
SELECT tool.moveMode FROM
isoRectangle => {
opp: Pair ← tool.p[Basics.BITXOR[c, 3]];
tool.p[c] ← point;
tool.p[Basics.BITXOR[c, 1]] ← [opp.x, point.y];
tool.p[Basics.BITXOR[c, 2]] ← [point.x, opp.y];
};
translate => {
delta: Pair ← [point.x-tool.p[c].x, point.y-tool.p[c].y];
FOR i: [0..4) IN [0..4) DO
tool.p[i] ← [tool.p[i].x+delta.x, tool.p[i].y+delta.y];
ENDLOOP;
};
scaleAndRotate => {
opp: [0..4) ← Basics.BITXOR[c, 3];
oldDiag: Complex.Vec ← [tool.p[c].x-tool.p[opp].x, tool.p[c].y-tool.p[opp].y];
newDiag: Complex.Vec ← [point.x-tool.p[opp].x, point.y-tool.p[opp].y];
IF Complex.SqrAbs[oldDiag] > 0 AND Complex.SqrAbs[newDiag] > 0 THEN {
m: Complex.Vec ← Complex.Div[newDiag, oldDiag];
o: Complex.Vec ← [tool.p[opp].x, tool.p[opp].y];
FOR i: [0..4) IN [0..4) DO
a: Complex.Vec ← Complex.Add[Complex.Mul[Complex.Sub[[tool.p[i].x, tool.p[i].y], o], m], o];
tool.p[i] ← [a.x, a.y];
ENDLOOP;
};
};
ENDCASE => NULL;
DisplayTrans[tool];
};
$PaintImage, $PutUpLines, $TakeDownLines => {
ViewerOps.PaintViewer[viewer, client, FALSE, action];
};
$IsoMode => tool.moveMode ← isoRectangle;
$TranslateMode => tool.moveMode ← translate;
$ScaleRotateMode => tool.moveMode ← scaleAndRotate;
ENDCASE => ERROR;
ENDLOOP;
};
MenuAction: Menus.ClickProc = {Notify[NARROW[parent], LIST[clientData]]};
AddMenuItem: PUBLIC PROC [atom: ATOM] = {
name: ROPE ~ Atom.GetPName[atom];
menu: Menus.Menu ← NARROW[viewerClass.menu];
ResetViewerMenu: PROC [v: Viewer] RETURNS [BOOLTRUE] ~ {
IF v.class = viewerClass THEN ViewerOps.SetMenu[v, menu];
};
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: name,
proc: MenuAction,
clientData: atom,
documentation: NIL
]
];
ViewerOps.EnumerateViewers[ResetViewerMenu];
};
CreateMenu: PROC RETURNS [Menus.Menu] = {
menu: Menus.Menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Refresh",
proc: MenuAction,
clientData: $Refresh,
documentation: "Refresh the viewer"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Save",
proc: MenuAction,
clientData: $Save,
documentation: "Save the samples"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Resize",
proc: MenuAction,
clientData: $Resize,
documentation: "Use the new output proportions"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Smaller",
proc: MenuAction,
clientData: $Smaller,
documentation: "Make the sampled image smaller"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Bigger",
proc: MenuAction,
clientData: $Bigger,
documentation: "Make the sampled image bigger"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "GetGallery",
proc: MenuAction,
clientData: $GetGallery,
documentation: "Get the current Gallery selection"
]
];
RETURN [menu];
};
NewTIP: PUBLIC PROC = {
ResetViewerTIP: PROC [v: Viewer] RETURNS [BOOLTRUE] ~ {
IF v.class = viewerClass THEN v.tipTable ← viewerClass.tipTable;
};
viewerClass.tipTable ← TIPUser.InstantiateNewTIPTable["SampledImageTransform.TIP"];
ViewerOps.EnumerateViewers[ResetViewerTIP];
};
viewerClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintProc,
notify: Notify,
init: InitViewer,
save: SaveProc,
menu: CreateMenu[],
tipTable: TIPUser.InstantiateNewTIPTable["SampledImageTransform.TIP"]
]];
Break: PROC [char: CHAR] RETURNS [IO.CharClass] = {
IF char = '← OR char = '; THEN RETURN [break];
IF char = ' OR char = '  OR char = ', OR char = '\n THEN RETURN [sepr];
RETURN [other];
};
GetToken: PROC [stream: IO.STREAM, breakProc: IO.BreakProc] RETURNS [rope: ROPENIL] ~ {
rope ← stream.GetTokenRope[breakProc ! IO.EndOfStream => CONTINUE].token
};
SampledImageTransformCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.RIS[cmd.commandLine];
name: ROPE ← GetToken[stream, Break];
IF Rope.Length[name] # 0 THEN {
name ← FS.ExpandName[name ! FS.Error => {cmd.out.PutRope[error.explanation]; name ← NIL; CONTINUE}].fullFName;
[] ← ViewerOps.CreateViewer[$SampledImageTransform, [name: Rope.Concat["Sampled Image Transform ", name], file: name] ! FS.Error => {cmd.out.PutRope[error.explanation]; CONTINUE}];
}
ELSE
[] ← ViewerOps.CreateViewer[$SampledImageTransform, [name: "Sampled Image Transform [No File]", file: NIL]]
};
ViewerOps.RegisterViewerClass[$SampledImageTransform, viewerClass];
Commander.Register["SampledImageTransform", SampledImageTransformCommand, "Create a viewer to transform a sampled image"];
END.