CtMapCommandsImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Bloomenthal, April 15, 1993 8:21 pm PDT
Heckbert, June 13, 1988 11:43:17 pm PDT
Glassner, November 12, 1990 3:30 pm PST
DIRECTORY Args, CedarProcess, Commander, CommanderOps, Controls, CtBasic, CtDispatch, CtMap, CtViewer, Imager, ImagerColor, ImagerFont, IO, Menus, Process, Random, RawViewers, Real, RealFns, Rope, RuntimeError, TIPUser, ViewerClasses, ViewerOps, ViewersWorldInstance, ViewersWorldTypes;
CtMapCommandsImpl: CEDAR PROGRAM
IMPORTS Args, CedarProcess, CommanderOps, Controls, CtBasic, CtDispatch, CtMap, CtViewer, Imager, ImagerFont, IO, Menus, Process, Random, RawViewers, Real, RealFns, Rope, RuntimeError, TIPUser, ViewerOps, ViewersWorldInstance
~ BEGIN
Arg:     TYPE ~ Args.Arg;
Control:    TYPE ~ Controls.Control;
OuterData:   TYPE ~ Controls.OuterData;
CtProc:    TYPE ~ CtDispatch.CtProc;
Cmap:     TYPE ~ CtMap.Cmap;
RGB:     TYPE ~ ImagerColor.RGB;
ROPE:     TYPE ~ Rope.ROPE;
Using ViewerOps from within a CtProc is ok for all CtProcs registered as type cm.
Editor
CmEditData:    TYPE ~ REF CmEditDataRec;
CmEditDataRec:   TYPE ~ RECORD [
cm, cmSave:      Cmap,
mono, watch:     BOOL ¬ FALSE,
w, n, base, i0, i1, y0, y1:  NAT ¬ 0,
yMul, xScale, yScale:   REAL ¬ 0.0,
x:         ARRAY[0..255] OF NAT ¬ ALL[0],
y:         ARRAY[0..2] OF ARRAY[0..255] OF NAT ¬ ALL[ALL[0]],
bases:        ARRAY[0..2] OF NAT ¬ ALL[0],
viewer:       ViewerClasses.Viewer ¬ NIL
];
CmEditPaint: ViewerClasses.PaintProc ~ {
Action: PROC ~ {
CmEditInit[d];
CmEditDrawCmap[context, d, self.ww, self.wh];
Imager.SetFont[context, ImagerFont.Find["Xerox/TiogaFonts/Helvetica14"]];
IF d.mono
THEN {
Imager.SetXYI[context, self.ww-50, 10+d.bases[2]];
Imager.ShowRope[context, "mono"];
}
ELSE FOR n: NAT IN [0..2] DO
Imager.SetXYI[context, self.ww-50, 10+d.bases[n]];
Imager.ShowRope[context, SELECT n FROM 0=>"red", 1=>"green", ENDCASE=>"blue"];
Imager.MaskRectangleI[context, 0, d.bases[n], self.ww, 1];
ENDLOOP;
};
d: CmEditData ¬ NARROW[self.data];
IF whatChanged = NIL
THEN Imager.DoWithBuffer[context, Action, 0, 0, self.ww, self.wh]
ELSE CmEditUpdate[context, d];
};
CmEditInit: PROC [d: CmEditData] ~ {
x: REAL ¬ 0;
xInc: REAL ¬ d.viewer.cw/255.0;
compH: NAT ¬ d.viewer.ch/3;
d.w ¬ RoundI[xInc];
d.yMul ¬ (IF d.mono THEN d.viewer.ch ELSE compH)/255.0;
d.xScale ¬ IF d.viewer.cw > 1.0 THEN 255.0/(d.viewer.cw-1) ELSE 0.0;
d.yScale ¬ IF d.yMul # 0.0 THEN 1.0/d.yMul ELSE 0.0;
FOR i: NAT IN [0..255] DO d.x[i] ¬ RoundI[x]; x ¬ x+xInc; ENDLOOP;
FOR n: NAT IN [IF d.mono THEN 2 ELSE 0..2] DO
y: INTEGER ¬ d.bases[n] ¬ (2-n)*compH;
FOR i: NAT IN[0..255] DO d.y[n][i] ¬ RoundI[y+d.yMul*d.cm[n][i]]; ENDLOOP;
ENDLOOP;
TRUSTED {IF NOT d.watch THEN Process.Detach[FORK CmEditWatch[d]]};
};
CmEditCompIndex: PROC [d: CmEditData, y: INTEGER] RETURNS [NAT] ~ INLINE {
IF d.mono THEN RETURN[2];
RETURN[SELECT y FROM > d.bases[0] => 0, > d.bases[1] => 1, ENDCASE => 2];
};
CmEditNotify: ViewerClasses.NotifyProc ~ {
d: CmEditData ¬ NARROW[self.data];
mouse: TIPUser.TIPScreenCoords ¬ NARROW[input.first];
SELECT input.rest.first FROM
$leftDown, $rightDown => {
d.n ¬ CmEditCompIndex[d, mouse.mouseY];
d.base ¬ d.bases[d.n];
d.i0 ¬ d.i1 ¬ RoundI[mouse.mouseX*d.xScale];
d.y0 ¬ d.y1 ¬ mouse.mouseY-d.bases[d.n];
};
$leftHeld => {
IF d.n # CmEditCompIndex[d, mouse.mouseY] THEN RETURN; -- disallow comp change
d.i0 ¬ d.i1;
d.y0 ¬ d.y1;
d.i1 ¬ RoundI[mouse.mouseX*d.xScale];
d.y1 ¬ mouse.mouseY-d.base;
};
$rightHeld => {
IF d.n # CmEditCompIndex[d, mouse.mouseY] THEN RETURN; -- disallow comp change
d.i0 ¬ d.i1;
d.y0 ¬ d.y1;
d.i1 ¬ RoundI[mouse.mouseX*d.xScale];
d.y1 ¬ mouse.mouseY-d.base;
};
ENDCASE => RETURN;
ViewerOps.PaintViewer[viewer: self, hint: client, whatChanged: d, clearClient: FALSE];
};
CmEditUpdate: PROC [cxt: Imager.Context, d: CmEditData] ~ {
i0, i1: NAT;
y, dy, imy, imdy: REAL;
IF d.i1 > d.i0
THEN {i0 ¬ d.i0; i1 ¬ d.i1; y ¬ d.yScale*d.y0; imy ¬ d.base+d.y0}
ELSE {i0 ¬ d.i1; i1 ¬ d.i0; y ¬ d.yScale*d.y1; imy ¬ d.base+d.y1};
Imager.SetColor[cxt, Imager.white];
FOR i: NAT IN [i0..i1] DO Imager.MaskRectangleI[cxt, d.x[i], d.y[d.n][i], d.w, 1]; ENDLOOP;
Imager.SetColor[cxt, Imager.black];
dy ¬ IF i0 = i1 THEN 0.0 ELSE d.yScale*(1.0*(d.y1-d.y0))/(d.i1-d.i0);
imdy ¬ d.yMul*dy;
FOR i: NAT IN [i0..i1] DO
IF d.mono
THEN d.cm[0][i] ¬ d.cm[1][i] ¬ d.cm[2][i] ¬ RoundI[y]
ELSE d.cm[d.n][i] ¬ RoundI[y];
CtMap.WriteEntry[i, [d.cm[0][i], d.cm[1][i], d.cm[2][i]]];
Imager.MaskRectangleI[cxt, d.x[i], d.y[d.n][i] ¬ RoundI[imy], d.w, 1];
imy ¬ imy+imdy;
y ¬ y+dy;
ENDLOOP;
};
cmEditUsage: ROPE ~ "Cm Edit [-mono]: interactively edit the colormap.";
CmEdit: PUBLIC CtProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
cm: Cmap ¬ CtMap.Read[];
data: CmEditData ¬ NEW[CmEditDataRec ¬ [cm: cm, cmSave: CtMap.Copy[cm]]];
v: ViewerClasses.Viewer ¬ ViewerOps.CreateViewer[
flavor: $CmEdit,
paint: FALSE,
info: [
data: data,
name: "Color Map Editor",
openHeight: 300+24,
iconic: TRUE,
column: right,
menu: Menus.CreateMenu[],
scrollable: FALSE]];
data.mono ¬ argv.argc = 2 AND Rope.Equal[argv[1], "-mono", FALSE];
Menus.AppendMenuEntry[v.menu, Menus.CreateEntry["Undo", CmEditUndo, data,, FALSE]];
Menus.AppendMenuEntry[v.menu, Menus.CreateEntry["Read", CmEditRead, data,, FALSE]];
data.viewer ¬ v;
ViewerOps.AddProp[v, $CmEdit, data];
ViewerOps.OpenIcon[v];
};
CmEditDrawCmap: PROC [context: Imager.Context, d: CmEditData, w, h: INTEGER] ~ {
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, 0, 0, w, h];
Imager.SetColor[context, Imager.black];
FOR n: NAT IN [IF d.mono THEN 2 ELSE 0..2] DO
b: NAT ¬ d.bases[n];
FOR i: NAT IN [0..255] DO
Imager.MaskRectangleI[context, d.x[i], RoundI[b+d.yMul*d.cm[n][i]], d.w, 1];
ENDLOOP;
ENDLOOP;
};
CmEditNewCmap: PROC [data: CmEditData] ~ {
data.cmSave ¬ CtMap.Copy[data.cm ¬ CtMap.Read[]];
Terminal.WaitForBWVerticalRetrace[InterminalBackdoor.terminal];
ViewerOps.PaintViewer[data.viewer, client, FALSE, NIL];
};
CmEditRead: Menus.ClickProc ~ {CmEditNewCmap[NARROW[clientData]]};
CmEditUndo: Menus.ClickProc ~ {CtMap.Write[NARROW[clientData, CmEditData].cmSave]};
CmEditWatch: PROC [data: CmEditData] ~ {
data.watch ¬ TRUE;
WHILE data.watch DO
CtMap.WaitTilNew[];
CmEditNewCmap[data];
ENDLOOP;
};
CmEditDestroy: ViewerClasses.DestroyProc ~ {NARROW[self.data, CmEditData].watch ¬ FALSE};
Basic Commands
cmSaveUsage: ROPE ~ "Cm Save <color map name>.";
CmSave: CtProc ~ {
IF Args.GetRope[cmd] = NIL THEN RETURN[error: "incorrect arguments"];
CtMap.Save[CtMap.Read[], Args.GetRope[cmd]];
};
cmLoadUsage: ROPE ~ "Cm Load <color map name>.";
CmLoad: CtProc ~ {
IF Args.GetRope[cmd] = NIL THEN RETURN[error: "incorrect arguments"];
IF NOT CtMap.Load[Args.GetRope[cmd]] THEN RETURN[error: "Can't read file."];
};
cmLinearUsage: ROPE ~ "Cm Linear: set the color map to a linear ramp.";
CmLinear: CtProc ~ {CtMap.Mono[]};
cmDitherUsage: ROPE ~ "Cm Dither [-dense] [gamma (default: 1.0)]: dither to span RGB space.";
CmDither: CtProc ~ {
a, b: Arg; [a, b] ¬ Args.ArgsGet[cmd, "[r-dense%b"];
IF b.ok OR RawViewers.GetColorWorld[ViewersWorldInstance.GetWorld[]] = $dense
THEN DoGamma[IF a.ok THEN a.real ELSE 1.0, $Dither]
ELSE { -- from RawViewersImpl.mesa
GCorrect: PROC [c: REAL] RETURNS [b: NAT] ~ {b ¬ Clip[RealFns.Power[c,invGamma]]};
Clip: PROC [r: REAL] RETURNS [s: NAT] ~ {
s ¬ Real.Round[r*255.0];
IF s < 0 THEN s ¬ 0 ELSE IF s > 255 THEN s ¬ 255;
};
SetColor: PROC [i: NAT, r, g, b: REAL] ~ {
cm[0][i] ¬ GCorrect[r];
cm[1][i] ¬ GCorrect[g];
cm[2][i] ¬ GCorrect[b];
};
SetColor2: PROC [i: NAT, r, g, b: REAL] ~ {
SetColor[i, r, g, b];
SetColor[BYTE.LAST-i, 1.0-r, 1.0-g, 1.0-b];
};
invGamma: REAL;
cm: Cmap ¬ CtMap.ObtainCmap[];
invGamma ¬ 1.0/(IF a.ok AND a.real # 0.0 THEN a.real ELSE 2.2);
FOR i: NAT IN [0..3) DO FOR j: NAT IN [0..256) DO cm[i][j] ¬ 0; ENDLOOP; ENDLOOP;
FOR b: NAT IN [0..4) DO
FOR g: NAT IN [0..6) DO
FOR r: NAT IN [0..5) DO
i: NAT ~ (r*6+g)*4+b;
IF i < 60 THEN SetColor2[i, r/4.0, g/5.0, b/3.0];
ENDLOOP;
ENDLOOP;
ENDLOOP;
SetColor2[60, 0.25, 0.25, 0.25]; -- quarter-grey
SetColor2[61, 0.5, 0.5, 0.5];  -- half-grey
SetColor2[62, 0.1, 0.3, 0.85];  -- distinct/blue
CtMap.WriteAndRelease[cm];
};
};
cmGrayUsage: ROPE ~ "Cm Gray [gamma (default = 2.2)]: grayscale image.";
CmGray: CtProc ~ {
a: Arg; [a] ¬ Args.ArgsGet[cmd, "[r"];
DoGamma[IF a.ok THEN a.real ELSE 2.2, $BW];
};
cmGammaUsage: ROPE ~ "Cm Gamma [value] [-bw | -dither]: if no value, interactive.";
Gammaler: Controls.ControlProc ~ {DoGamma[control.value, control.clientUse]};
CmGamma: CtProc ~ {
gamma, bw, dither: Arg;
[gamma, bw, dither] ¬ Args.ArgsGet[cmd, "[r-bw%b-dither%b"];
IF bw.ok AND dither.ok
THEN RETURN[error: "can't be both bw and dither"]
ELSE {
mode: ATOM ¬ SELECT TRUE FROM
bw.ok => $BW,
dither.ok => $Dither,
ENDCASE => IF CtBasic.GetBpp[] = 24 THEN $BW ELSE $Dither;
init: REAL ¬ IF mode = $BW THEN 2.2 ELSE 1.0;
max: REAL ¬ IF mode = $BW THEN 5.0 ELSE 2.0;
name: ROPE ¬ IF mode = $BW THEN "Gamma (BW)" ELSE "Gamma (Dither)";
IF gamma.ok
THEN DoGamma[gamma.real, mode]
ELSE [] ¬ Tool[name, LIST[Controls.NewControl[type: hSlider, max: max, init: init, x: 60, y: 10, w: 200, h: 20, detents: LIST[[init, init]], proc: Gammaler, clientUse: mode]]];
};
};
DoGamma: PROC [gamma: REAL, mode: ATOM] ~ {
SELECT mode FROM
$Dither => {
ditherMap, gammaMap: Cmap;
CtMap.Dither[ditherMap ¬ CtMap.ObtainCmap[]];
CtMap.Gamma[gamma, gammaMap ¬ CtMap.ObtainCmap[]];
FOR i: NAT IN [0..255] DO
FOR j: NAT IN [0..2] DO
gammaMap[j][i] ¬ MIN[255, MAX[0, Real.Round[
REAL[ditherMap[j][i]]*
(IF i = 0 THEN 1.0 ELSE REAL[gammaMap[j][i]]/REAL[i])]]];
ENDLOOP;
ENDLOOP;
CtMap.ReleaseCmap[ditherMap];
CtMap.WriteAndRelease[gammaMap];
};
$BW => CtMap.Gamma[gamma];
ENDCASE;
};
cmDeGammaUsage: ROPE ~ "Cm DeGamma: apply inverse gamma to color map";
CmDeGamma: CtProc ~ {CtMap.DeGamma[]};
cmNegateUsage: ROPE ~ "Cm Negate: flip color map entries about the center.";
CmNegate: CtProc ~ {
cmap: Cmap ¬ CtMap.Read[];
FOR n: NAT IN [0..128) DO
save: CtMap.RGB ¬ CtMap.GetEntry[cmap, n];
CtMap.SetEntry[cmap, n, CtMap.GetEntry[cmap, 255-n]];
CtMap.SetEntry[cmap, 255-n, save];
ENDLOOP;
CtMap.Write[cmap];
};
Explicit Setting
cmColorUsage: ROPE ~ "Cm Color <i r g b>: Set color map entry i.";
CmColor: CtProc ~ {
ENABLE Real.RealException => GOTO BadVal;
R: PROC [a: Arg] RETURNS [i: INT] ~ {i ¬ MAX[0, MIN[255, RoundI[255.0*a.real]]]};
i, r, g, b: Arg;
[i, r, g, b] ¬ Args.ArgsGet[cmd, "%irrr" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
CtMap.WriteEntry[i.int, [R[r], R[g], R[b]]];
EXITS BadVal => RETURN[error: "real must be in [0.0..1.0]"];
};
cmRampUsage: ROPE ~ "Cm Ramp <i0: [0..255]> <r0 g0 b0: [0.0..1.0]> <i1> <r1 g1 b1>.";
CmRamp: CtProc ~ {
ENABLE Real.RealException => GOTO BadVal;
R: PROC [a: Arg] RETURNS [i: INT] ~ {i ¬ MAX[0, MIN[255, RoundI[255.0*a.real]]]};
i0, r0, g0, b0, i1, r1, g1, b1: Arg;
[i0, r0, g0, b0, i1, r1, g1, b1] ¬ Args.ArgsGet[cmd, "%irrrirrr"
! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
CtMap.Ramp[i0.int, i1.int, [R[r0], R[g0], R[b0]], [R[r1], R[g1], R[b1]]];
EXITS BadVal => RETURN[error: "real must be in [0.0..1.0]"];
};
cmSplineUsage: ROPE ~ "Cm Spline i0 r0 g0 b0 i1 r1 g1 b1";
CmSpline: CtProc ~ {
IF Args.NArgs[cmd] # 8
THEN RETURN[error: "bad args"]
ELSE {
SlowInOut: PROC [value0, value1: NAT, t: REAL] RETURNS [REAL] ~ {
t2: REAL ¬ t*t;
t3: REAL ¬ t*t2;
RETURN[value0+(3.0*t2-t3-t3)*(value1-value0)];
};
cmap: CtMap.Cmap ¬ CtMap.Read[];
i0, r0, g0, b0, i1, r1, g1, b1: Arg;
[i0, r0, g0, b0, i1, r1, g1, b1] ¬ Args.ArgsGet[cmd, "%iiiiiiii"
! Args.Error => {error ¬ reason; GOTO Bad}];
IF i1.int <= i0.int THEN RETURN;
FOR i: INT IN [i0.int..i1.int] DO
t: REAL ¬ REAL[i-i0.int]/REAL[i1.int-i0.int];
r: NAT ¬ RoundI[SlowInOut[r0.int, r1.int, t]];
g: NAT ¬ RoundI[SlowInOut[g0.int, g1.int, t]];
b: NAT ¬ RoundI[SlowInOut[b0.int, b1.int, t]];
CtMap.SetEntry[cmap, i, [r, g, b]];
ENDLOOP;
CtMap.Write[cmap];
};
EXITS Bad => NULL;
};
cmTriColorsUsage: ROPE ~ "Cm TriColors <rgb1> <rgb2> <rgb3> [-gamma <gamma>].";
CmTriColors: CtProc ~ {
r1, g1, b1, r2, g2, b2, r3, g3, b3, gammaA: Arg;
[r1, g1, b1, r2, g2, b2, r3, g3, b3, gammaA] ¬
Args.ArgsGet[cmd, "%rrrrrrrrr-gammaA%r" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL
THEN RETURN
ELSE {
ENABLE RuntimeError.BoundsFault => GOTO Bad;
R: PROC [a: Arg] RETURNS [r: REAL] ~ {r ¬ MAX[0.0, MIN[1.0, a.real]]};
Scale: PROC [v: RGB, s: REAL] RETURNS [r: RGB] ~ {r ¬ [s*v.R, s*v.G, s*v.G]};
Interp: PROC [v1, v2: RGB, a: REAL] RETURNS [v: RGB] ~ { -- a=1 => v2
v ¬ [v1.R+a*(v2.R-v1.R), v1.G+a*(v2.G-v1.G), v1.B+a*(v2.B-v1.B)]};
G: PROC [a: REAL] RETURNS [r: REAL] ~ {r ¬ CtMap.ApplyGamma[a, gamma]};
Gam: PROC [v: RGB] RETURNS [r: RGB] ~ {r ¬ [G[v.R], G[v.G], G[v.B]]};
gamma: REAL ¬ IF gammaA.ok THEN gammaA.real ELSE 2.2;
rgb1: RGB ¬ [R[r1], R[g1], R[b1]];
rgb2: RGB ¬ [R[r2], R[g2], R[b2]];
rgb3: RGB ¬ [R[r3], R[g3], R[b3]];
cm: Cmap ¬ CtMap.ObtainCmap[];
FOR i: INTEGER IN [0..255] DO
a: REAL ¬ REAL[IF i < 128 THEN i ELSE i-128]/127.0;
v: RGB ¬ IF i < 128 THEN Gam[Interp[rgb1, rgb2, a]] ELSE Gam[Interp[rgb2, rgb3, a]];
CtMap.SetEntry[cm, i, [RoundI[v.R], RoundI[v.G], RoundI[v.B]]];
ENDLOOP;
CtMap.WriteAndRelease[cm];
EXITS Bad => RETURN[error: "bad argument"];
};
};
Scale/Add/Compose/Interpolate
cmScaleUsage: ROPE ~ "Cm Scale <scale>.";
CmScale: CtProc ~ {
IF NOT Args.ArgReal[cmd].ok THEN RETURN[error: "bad argument"];
CtMap.Scale[CtMap.Read[], Args.ArgReal[cmd].real];
};
cmTriScaleUsage: ROPE ~ "Cm Scale <r, g, b scale>.";
CmTriScale: CtProc ~ {
r, g, b: Arg;
[r, g, b] ¬ Args.ArgsGet[cmd, "%rrr" ! Args.Error => {error ¬ reason; CONTINUE}];
CtMap.TriScale[CtMap.Read[], r.real, g.real, b.real];
};
cmAddUsage: ROPE ~ "Cm Add <offset>.";
CmAdd: CtProc ~ {
IF NOT Args.ArgInt[cmd].ok THEN RETURN[error: "bad argument"];
CtMap.Add[CtMap.Read[], Args.ArgInt[cmd].int];
};
cmInterpUsage: ROPE ~ "Cm Interp <t: REAL, cmap: ROPE>.";
CmInterp: CtProc ~ {
cmLoad: Cmap ¬ CtMap.NewCmap[];
IF NOT Args.ArgReal[cmd].ok OR NOT CtMap.Load[Args.GetRope[cmd, 1], cmLoad]
THEN RETURN[error: "bad arguments"];
CtMap.Interp[Args.ArgReal[cmd].real, CtMap.Read[], cmLoad];
};
cmComposeUsage: ROPE ~ "Cm Compose <cmap1 cmap2>: result ← c2[c1].";
CmCompose: CtProc ~ {
cm1, cm2: Cmap;
ok1: BOOL ¬ CtMap.Load[Args.GetRope[cmd, 0], cm1 ¬ CtMap.NewCmap[]];
ok2: BOOL ¬ CtMap.Load[Args.GetRope[cmd, 1], cm2 ¬ CtMap.NewCmap[]];
IF NOT ok1 OR NOT ok2 THEN RETURN[error: "bad color map name(s)."];
CtMap.Compose[cm1, cm2];
};
Function Creation
cmTentsUsage: ROPE ~ "Cm Tents [nTents].";
TentCycler: ToolProc ~ {
c: Control ¬ o.controls.first;
abs: REAL ¬ ABS[c.value];
FOR ntent: REAL ¬ .5, ntent+c.value WHILE ABS[ntent] < 128 AND NOT o.destroyed DO
IF c.value = 0 THEN Process.Pause[Process.MsecToTicks[100]]; -- no hog
CtMap.Tents[ntent];
ENDLOOP;
IF abs > 0.01 THEN Process.Pause[Process.MsecToTicks[RoundI[1000.0/abs]]];
};
CmTents: CtProc ~ {
IF Args.ArgReal[cmd].ok
THEN CtMap.Tents[Args.ArgReal[cmd].real]
ELSE [] ¬ Tool["Cytent", LIST[Controls.NewControl[name: "Speed", type: hSlider, x: 60, y: 10, w: 200, h: 20, min: -2, max: 2, detents: LIST[[, 0]]]], TentCycler];
};
cmLineUsage: ROPE ~ "Cm Line <nCycles: REAL> <width: NAT>.";
CmLine: CtProc ~ {
RealMod: PROC [a, b: REAL] RETURNS [REAL] ~ {RETURN[a-Real.Floor[a/b]*b]};
ncyclesA: Arg ¬ Args.ArgReal[cmd, 0];
widthA: Arg ¬ Args.ArgInt[cmd, 1];
ncycles: REAL ¬ IF ncyclesA.ok THEN ncyclesA.real ELSE 1;
width: INTEGER ¬ IF widthA.ok THEN widthA.int ELSE 1;
period: REAL ¬ 256.0/ncycles;
cm: Cmap ¬ CtMap.ObtainCmap[];
FOR i: NAT IN [0..256) DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 255; ENDLOOP;
FOR i: NAT IN [0..256) DO
cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ IF RealMod[i, period] < width THEN 0 ELSE 255;
ENDLOOP;
CtMap.WriteAndRelease[cm];
};
cmSquareUsage: ROPE ~ "Cm Square <nCycles>.";
CmSquare: CtProc ~ {
ncycles: REAL ¬ IF Args.ArgReal[cmd].ok THEN Args.ArgReal[cmd].real ELSE 1;
CtMap.Square[ncycles];
};
cmSinUsage: ROPE ~ "Cm Sin <nCycles>.";
CmSin: CtProc ~ {
v: ARRAY[0..2] OF REAL ¬ ALL[0.0];
IF Args.NArgs[cmd] < 1 THEN RETURN[error: "bad arguments"];
FOR i: NAT IN [0..2] DO
a: Arg ¬ Args.ArgReal[cmd, i];
IF NOT a.ok AND i = 0 THEN RETURN[error: "bad arguments"];
v[i] ¬ IF a.ok THEN a.real ELSE v[i-1];
ENDLOOP;
CtMap.Sin[v[0], v[1], v[2]];
};
cmGaussUsage: ROPE ~ "Cm Gauss: put a gaussian in color map.";
CmGauss: CtProc ~ {CtMap.Gauss[]};
cmContoursUsage: ROPE ~ "Cm Contours [-power <real>] [-ncycles <real>] show contours.";
CmContours: CtProc ~ {
nCyclesArg, powerArg: Arg;
[nCyclesArg, powerArg] ¬ Args.ArgsGet[cmd, "-power%r-ncycles%r" !
Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL
THEN RETURN
ELSE {
nCycles: REAL ~ IF nCyclesArg.ok THEN nCyclesArg.real ELSE 4.0;
power: REAL ~ 1.0/(IF powerArg.ok THEN MAX[0.01, powerArg.real] ELSE 6.0);
FOR n: NAT IN [0..255] DO
t: REAL ~ REAL[n]/255.0;
intensity: REAL ¬ .5+.5*RealFns.Sin[nCycles*2.5*3.141592*RealFns.Power[t, power]];
cmapValue: INT ¬ RoundI[(t+(1.0-t)*intensity)*255.0];
CtMap.WriteEntry[n, [cmapValue, cmapValue, cmapValue]];
ENDLOOP;
CtMap.JustWritten[];
};
};
Miscellany
cmOnlyUsage: ROPE ~ "Cm Only <red | green | blue>.";
CmOnly: CtProc ~ {
a: ROPE ¬ Args.GetRope[cmd];
IF a = NIL THEN RETURN[error: "bad arguments"];
SELECT TRUE FROM
a.Equal["red"] => CtMap.PrimaryOnly[r];
a.Equal["green"] => CtMap.PrimaryOnly[g];
a.Equal["blue"] => CtMap.PrimaryOnly[b];
ENDCASE => RETURN[error: "argument one of: red, green, blue."];
};
cmToOthersUsage: ROPE ~ "Cm ToOthers <red | grn | blu> one component to other two.";
CmToOthers: CtProc ~ {
source: ROPE ~ Args.GetRope[cmd];
i0dst, i1dst, isrc: INT ¬ -1;
SELECT TRUE FROM
Rope.Equal[source, "red", FALSE] => {isrc ¬ 0; i0dst ¬ 1; i1dst ¬ 2};
Rope.Equal[source, "grn", FALSE] => {isrc ¬ 1; i0dst ¬ 0; i1dst ¬ 2};
Rope.Equal[source, "blu", FALSE] => {isrc ¬ 2; i0dst ¬ 0; i1dst ¬ 1};
ENDCASE;
IF isrc # -1 THEN {
cmap: CtMap.Cmap ¬ CtMap.Read[];
FOR n: NAT IN [0..255] DO cmap[i0dst][n] ¬ cmap[i1dst][n] ¬ cmap[isrc][n]; ENDLOOP;
CtMap.Write[cmap];
CtMap.JustWritten[];
};
};
cmNBitsUsage: ROPE ~ "Cm NBits [nBits]: if no argument, interactive.";
NBitser: Controls.ControlProc ~ {
cm: Cmap ¬ CtMap.ObtainCmap[];
CtMap.Mono[cm];
CtMap.NBits[cm, RoundI[control.value]];
CtMap.ReleaseCmap[cm];
};
CmNBits: CtProc ~ {
IF Args.ArgInt[cmd].ok
THEN CtMap.NBits[CtMap.Read[], Args.ArgInt[cmd].int]
ELSE [] ¬ Tool["CmNBits", LIST[Controls.NewControl[type: hSlider, x: 60, y: 10, w: 80, h: 20, min: 1, max: 8, init: 8, precision: 0, proc: NBitser]]];
};
cmPrintUsage: ROPE ~ "Cm Print: print the color map contents.";
CmPrint: CtProc ~ {
cm: Cmap ¬ CtMap.Read[];
FOR i: INT IN [0..256) DO
IO.PutFL[cmd.out, "%3g:\t%3g\t%3g\t%3g\n",
LIST[IO.int[i], IO.int[cm[0][i]], IO.int[cm[1][i]], IO.int[cm[2][i]]]];
ENDLOOP;
};
Randomizing
cmScrambleUsage: ROPE ~ "Cm Scramble: randomly interchange entries.";
CmScramble: CtProc ~ {CtMap.Scramble[CtMap.Read[]]};
cmRandomUsage: ROPE ~ "Cm Random: randomize the color map.";
CmRandom: CtProc ~ {CtMap.Rand[]};
cmFlashUsage: ROPE ~ "Cm Flash: interactively randomize color map.";
Flasher: ToolProc ~ {
CtMap.Rand[];
Process.Pause[Process.MsecToTicks[RoundI[1000*o.controls.first.value]]];
};
CmFlash: CtProc ~ {
[] ¬ Tool["Flash", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: 1.0, max: 0.0, init: 0.5]], Flasher];
};
cmSpeckleUsage: ROPE ~ "Cm Speckle: randomly set single entries.";
Speckler: ToolProc ~ {
RandInt: PROC RETURNS [INTEGER] ~ {RETURN[Random.ChooseInt[max: 255]]};
Process.Pause[Process.MsecToTicks[RoundI[100*o.controls.first.value]]];
CtMap.WriteEntry[RandInt[], [RandInt[], RandInt[], RandInt[]]];
};
CmSpeckle: CtProc ~ {
[] ¬ Tool["Speckle", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: 1, max: 0]], Speckler];
};
Cycling
cmCycleUsage: ROPE ~ "Cm Cycle: interactively cycle color map.";
Cycler: ToolProc ~ {
abs: REAL ¬ ABS[o.controls.first.value];
n: INT ¬ MAX[1, RoundI[abs]];
IF abs = 0.0 THEN {Process.Pause[Process.MsecToTicks[1000]]; RETURN};
CtMap.Write[CtMap.Cycle[cm, IF o.controls.first.value > 0 THEN n ELSE -n]];
IF abs > 0.01 THEN Process.Pause[Process.MsecToTicks[RoundI[1000.0/abs]]];
};
CmCycle: CtProc ~ {
[] ¬ Tool["Cycle", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: -25, max: 25, init: 0, detents: LIST[[, 0.0]]]], Cycler];
};
cmShiftUsage: ROPE ~ "Cm Shift [n]: if no argument, interactive.";
Shifter: Controls.ControlProc ~ {
cm: Cmap ¬ CtMap.ObtainCmap[];
CtMap.Write[CtMap.Cycle[CtMap.Read[cm], RoundI[control.valuePrev-control.value]]];
CtMap.ReleaseCmap[cm];
};
CmShift: CtProc ~ {
IF Args.ArgInt[cmd].ok
THEN CtMap.Write[CtMap.Cycle[CtMap.Read[], Args.ArgInt[cmd].int]]
ELSE [] ¬ Tool["Shift", LIST[Controls.NewControl[type: dial, report: FALSE, x: 60, y: 10, w: 80, h: 80, max: 255, init: 0, detents: LIST[[, 0]], proc: Shifter]]];
};
cmThresholdUsage: ROPE ~ "Cm Threshold [n]: in no argument, interactive.";
Thresholder: Controls.ControlProc ~ {CtMap.Write[MakeThreshMap[RoundI[control.value]]]};
MakeThreshMap: PROC [thresh: INT] RETURNS [cm: Cmap] ~ {
cm ¬ CtMap.ObtainCmap[];
thresh ¬ thresh MOD 255;
FOR i: INT IN [0 .. thresh) DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 0; ENDLOOP;
FOR i: INT IN [thresh .. 255] DO cm[0][i] ¬ cm[1][i] ¬ cm[2][i] ¬ 255; ENDLOOP;
};
CmThreshold: CtProc ~ {
IF Args.ArgInt[cmd].ok
THEN CtMap.Write[MakeThreshMap[Args.ArgInt[cmd].int]]
ELSE [] ¬ Tool["Threshold", LIST[Controls.NewControl[type: dial, report: TRUE, x: 60, y: 10, w: 80, h: 80, max: 255, init: 0, detents: LIST[[, 0]], proc: Thresholder]]];
};
Rippling/Snaking
cmRippleUsage: ROPE ~ "Cm Ripple: interactively ripple sine waves.";
Rip: TYPE ~ RECORD [nSins, incr: REAL ¬ 1.0];
Rippler: ToolProc ~ {
nSins: ARRAY [0..2] OF REAL;
c: ARRAY [0..2] OF Control ¬ [o.controls.first, o.controls.rest.first, o.controls.rest.rest.first];
FOR i: NAT IN [0..2] DO
data: REF Rip ¬ NARROW[c[i].clientData];
data.incr ¬ SELECT data.nSins FROM < 0.25 => 1.0, > 25.0 => -1.0, ENDCASE => data.incr;
nSins[i] ¬ data.nSins ¬ data.nSins+data.incr*c[i].value;
ENDLOOP;
CtMap.Sin[nSins[0], nSins[1], nSins[2]];
};
CmRipple: CtProc ~ {
NewC: PROC [r: ROPE, y: NAT] RETURNS [c: Control] ~ {
c ¬ Controls.NewControl[r, hSlider, NEW[Rip],, 0.1, 0.05,, FALSE,,, 60, y, 200, 20, [left, center]];
};
[] ¬ Tool["Ripple", LIST[NewC["red", 10], NewC["grn", 30], NewC["blu", 50]], Rippler];
};
cmSnakeUsage: ROPE ~ "Cm Snake: snake through color space.";
Snaker: ToolProc ~ {
r, g, b: REAL;
[r, g, b] ¬ GetSnake[];
[] ¬ CtMap.Cycle[cm, 1];
CtMap.SetEntry[cm, 1, [RoundI[255*r], RoundI[255*g], RoundI[255*b]]];
CtMap.Write[cm];
Process.Pause[Process.MsecToTicks[RoundI[100*(o.controls.first.max-o.controls.first.value)]]];
};
r, g, b: Section;
Section: TYPE ~ RECORD [count: INTEGER ¬ 0, p, d: REAL ¬ 0.0];
GetSnake: PROC RETURNS [REAL, REAL, REAL] ~ {
NewSection: PROC [p: REAL] RETURNS [s: Section] ~ {
s.p ¬ p;
s.count ¬ Random.ChooseInt[min: 5, max: 30];
s.d ¬ ((1./1024.)*REAL[Random.ChooseInt[min: 1, max: 1024]]-p)/REAL[s.count];
};
IF (r.count ¬ r.count-1) <= 0 THEN r ¬ NewSection[r.p];
IF (g.count ¬ g.count-1) <= 0 THEN g ¬ NewSection[g.p];
IF (b.count ¬ b.count-1) <= 0 THEN b ¬ NewSection[b.p];
r.p ¬ MAX[0.0, MIN[1.0, r.p+r.d]];
g.p ¬ MAX[0.0, MIN[1.0, g.p+g.d]];
b.p ¬ MAX[0.0, MIN[1.0, b.p+b.d]];
RETURN[r.p, g.p, b.p];
};
CmSnake: CtProc ~ {[] ¬ Tool["Snake", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 350, h: 20, taper: exp]], Snaker]};
Blending
cmBlendUsage: ROPE ~ "Cm Blend <LIST of Cmap>: continuously blend.";
rec: TYPE ~ RECORD [data: SEQUENCE num: NAT OF Cmap];
Blender: PROC [o: OuterData, cm: REF rec, ncm: NAT] ~ {
inc: INTEGER ¬ 1;
n0, n1: INTEGER ¬ 0;
WHILE NOT o.destroyed DO
IF (n1 ¬ n0+inc) = -1 OR n1 = ncm THEN {inc ¬ -inc; n1 ¬ n0+inc};
FOR t: REAL ¬ 0, t+o.controls.first.value WHILE t <= 0.5*3.1415926535 DO
IF o.destroyed THEN EXIT;
CtMap.Interp[RealFns.Sin[t], cm.data[n0], cm.data[n1]];
ENDLOOP;
n0 ¬ n1;
ENDLOOP;
Cleanup[NARROW[o.clientData, REF ToolData]];
};
CmBlend: CtProc ~ {
ncm: NAT ¬ 0;
cm: REF rec ¬ NEW[rec[Args.NArgs[cmd]]];
FOR a: NAT IN[0..Args.NArgs[cmd]) DO
Process.CheckForAbort[];
IF CtMap.Load[Args.GetRope[cmd, a], cm.data[ncm] ¬ CtMap.NewCmap[]]
THEN ncm ¬ ncm+1
ELSE IO.PutF1[cmd.out, "Can't read %g\n", IO.rope[Args.GetRope[cmd, a]]];
ENDLOOP;
IF ncm # 0 THEN TRUSTED {
Process.Detach[FORK Blender[Tool["Color Map Blender", LIST[Controls.NewControl[type: hSlider, report: FALSE, x: 60, y: 10, w: 200, h: 20, min: .01, max: 1., init: .1]]], cm, ncm]];
};
};
Support
ToolProc: TYPE ~ PROC [o: OuterData, cm: Cmap];
ToolData: TYPE ~ RECORD [cm: Cmap, reset, looping: BOOL];
Tool: PROC [name: ROPE, controls: Controls.ControlList, proc: ToolProc ¬ NIL]
RETURNS [o: OuterData]
~{
t: REF ToolData ¬ NEW[ToolData ¬ [CtMap.Read[], FALSE, proc # NIL]];
o ¬ Controls.OuterViewer[
name: name,
column: right,
buttons: LIST[
Controls.ClickButton["Reset", Reset, t],
Controls.ClickButton["Reset&Quit", ResetAndQuit, t]],
destroyProc: Destroy,
controls: controls,
clientData: t];
IF proc # NIL THEN TRUSTED {Process.Detach[FORK Looper[o, proc, t]]};
};
Looper: PROC [o: OuterData, proc: ToolProc, t: REF ToolData] ~ {
cm: Cmap ¬ CtMap.Read[];
CedarProcess.SetPriority[background]; -- effective?
WHILE NOT o.destroyed DO proc[o, cm]; ENDLOOP;
Cleanup[t];
};
Destroy: Controls.DestroyProc ~ {
t: REF ToolData ¬ NARROW[clientData];
IF NOT t.looping THEN Cleanup[t]; -- otherwise, do so after loop exits
};
Cleanup: PROC [t: REF ToolData] ~ {
IF t.reset THEN CtMap.Write[t.cm];
CtMap.ReleaseCmap[t.cm];
CtViewer.PutColormap[CtViewer.currentViewer, CtMap.Read[]];
};
Reset: Controls.ClickProc ~ {CtMap.Write[NARROW[clientData, REF ToolData].cm]};
ResetAndQuit: Controls.ClickProc ~ {
NARROW[clientData, REF ToolData].reset ¬ TRUE;
ViewerOps.DestroyViewer[parent.parent];
};
RoundI: PROC [r: REAL] RETURNS [INT] ~ INLINE {RETURN[Real.Round[r]]};
Start Code
ViewerOps.RegisterViewerClass[$CmEdit, NEW[ViewerClasses.ViewerClassRec ¬ [
destroy: CmEditDestroy,
paint: CmEditPaint,
notify: CmEditNotify,
tipTable: TIPUser.InstantiateNewTIPTable["ColorTrix.tip"]]]];
CtDispatch.RegisterCmOp["Basic Commands:", NIL,    NIL];
CtDispatch.RegisterCmOp["Save",    CmSave,   cmSaveUsage];
CtDispatch.RegisterCmOp["Load",    CmLoad,   cmLoadUsage];
CtDispatch.RegisterCmOp["Linear",   CmLinear,  cmLinearUsage];
CtDispatch.RegisterCmOp["Gray",    CmGray,   cmGrayUsage];
CtDispatch.RegisterCmOp["Dither",   CmDither,  cmDitherUsage];
CtDispatch.RegisterCmOp["Gamma",   CmGamma,  cmGammaUsage];
CtDispatch.RegisterCmOp["DeGamma",  CmDeGamma, cmDeGammaUsage];
CtDispatch.RegisterCmOp["Negate",    CmNegate,   cmNegateUsage];
CtDispatch.RegisterCmOp["Edit",     CmEdit,   cmEditUsage];
CtDispatch.RegisterCmOp["Explicit Setting:", NIL,    NIL];
CtDispatch.RegisterCmOp["Color",    CmColor,   cmColorUsage];
CtDispatch.RegisterCmOp["Ramp",    CmRamp,   cmRampUsage];
CtDispatch.RegisterCmOp["Spline",   CmSpline,  cmSplineUsage];
CtDispatch.RegisterCmOp["TriColors",   CmTriColors,  cmTriColorsUsage];
CtDispatch.RegisterCmOp["Scale/Add/Compose/Interpolate:", NIL,    NIL];
CtDispatch.RegisterCmOp["Scale",    CmScale,   cmScaleUsage];
CtDispatch.RegisterCmOp["TriScale",   CmTriScale,  cmTriScaleUsage];
CtDispatch.RegisterCmOp["Add",    CmAdd,   cmAddUsage];
CtDispatch.RegisterCmOp["Interp",   CmInterp,  cmInterpUsage];
CtDispatch.RegisterCmOp["Compose",   CmCompose,  cmComposeUsage];
CtDispatch.RegisterCmOp["Function Creation:", NIL,    NIL];
CtDispatch.RegisterCmOp["Tents",    CmTents,   cmTentsUsage];
CtDispatch.RegisterCmOp["Square",   CmSquare,  cmSquareUsage];
CtDispatch.RegisterCmOp["Line",    CmLine,   cmLineUsage];
CtDispatch.RegisterCmOp["Sin",    CmSin,   cmSinUsage];
CtDispatch.RegisterCmOp["Gauss",    CmGauss,   cmGaussUsage];
CtDispatch.RegisterCmOp["Contours",   CmContours,  cmContoursUsage];
CtDispatch.RegisterCmOp["Miscellany:",  NIL,    NIL];
CtDispatch.RegisterCmOp["Only",    CmOnly,   cmOnlyUsage];
CtDispatch.RegisterCmOp["ToOthers",   CmToOthers,  cmToOthersUsage];
CtDispatch.RegisterCmOp["NBits",    CmNBits,   cmNBitsUsage];
CtDispatch.RegisterCmOp["Print",    CmPrint,   cmPrintUsage];
CtDispatch.RegisterCmOp["Blend",    CmBlend,   cmBlendUsage];
CtDispatch.RegisterCmOp["Randomizing:",  NIL,    NIL];
CtDispatch.RegisterCmOp["Scramble",   CmScramble,  cmScrambleUsage];
CtDispatch.RegisterCmOp["Random",   CmRandom,  cmRandomUsage];
CtDispatch.RegisterCmOp["Flash",    CmFlash,   cmFlashUsage];
CtDispatch.RegisterCmOp["Speckle",   CmSpeckle,  cmSpeckleUsage];
CtDispatch.RegisterCmOp["Cycling:",   NIL,    NIL];
CtDispatch.RegisterCmOp["Cycle",    CmCycle,   cmCycleUsage];
CtDispatch.RegisterCmOp["Shift",    CmShift,   cmShiftUsage];
CtDispatch.RegisterCmOp["Threshold",  CmThreshold, cmThresholdUsage];
CtDispatch.RegisterCmOp["Ripple",   CmRipple,  cmRippleUsage];
CtDispatch.RegisterCmOp["Snake",    CmSnake,   cmSnakeUsage];
END.