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.