CtSimpleCommandsImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Bloomenthal, December 7, 1992 4:22 pm PST
Heckbert, June 1, 1988 0:08:05 am PDT
Shoemake, June 19, 1989 2:40:08 am PDT
Glassner, November 14, 1990 2:57 pm PST
DIRECTORY Args, Commander, CommanderOps, CtBasic, CtDispatch, CtFile, CtMap, CtMisc, CtMod, CtPix, Convert, FileNames, Imager, ImagerBackdoor, ImagerPath, ImagerSample, IO, Process, Real, RealFns, Rope;
CtSimpleCommandsImpl: CEDAR PROGRAM
IMPORTS Args, CommanderOps, Convert, CtBasic, CtDispatch, CtFile, CtMap, CtMisc, CtMod, CtPix, FileNames, Imager, ImagerBackdoor, ImagerPath, ImagerSample, IO, Process, Real, RealFns, Rope
~ BEGIN
SampleMap:   TYPE ~ CtBasic.SampleMap;
SampleMaps:   TYPE ~ CtBasic.SampleMaps;
PixelArray:   TYPE ~ CtBasic.PixelArray;
ValueProc:   TYPE ~ CtBasic.ValueProc;
RGB:     TYPE ~ CtBasic.RGB;
CtProc:    TYPE ~ CtDispatch.CtProc;
Cmap:      TYPE ~ CtMap.Cmap;
Table:      TYPE ~ CtMod.Table;
Box:     TYPE ~ ImagerSample.Box;
SampleBuffer:  TYPE ~ ImagerSample.SampleBuffer;
Vec:     TYPE ~ ImagerSample.Vec;
ROPE:     TYPE ~ Rope.ROPE;
Clearing/Drawing Commands
ctClearUsage: ROPE ~ "Ct Clear [r[gb]] [-wi] [-w<xywh>]: clear the color display.";
CtClear: CtProc ~ {
rVal, gVal, bVal: CARDINAL;
rArg, gArg, bArg: Args.Arg;
[rArg, gArg, bArg] ¬ Args.ArgsGet[cmd, "[iii" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
rVal ¬ MIN[255, IF rArg.ok THEN INTEGER[rArg.int] ELSE 255];
gVal ¬ MIN[255, IF gArg.ok THEN INTEGER[gArg.int] ELSE rVal];
bVal ¬ MIN[255, IF bArg.ok THEN INTEGER[bArg.int] ELSE gVal];
CtBasic.FillMaps[maps, rVal, rVal, gVal, bVal];
affect ¬ maps.box;
};
ctLineUsage: ROPE ~ "Ct Line x0 y0 x1 y1 r[gb] [-wi] [-w<xywh>]: draw a line.";
CtLine: CtProc ~ {
x0, y0, x1, y1, r, g, b: Args.Arg;
[x0, y0, x1, y1, r, g, b] ¬ Args.ArgsGet[cmd, "[iiiiiii"
! Args.Error => {error ¬ reason; CONTINUE}];
IF NOT (error = NIL AND x0.ok AND y0.ok AND x1.ok AND y1.ok AND r.ok) THEN RETURN;
IF maps.bpp = 24
THEN {
IF NOT (g.ok AND b.ok) THEN RETURN[error: "bad arguments"];
CtBasic.PutRGBLine[maps, x0.int, y0.int, x1.int, y1.int, [r.int, g.int, b.int]];
}
ELSE CtBasic.PutBWLine[maps[0].map, x0.int, y0.int, x1.int, y1.int, r.int];
affect ¬ [[y0.int, x0.int], [y1.int, x1.int]];
};
ctColorBarsUsage: ROPE ~ "Ct ColorBars [-SMPTE] [-EIA] (default is SMPTE).";
CtColorBars: CtProc ~ {
PutBox: PROC [x0, y0, x1, y1: INTEGER, rgb: RGB] ~ {
CtBasic.PutRGBBox[maps, maps.x+x0, maps.y+y0, maps.x+x1, maps.y+y1, rgb];
};
smpte, eia: Args.Arg;
[smpte, eia] ¬ Args.ArgsGet[cmd, "-smpte%b-eia%b"
! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
IF maps.bpp # 24 THEN RETURN[error: "only works for 24 bpp"];
For EIA color bars:
CtMap.Mono[];
CtBasic.FillMaps[maps]; -- Start with black
PutBox[0*91, 0, 1*91, 360, [191, 191, 191]];   -- Gray
PutBox[1*91, 0, 2*91, 360, [191, 191, 000]];   -- Yellow
PutBox[2*91, 0, 3*91, 360, [000, 191, 191]];   -- Cyan
PutBox[3*91, 0, 4*91, 360, [000, 191, 000]];   -- Green
PutBox[4*91, 0, 5*91, 360, [191, 000, 191]];   -- Magenta
PutBox[5*91, 0, 6*91, 360, [191, 000, 000]];   -- Red
PutBox[6*91, 0, 7*91, 360, [000, 000, 191]];   -- Blue
PutBox[0*114, 360, 1*114, 480, [0, 76, 127]];   -- NTSC -I
PutBox[1*114, 360, 2*114, 480, [255, 255, 255]];  -- White
PutBox[2*114, 360, 3*114, 480, [75, 0, 139]];   -- NTSC +Q
For SMPTE split color bars, with PLUGE black:
IF NOT eia.ok THEN {
PutBox[0*91, 322, 7*91, 360, [000, 000, 000]]; -- Black for split
PutBox[0*91, 322, 1*91, 360, [000, 000, 191]]; -- Blue
PutBox[2*91, 322, 3*91, 360, [191, 000, 191]]; -- Magenta
PutBox[4*91, 322, 5*91, 360, [000, 191, 191]]; -- Cyan
PutBox[6*91, 322, 7*91, 360, [191, 191, 191]]; -- Gray
PutBox[5*91, 360, 5*91+30, 480, [11, 11, 11]]; -- Black+4 for PLUGE
};
affect ¬ [[0, 0], [480, 7*91]];
};
ctRampUsage: ROPE ~ "Ct Ramp [-h|-v] [-wi] [-w<xywh>]: draw horiz | vert ramp.";
CtRamp: CtProc ~ {
v, h: Args.Arg;
[v, h] ¬ Args.ArgsGet[cmd, "-v%b-h%b" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
FOR n: NAT IN [0..maps.nChannels) DO
IF h.bool
THEN CtPix.RampH[maps[n].map]
ELSE CtPix.RampV[maps[n].map];
ENDLOOP;
affect ¬ maps.box;
};
ctPieUsage: ROPE ~ "Ct Pie [-wi] [-w<xywh>]: draw a circular ramp.";
CtPie: CtProc ~ {
FOR i: INT IN [0..maps.nChannels) DO CtPix.Pie[maps[i].map, [0, 0], 0]; ENDLOOP;
affect ¬ maps.box;
};
ctDitherUsage: ROPE ~
"Ct Dither [pixel-value] [-wi] [-w<xywh>]: dither the picture.
 If no value specified, dither the existing image;
 otherwise, create a dithered flat field of intensity <value>.";
CtDither: CtProc ~ {
value: CARDINAL ¬ 0;
value ¬ Args.ArgInt[cmd ! Args.Error => CONTINUE].int;
FOR n: NAT IN [0..maps.nChannels) DO
CtPix.Dither[maps[n].map, value];
ENDLOOP;
affect ¬ maps.box;
};
ctGridUsage: ROPE ~ "[-pVal<INT>][-space<INT>][-width<INT>][-wi][-w<xywh>].";
CtGrid: CtProc ~ {
InnerGrid: PROC [map: SampleMap] ~ {
FOR i: NAT ¬ maps.box.min.f, i+space WHILE i < maps.box.max.f DO
FOR j: NAT ¬ maps.box.min.s, j+1 WHILE j < maps.box.max.s DO
ImagerSample.Fill[map, [[j, i], [j+width, i+width]], pVal];
ENDLOOP;
ENDLOOP;
FOR j: NAT ¬ maps.box.min.s, j+space WHILE j < maps.box.max.s DO
FOR i: NAT ¬ maps.box.min.f, i+1 WHILE i < maps.box.max.f DO
ImagerSample.Fill[map, [[j, i], [j+width, i+width]], pVal];
ENDLOOP;
ENDLOOP;
};
pVal, space, width: CARDINAL;
pValArg, spaceArg, widthArg: Args.Arg;
[pValArg, spaceArg, widthArg] ¬ Args.ArgsGet[cmd, "-pVal%i-space%i-width%i"
! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
pVal ¬ IF pValArg.ok THEN pValArg.int ELSE 0;
pVal ¬ 256*pVal+pVal;
space ¬ IF spaceArg.ok THEN spaceArg.int ELSE 40;
width ¬ IF widthArg.ok THEN widthArg.int ELSE 1;
FOR n: NAT IN [0..maps.nChannels) DO InnerGrid[maps[n].map]; ENDLOOP;
affect ¬ maps.box;
};
ctCheckUsage: ROPE ~ "Ct Check [-pVal<INT>] [-space<INT>] [-wi] [-w<xywh>].";
CtCheck: CtProc ~ {
InnerCheck: PROC [map: SampleMap] ~ {
ySense: BOOL ¬ TRUE;
FOR y: NAT ¬ maps.box.min.s, y+space WHILE y < maps.box.max.s DO
xSense: BOOL ¬ (ySense ¬ NOT ySense);
FOR x: NAT ¬ maps.box.min.f, x+space WHILE x < maps.box.max.f DO
Process.CheckForAbort[];
IF xSense THEN ImagerSample.Fill[map, [[y, x], [y+space, x+space]], pVal];
xSense ¬ NOT xSense;
ENDLOOP;
ENDLOOP;
};
pVal, space: CARDINAL;
pValArg, spaceArg: Args.Arg;
[pValArg, spaceArg] ¬ Args.ArgsGet[cmd, "-pVal%i-space%i"
! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
pVal ¬ IF pValArg.ok THEN pValArg.int ELSE 0;
space ¬ IF spaceArg.ok THEN spaceArg.int ELSE 40;
FOR n: NAT IN [0..maps.nChannels) DO InnerCheck[maps[n].map]; ENDLOOP;
affect ¬ maps.box;
};
ctSinesUsage: ROPE
~ "Ct Sines -minF<INT> -maxF<INT> [-hOnly] [-wi] [-w<xywh>]: sine pattern, F is freq.";
CtSines: CtProc ~ {
Sines: PROC [map: SampleMap, minF, maxF: INT] ~ {
Action: PROC [samples: ImagerSample.SampleBuffer] ~ TRUSTED {
IF hOnly.bool THEN
FOR x: INTEGER IN [0..maps.size.f) DO
dt: REAL ¬ inc*(x*mul+minF);
ival: CARDINAL ¬ Real.Round[127*RealFns.Sin[x*dt]+127.5];
samples[x] ¬ ival;
ENDLOOP;
FOR y: INTEGER IN [maps.box.min.s..maps.box.max.s) DO
dt: REAL ¬ inc*((y-maps.box.min.s)*mul+minF);
Process.CheckForAbort[];
IF NOT hOnly.bool THEN
FOR x: INTEGER IN [0..maps.size.f) DO
ival: CARDINAL ¬ Real.Round[127*RealFns.Sin[x*dt]+127.5];
samples[x] ¬ ival;
ENDLOOP;
FOR n: NAT IN [0..maps.nChannels) DO
ImagerSample.PutSamples[maps[n].map, [y, maps.x],, samples, 0, maps.size.f];
ENDLOOP;
ENDLOOP;
};
mul: REAL ¬ (maxF-minF)/REAL[maps.size.s];
ImagerSample.DoWithScratchSamples[maps.size.f, Action];
};
minF, maxF, hOnly: Args.Arg;
inc: REAL ¬ 2.0*3.14159265358979/REAL[maps.size.f];
[minF, maxF, hOnly] ¬ Args.ArgsGet[cmd, "-minF%i-maxF%i-hOnly%b"
! Args.Error => {error ¬ reason; CONTINUE}];
FOR n: NAT IN [0..maps.nChannels) DO
Sines[maps[n].map, IF minF.ok THEN minF.int ELSE 0, IF maxF.ok THEN maxF.int ELSE 10];
ENDLOOP;
affect ¬ maps.box;
};
Safe Image/Title and Palette Commands
ctPalUsage: ROPE ~ "Ct Pal [INT: nrows] [-smooth]: display palette.";
CtPal: CtProc ~ {
n, smooth: Args.Arg;
[n, smooth] ¬ Args.ArgsGet[cmd, "[i-smooth%b" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error = NIL THEN affect ¬ CtMod.Palette[maps, IF n.ok THEN n.int ELSE 5, smooth.bool];
};
ctSafeUsage: ROPE ~ "Ct Safe: outline the SMPTE safe action and safe title areas.";
CtSafe: CtProc ~ {
Outline: PROC [x, y, w, h, r: REAL] ~ {
sinCos45: REAL ~ 0.7071067811;
rcos: REAL ¬ r*sinCos45;
rsin: REAL ¬ r*sinCos45;
t: Imager.Trajectory ¬ ImagerPath.MoveTo[[x+r, y]];
t ¬ ImagerPath.ArcTo[t, [x+r-rcos, y+r-rsin], [x, y+r]];
t ¬ ImagerPath.LineTo[t, [x, y+h-r]];
t ¬ ImagerPath.ArcTo[t, [x+r-rcos, y+h-r+rsin], [x+r, y+h]];
t ¬ ImagerPath.LineTo[t, [x+w-r, y+h]];
t ¬ ImagerPath.ArcTo[t, [x+w-r+rcos, y+h-r+rsin], [x+w, y+h-r]];
t ¬ ImagerPath.LineTo[t, [x+w, y+r]];
t ¬ ImagerPath.ArcTo[t, [x+w-r+rcos, y+r-rsin], [x+w-r, y]];
Imager.MaskStrokeTrajectory[context, t, TRUE];
};
context: Imager.Context ¬ CtBasic.ContextFromSampleMaps[maps];
Imager.SetColor[context, ImagerBackdoor.invert];
Outline[32, 24, 576, 432, 115]; -- SMPTE recommended safe action area
Outline[65, 48, 510, 384, 101]; -- SMPTE recommended safe title area
};
Processing Commands
GammaSwitch: PROC [cmd: Commander.Handle] RETURNS [gamma: BOOL ¬ FALSE] ~ {
FOR n: NAT IN [0..Args.NArgs[cmd]) DO
IF Eq["-gamma", Args.GetRope[cmd, n]] THEN RETURN[TRUE];
ENDLOOP;
};
ctLumUsage: ROPE ~
"Ct Lum [-gamma] [-wi] [-w<xywh>]:
 process image into luminance; if 8 bit image, the color map
 will be set to mono or (if -gamma) gamma of 2.2.";
CtLum: CtProc ~ {
cm: Cmap ¬ CtMap.Read[];
gamma: BOOL ¬ GammaSwitch[cmd];
IF gamma THEN CtMap.DeGamma[2.2, cm];
IF gamma THEN CtMap.Gamma[2.2] ELSE CtMap.Mono[];
IF maps.bpp = 24
THEN CtMod.Lum24[maps, cm]
ELSE CtMod.Indirect[maps[0].map, CtMap.GetLuminanceTable[cm], maps.box];
affect ¬ maps.box;
};
ctPseudoToRGBUsage: ROPE ~
"Ct PsuedoToRGB <name> [-gamma] [-wi] [-w<xywh>]:
 creates name-red, -grn, -blu.ais from pseudo color image
 -gamma: create for viewing with gamma map (mono otherwise).";
CtPseudoToRGB: CtProc ~ {
name: ROPE ~ Args.GetRope[cmd];
IF name = NIL
THEN RETURN[error: ctPseudoToRGBUsage]
ELSE {
gamma: BOOL ← GammaSwitch[cmd];
map: SampleMap ← IF maps.bpp = 8 THEN maps[0].map ELSE maps[1].map;
cmap: Cmap ← CtMap.Read[];
base: ROPE ~ FileNames.ResolveRelativePath[name];
tmp: SampleMap ← ImagerSample.NewSampleMap[maps.box, 8];
SaveOneFile: PROC [color: ROPE, i: NAT] ~ {
IO.PutF1[cmd.out, "%g . . . ", IO.rope[color]];
ImagerSample.Transfer[tmp, maps[0].map];
CtMod.Indirect[tmp, CtMap.GetTable[cmap, i], maps.box];
CtFile.SaveSampleMapToFile[tmp, Rope.Cat[base, "-", color, ".ais"]];
};
IF maps.bpp = 24 THEN RETURN[error: "this command for 8 bpp only"];
IF gamma THEN CtMap.DeGamma[2.2, cmap];
IO.PutRope[cmd.out, "writing "];
SaveOneFile["red", 0];
SaveOneFile["grn", 1];
SaveOneFile["blu", 2];
IO.PutRope[cmd.out, "done!\n"];
};
affect ← maps.box;
};
ctIndirectUsage: ROPE ~
"Ct Indirect [-gamma] [-wi] [-w<xywh>]:
 process 24 bit image by indirecting through the color map.
 -gamma: create for viewing with gamma map (mono otherwise).";
CtIndirect: CtProc ~ {
cmap: Cmap ¬ CtMap.Read[];
IF GammaSwitch[cmd] THEN CtMap.DeGamma[2.2, cmap];
IF maps.nChannels # 3 THEN RETURN[error: "color display must be at 24 bits per pixel."];
FOR n: NAT IN [0..3) DO
CtMod.Indirect[maps[n].map, CtMap.GetTable[cmap, n], maps.box];
ENDLOOP;
affect ¬ maps.box;
};
ctDifUsage: ROPE ~ "Ct Dif <x0 y0 x1 y1 x2 y2 w h> Display |dif| of boxes.";
CtDif: CtProc ~ {
x0, y0, x1, y1, x2, y2, w, h: Args.Arg;
[x0, y0, x1, y1, x2, y2, w, h] ¬
Args.ArgsGet[cmd, "%iiiiiiii" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
FOR n: NAT IN [0..maps.nChannels) DO
CtMod.Dif[maps[n].map, x0.int, y0.int, x1.int, y1.int, x2.int, y2.int, w.int, h.int];
ENDLOOP;
affect ¬ [[y2.int, x2.int], [y2.int+h.int, x2.int+w.int]];
};
ctPValUsage: ROPE ~ "Ct PVal <newVal> <oldVal> [oldVal] . . . [-wi] [-w<xywh>].";
CtPVal: CtProc ~ {
ENABLE Convert.Error => GOTO Bad;
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
IF argv.argc < 3
THEN RETURN[error: "bad arguments"]
ELSE {
l: LIST OF NAT ¬ NIL;
new: INTEGER ¬ Convert.IntFromRope[argv[1]];
FOR i: NAT IN [2..argv.argc) DO l ¬ CONS[Convert.IntFromRope[argv[i]], l]; ENDLOOP;
FOR n: NAT IN [0..maps.nChannels) DO CtMod.PVal[maps[n].map, new, l]; ENDLOOP;
affect ¬ maps.box;
};
EXITS Bad => RETURN[error: "conversion error"];
};
ctBlurUsage: ROPE ~ "Ct Blur [blur-radius, default = 1] [-wi] [-w<xywh>].";
CtBlur: CtProc ~ {
radius: NAT ~ IF Args.NArgs[cmd] = 1 THEN Args.ArgInt[cmd].int ELSE 1;
denom: NAT ~ (2*radius+1)*(2*radius+1);
x0: INTEGER ~ maps.x+radius;
y0: INTEGER ~ maps.y+radius;
x1: INTEGER ~ maps.x+maps.w-1-radius;
y1: INTEGER ~ maps.y+maps.h-1-radius;
IO.PutRope[cmd.out, "Getting pixel array(s) . . . "];
IF maps.bpp = 24
THEN {
Blur: CtBasic.RGBProc ~ {
r, g, b: SampleBuffer;
rr, gg, bb: CARDINAL ¬ 0;
IF x IN [x0..x1] AND y IN [y0..y1]
THEN {
FOR yy: INTEGER IN [y-radius..y+radius] DO
r ¬ red[yy];
g ¬ grn[yy];
b ¬ blu[yy];
FOR xx: INTEGER IN [x-radius..x+radius] DO
rr ¬ rr + r[xx];
gg ¬ gg + g[xx];
bb ¬ bb + b[xx];
ENDLOOP
ENDLOOP;
rgb ¬ [rr/denom, gg/denom, bb/denom];
}
ELSE {
n: CARDINAL ¬ 0;
FOR yy: INTEGER IN [y-radius..y+radius] DO
IF yy NOT IN [y0..y1] THEN LOOP;
r ¬ red[yy];
g ¬ grn[yy];
b ¬ blu[yy];
FOR xx: INTEGER IN [x-radius..x+radius] DO
IF xx NOT IN [x0..x1] THEN LOOP;
rr ¬ rr + r[xx];
gg ¬ gg + g[xx];
bb ¬ bb + b[xx];
n ¬ n+1;
ENDLOOP;
ENDLOOP;
rgb ¬ IF n # 0 THEN [rr/n, gg/n, bb/n] ELSE [0, 0, 0];
};
};
red, grn, blu: PixelArray;
red ¬ CtBasic.PixelArrayFromSampleMap[maps[0].map];
grn ¬ CtBasic.PixelArrayFromSampleMap[maps[1].map];
blu ¬ CtBasic.PixelArrayFromSampleMap[maps[2].map];
IO.PutRope[cmd.out, "blurring . . .\n"];
CtBasic.PutRGBFrame[maps, Blur];
}
ELSE {
Blur: CtBasic.ValueProc ~ {
vv: CARDINAL ¬ 0;
IF x IN [x0..x1] AND y IN [y0..y1]
THEN {
FOR yy: INTEGER IN [y-radius..y+radius] DO
sb: SampleBuffer ¬ bw[yy];
FOR xx: INTEGER IN [x-radius..x+radius] DO vv ¬ vv+sb[xx]; ENDLOOP;
ENDLOOP;
value ¬ vv/denom;
}
ELSE {
n: CARDINAL ¬ 0;
FOR yy: INTEGER IN [y-radius..y+radius] DO
IF yy IN [y0..y1] THEN {
sb: SampleBuffer ¬ bw[yy];
FOR xx: INTEGER IN [x-radius..x+radius] DO
IF xx IN [x0..x1] THEN {vv ¬ vv+sb[xx]; n ¬ n+1}
ENDLOOP;
};
ENDLOOP;
value ¬ IF n # 0 THEN vv/n ELSE 0;
};
};
bw: PixelArray ¬ CtBasic.PixelArrayFromSampleMap[maps[0].map];
IO.PutRope[cmd.out, "blurring . . .\n"];
CtBasic.PutBWFrame[maps[0].map, Blur];
};
affect ¬ maps.box;
};
Replicate and Gammas Command:
ctGammasUsage:  ROPE ~ "
Ct Gammas <wh> [gamma0] [gamma1]
apply varying gamma to the screen";
CtGammas: CtProc ~ {
width, height, gamma0, gamma1: Args.Arg;
[width, height, gamma0, gamma1] ¬ Args.ArgsGet[cmd, "%ii[rr" ! Args.Error => GOTO Bad];
IF width.ok AND width.int > 0 AND height.ok AND height.int > 0
THEN {
context: Imager.Context ¬ CtBasic.ContextFromSampleMaps[maps];
i: NAT ¬ 0;
w: NAT ¬ width.int;
h: NAT ¬ height.int;
g0: REAL ¬ IF gamma0.ok THEN gamma0.real ELSE 0.5;
g1: REAL ¬ IF gamma1.ok THEN gamma1.real ELSE 3.0;
nTiles: NAT ¬ MAX[1, (maps.h/h)*(maps.w/w)];
FOR yy: NAT ¬ maps.y, yy+h WHILE yy+h <= maps.y+maps.h DO
FOR xx: NAT ¬ maps.x, xx+w WHILE xx+w <= maps.x+maps.w DO
g: REAL ¬ g0+(IF nTiles = 1 THEN 0.5 ELSE REAL[i]/REAL[nTiles-1])*(g1-g0);
CtMod.Gammatize[maps, g, xx, yy, w, h];
Imager.SetGray[context, IF i > nTiles/2 THEN 1 ELSE 0];
CtMisc.PrintRope[Convert.RopeFromReal[g, 4], [xx, yy+h],,,, context];
i ¬ i+1;
ENDLOOP;
ENDLOOP;
}
ELSE RETURN[error: "bad arguments"];
affect ¬ maps.box;
EXITS Bad => NULL;
};
ctReplicateUsage: ROPE ~ "Ct Replicate <xywh>\nreplicate the rectangle as much as possible";
CtReplicate: CtProc ~ {
ENABLE {Convert.Error => GOTO Bad};
x, y, w, h: INT;
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
IF argv.argc # 5 THEN RETURN[error: "bad arguments"];
x ¬ Convert.IntFromRope[argv[1]];
y ¬ Convert.IntFromRope[argv[2]];
w ¬ Convert.IntFromRope[argv[3]];
h ¬ Convert.IntFromRope[argv[4]];
IF x>=0 AND y>=0 AND w>=0 AND h>=0 AND NOT (x=0 AND y=0 AND w=0 AND h=0)
THEN {
srcBox: Box ¬ [[maps.y, maps.x], [maps.y+h, maps.x+w]];
CtBasic.MoveMaps[maps, [y, x], [maps.y, maps.x], [h, w]];
FOR yy: NAT ¬ maps.y, yy+h WHILE yy < maps.y+maps.h DO
FOR xx: NAT ¬ maps.x, xx+w WHILE xx < maps.x+maps.w DO
dstBox: Box ¬ [[yy, xx], [yy+h, xx+w]];
CtBasic.CopyClippedMaps[maps, maps, srcBox, dstBox];
ENDLOOP;
ENDLOOP;
}
ELSE RETURN[error: "bad arguments"];
affect ¬ maps.box;
EXITS Bad => RETURN[error: "bad arguments"];
}; 
ctMedialUsage: ROPE ~ "Ct Medial [-wi] [-w<xywh>]: incr values to region centers.";
CtMedial: CtProc ~ {
v0, v1, count: CARDINAL;
SetUp: PROC [x, y: CARDINAL] ~ {
Process.CheckForAbort[];
v0 ¬ in[y][x];
count ¬ 0;
};
Test: PROC [x, y, xx, yy: CARDINAL, noCompare: BOOL] ~ INLINE {
val: CARDINAL;
v1 ¬ in[y][x];
IF v0 # v1
THEN {count ¬ 0; v0 ¬ v1}
ELSE IF count < 255 THEN count ¬ count+1;
val ¬ MIN[count, out[yy][xx]+1];
IF noCompare
THEN out[y][x] ¬ val
ELSE IF val < out[y][x] THEN out[y][x] ¬ val;
};
map: SampleMap ¬ maps[0].map;
box: Box ~ ImagerSample.GetBox[map];
in: PixelArray ~ CtBasic.PixelArrayFromSampleMap[map];
out: PixelArray ~ CtBasic.AllocatePixelArray[box];
x0: CARDINAL ~ box.min.f;
y0: CARDINAL ~ box.min.s;
x1: CARDINAL ~ box.max.f-1;
y1: CARDINAL ~ box.max.s-1;
FOR x: NAT IN [x0..x1] DO out[y0][x] ¬ out[y1][x] ¬ 0; ENDLOOP;
FOR y: NAT IN [y0..y1] DO out[y][x0] ¬ out[y][x1] ¬ 0; ENDLOOP;
IO.PutRope[cmd.out, "Doing left to right, "];
FOR y: NAT IN (y0..y1) DO  -- downwards, left to right
SetUp[x0, y];
FOR x: NAT IN (x0..x1) DO
Test[x, y, x, y-1, TRUE];
ENDLOOP;
ENDLOOP;
IO.PutRope[cmd.out, "right to left, "];
FOR y: NAT DECREASING IN (y0..y1) DO-- upwards, right to left
SetUp[x1, y];
FOR x: NAT DECREASING IN (x0..x1) DO
Test[x, y, x, y+1, FALSE];
ENDLOOP;
ENDLOOP;
IO.PutRope[cmd.out, "up to down, "];
FOR x: NAT IN (x0..x1) DO  -- rightwards, up to down
SetUp[x, y0];
FOR y: NAT IN (y0..y1) DO
Test[x, y, x-1, y, FALSE];
ENDLOOP;
ENDLOOP;
IO.PutRope[cmd.out, "and down to up\n"];
FOR x: NAT DECREASING IN (x0..x1) DO-- leftwards, down to up
SetUp[x, y1];
FOR y: NAT DECREASING IN (y0..y1) DO
Test[x, y, x+1, y, FALSE];
ENDLOOP;
ENDLOOP;
CtBasic.TransferPixelArrayToMap[out, map];
affect ¬ maps.box;
};
Image Processing Commands:
aveNonBlackUsage: ROPE ~
"Ct AveNonBlack
\taverage all non-black parts of the screen";
AveNonBlackCmd: CtProc ~ {
radius: NAT ~ IF Args.NArgs[cmd] = 1 THEN Args.ArgInt[cmd].int ELSE 1;
denom: NAT ~ (2*radius+1)*(2*radius+1);
x0: INTEGER ~ maps.x+radius;
y0: INTEGER ~ maps.y+radius;
x1: INTEGER ~ maps.x+maps.w-1-radius;
y1: INTEGER ~ maps.y+maps.h-1-radius;
affect ¬ maps.box;
IO.PutRope[cmd.out, "Getting pixel array(s) . . . "];
IF maps.bpp<= 8
THEN {
Ave: CtBasic.ValueProc ~ {
val: CARDINAL ¬ bw[y][x];
IF val # 0
THEN RETURN[val]
ELSE {
vv, count: CARDINAL ¬ 0;
FOR yy: INTEGER IN [y-radius..y+radius] DO
IF yy IN [y0..y1] THEN {
sb: SampleBuffer ¬ bw[yy];
FOR xx: INTEGER IN [x-radius..x+radius] DO
IF xx IN [x0..x1] AND (val ¬ sb[xx]) # 0
THEN {vv ¬ vv+val; count ¬ count+1}
ENDLOOP;
};
ENDLOOP;
IF count # 0 THEN value ¬ vv/count ELSE value ¬ 0;
};
};
bw: PixelArray ¬ CtBasic.PixelArrayFromSampleMap[maps[0].map];
IO.PutRope[cmd.out, "averaging . . .\n"];
CtBasic.PutBWFrame[maps[0].map, Ave];
}
ELSE RETURN[error: "must be < 8 bpp"];
};
Other Manipulation Commands
ctLeftUsage:  ROPE ~ "Ct Left <INT> [-wi] [-w<xywh>]: shift image left.";
ctRightUsage: ROPE ~ "Ct Right <INT> [-wi] [-w<xywh>]: shift image right.";
ctUpUsage:  ROPE ~ "Ct Up <INT> [-wi] [-w<xywh>]: shift image up.";
ctDownUsage: ROPE ~ "Ct Down <INT> [-wi] [-w<xywh>]: shift image down.";
CtLeft, CtRight, CtUp, CtDown: CtProc ~ {
arg: Args.Arg;
[arg] ¬ Args.ArgsGet[cmd, "%i" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
FOR n: NAT IN [0..maps.nChannels) DO
SELECT TRUE FROM
Eq[cmd.command, "left"] => CtMod.Left[maps[n].map, arg.int];
Eq[cmd.command, "right"] => CtMod.Left[maps[n].map, -arg.int];
Eq[cmd.command, "up"] => CtMod.Up[maps[n].map, arg.int];
Eq[cmd.command, "down"] => CtMod.Up[maps[n].map, -arg.int];
ENDCASE;
ENDLOOP;
};
ctNegateUsage: ROPE ~ "Ct Negate [-wi] [-w<xywh>]: negate the image.";
CtNegate: CtProc ~ {
FOR n: NAT IN [0..maps.nChannels) DO CtMod.Negate[maps[n].map]; ENDLOOP;
affect ¬ maps.box;
};
ctReflectUsage: ROPE ~ "Ct Reflect <-h|-v> [-wi] [-w<xywh>]: reflect the image.";
CtReflect: CtProc ~ {
v, h: Args.Arg;
[v, h] ¬ Args.ArgsGet[cmd, "-v%b-h%b" ! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
FOR n: NAT IN [0..maps.nChannels) DO
m: SampleMap ¬ maps[n].map;
IF h.bool THEN CtMod.ReflectH[m] ELSE CtMod.ReflectV[m];
ENDLOOP;
affect ¬ maps.box;
};
ctMirrorUsage: ROPE ~ "Ct Mirror <-l|-r|-t|-b> [-wi] [-w<xywh>]: mirror the image.";
CtMirror: CtProc ~ {
MirrorH: PROC [maps: SampleMaps, topToBottom: BOOL] ~ {
FOR n: NAT IN [0..maps.nChannels) DO
CtMod.MirrorH[maps[n].map, topToBottom];
ENDLOOP;
};
MirrorV: PROC [maps: SampleMaps, leftToRight: BOOL] ~ {
FOR n: NAT IN [0..maps.nChannels) DO
CtMod.MirrorV[maps[n].map, leftToRight];
ENDLOOP;
};
l, r, t, b: Args.Arg;
[l, r, t, b] ¬ Args.ArgsGet[cmd, "-l%b-r%b-t%b-b%b"
! Args.Error => {error ¬ reason; CONTINUE}];
IF error # NIL THEN RETURN;
SELECT TRUE FROM
l.bool  => MirrorV[maps, TRUE];
r.bool  => MirrorV[maps, FALSE];
t.bool  => MirrorH[maps, TRUE];
b.bool  => MirrorH[maps, FALSE];
ENDCASE => MirrorH[maps, FALSE];
affect ¬ maps.box;
};
ctTransposeUsage: ROPE ~ "Ct Transpose [-wi] [-w<xywh>]: transpose the image.";
CtTranspose: CtProc ~ {
FOR n: NAT IN [0..maps.nChannels) DO CtMod.Transpose[maps[n].map]; ENDLOOP;
affect ¬ maps.box;
};
ctRotateUsage: ROPE ~
"Ct Rotate <angle> [-radians] [-wi] [-w<xywh>]
 rotate the image (positive rotation is counter-clockwise).";
CtRotate: CtProc ~ {
degrees: REAL;
angleA, inRadiansA: Args.Arg;
[angleA, inRadiansA] ¬ Args.ArgsGet[cmd, "%r-radians%b"];
degrees ¬ IF inRadiansA.bool THEN (180.0/3.1415926535)*angleA.real ELSE angleA.real;
FOR i: INTEGER IN [0..maps.nChannels) DO CtMod.Rotate[maps[i].map, degrees]; ENDLOOP;
affect ¬ maps.box;
};
Support
Eq: PROC [r1, r2: ROPE] RETURNS [BOOL] ~ {RETURN[Rope.Equal[r1, r2, FALSE]]};
Start Code
Clearing/Drawing
CtDispatch.RegisterCtOp["Clearing/Drawing:", NIL,    NIL];
CtDispatch.RegisterCtOp["Clear",     CtClear,   ctClearUsage];
CtDispatch.RegisterCtOp["Line",     CtLine,   ctLineUsage];
CtDispatch.RegisterCtOp["ColorBars",    CtColorBars,  ctColorBarsUsage];
CtDispatch.RegisterCtOp["Ramp",     CtRamp,   ctRampUsage];
CtDispatch.RegisterCtOp["Pie",      CtPie,    ctPieUsage];
CtDispatch.RegisterCtOp["Grid",     CtGrid,   ctGridUsage];
CtDispatch.RegisterCtOp["Check",     CtCheck,   ctCheckUsage];
CtDispatch.RegisterCtOp["Sines",     CtSines,   ctSinesUsage];
CtDispatch.RegisterCtOp["Dither",     CtDither,   ctDitherUsage];
Palette/Safety
CtDispatch.RegisterCtOp["Palette/Safety:",  NIL,    NIL];
CtDispatch.RegisterCtOp["Pal",      CtPal,    ctPalUsage];
CtDispatch.RegisterCtOp["Safe",     CtSafe,   ctSafeUsage];
Processing
CtDispatch.RegisterCtOp["Processing:",   NIL,    NIL];
CtDispatch.RegisterCtOp["PseudoToRGB",   CtPseudoToRGB, ctPseudoToRGBUsage];
CtDispatch.RegisterCtOp["Indirect",    CtIndirect,  ctIndirectUsage];
CtDispatch.RegisterCtOp["Lum",     CtLum,   ctLumUsage];
CtDispatch.RegisterCtOp["PVal",     CtPVal,   ctPValUsage];
CtDispatch.RegisterCtOp["Blur",     CtBlur,   ctBlurUsage];
CtDispatch.RegisterCtOp["Dif",      CtDif,    ctDifUsage];
CtDispatch.RegisterCtOp["Medial",     CtMedial,   ctMedialUsage];
CtDispatch.RegisterCtOp["Replicate",    CtReplicate,  ctReplicateUsage];
CtDispatch.RegisterCtOp["Gammas",    CtGammas,  ctGammasUsage];
CtDispatch.RegisterCtOp["AveNonBlack",   AveNonBlackCmd, aveNonBlackUsage];
Manipulation
CtDispatch.RegisterCtOp["Left",     CtLeft,   ctLeftUsage];
CtDispatch.RegisterCtOp["Right",     CtRight,   ctRightUsage];
CtDispatch.RegisterCtOp["Up",      CtUp,    ctUpUsage];
CtDispatch.RegisterCtOp["Down",     CtDown,   ctDownUsage];
CtDispatch.RegisterCtOp["Negate",     CtNegate,   ctNegateUsage];
CtDispatch.RegisterCtOp["Reflect",     CtReflect,   ctReflectUsage];
CtDispatch.RegisterCtOp["Mirror",     CtMirror,   ctMirrorUsage];
CtDispatch.RegisterCtOp["Transpose",    CtTranspose,  ctTransposeUsage];
CtDispatch.RegisterCtOp["Rotate",     CtRotate,   ctRotateUsage];
END.