DIRECTORY
AIS,
Ascii,
Commander USING [CommandProc, Register],
CommandTool USING [FileWithSearchRules],
FS USING [StreamOpen],
IO,
Process USING [CheckForAbort, GetPriority, Priority, priorityBackground, SetPriority],
Real USING [RoundI],
Rope USING [Equal, Concat, ROPE];
 
~ 
BEGIN 
OPEN Ascii;
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
FilterSpec: TYPE ~ REF FilterSpecRep;
FilterSpecRep: 
TYPE ~ 
RECORD [
x, y: CARDINAL,
seq: SEQUENCE cnt: CARDINAL OF ZEROREAL
];
ZEROREAL: TYPE ~ REAL← 0.0;
ApplyFilterSpec: 
PROC [inFile, outFile: 
ROPE, filter: FilterSpec] = 
BEGIN
inF: AIS.FRef = AIS.OpenFile[inFile];
inR: AIS.Raster = AIS.ReadRaster[inF];
outR: AIS.Raster = NEW [AIS.RasterPart ← inR^];
outF: AIS.FRef = AIS.CreateFile[outFile, outR];
inW: AIS.WRef = AIS.OpenWindow[inF];
outW: AIS.WRef;
firstScan, lastScan, firstPixel, lastPixel: CARDINAL;
[firstScan, lastScan, firstPixel, lastPixel] ← AIS.GetWindowParams[inW];
outW ← AIS.OpenWindow[outF, firstScan, lastScan, firstPixel, lastPixel];
AIS.WriteComment[outF, AIS.ReadComment[inF]];
FOR line: 
CARDINAL 
IN [firstScan .. lastScan] 
DO
FOR pixel: 
CARDINAL 
IN [firstPixel .. lastPixel] 
DO
ENABLE UNWIND => {AIS.CloseWindow[inW]; AIS.CloseWindow[outW]; AIS.CloseFile[inF]; AIS.CloseFile[outF]};
np: CARDINAL;
x: INT = filter.x/2;
y: INT = filter.y/2;
sum: REAL ← 0.0;
FOR i: 
INT 
IN [-y .. y] 
DO
IF i+line NOT IN [firstScan .. lastScan] THEN LOOP;
FOR j: 
INT 
IN [-x .. x] 
DO
fi: CARDINAL ← (i+y)*filter.x + (j+x);
IF j+pixel NOT IN [firstPixel .. lastPixel] THEN LOOP;
sum← sum + AIS.ReadSample[inW, i+line, j+pixel] * filter.seq[fi];
Process.CheckForAbort[];
ENDLOOP;
 
ENDLOOP;
 
np← MIN[MAX[Real.RoundI[sum], 0], 255];
AIS.WriteSample[outW, np, line, pixel];
ENDLOOP;
 
ENDLOOP;
 
AIS.CloseWindow[inW];
AIS.CloseWindow[outW];
AIS.CloseFile[inF];
AIS.CloseFile[outF];
END;
 
ParseFilterSpec: 
PROC [filterName: 
ROPE, fileName: 
ROPE] 
RETURNS [filter: FilterSpec] = 
BEGIN
name: ROPE = Rope.Concat[filterName, ":"];
s: STREAM = FS.StreamOpen[fileName];
token: ROPE; x, y: CARDINAL;
BEGIN 
ENABLE 
UNWIND => 
IF s # 
NIL 
THEN 
IO.Close[s];
DO
IF IO.EndOf[s] THEN EXIT;
token← IO.GetTokenRope[s, Break ! IO.EndOfStream => EXIT].token;
IF NOT Rope.Equal[token, name, FALSE] THEN LOOP;
x← IO.GetCard[s  ! IO.EndOfStream => ERROR];
token← IO.GetTokenRope[s, Break ! IO.EndOfStream => ERROR].token;
IF NOT Rope.Equal[token, "by", FALSE] THEN ERROR;
y← IO.GetCard[s  ! IO.EndOfStream => ERROR];
IF y MOD 2 # 1 THEN ERROR;
IF x MOD 2 # 1 THEN ERROR;
filter← NEW [FilterSpecRep[x*y]];
filter.x← x; filter.y← y;
FOR i: 
CARDINAL 
IN [0 .. y) 
DO
FOR j: 
CARDINAL 
IN [0 .. x) 
DO
filter.seq[i*x+j] ← IO.GetReal[s ! IO.EndOfStream => ERROR];
ENDLOOP;
 
ENDLOOP;
 
ENDLOOP;
 
END; -- of enable
 
IO.Close[s];
END;
 
Break: 
IO.BreakProc = 
{
RETURN [
SELECT char 
FROM
CR, SP, TAB, ',, '{, '} => sepr,
ENDCASE => other]};
 
GetToken: 
PROC [stream: 
IO.
STREAM] 
RETURNS [rope: 
ROPE ← 
NIL] = {
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];
};
 
rope ← stream.GetTokenRope[Break ! IO.EndOfStream => CONTINUE].token;
};
 
ConvolutionFilterCommand: Commander.CommandProc ~ {
stream: IO.STREAM ← IO.RIS[cmd.commandLine];
outputName: ROPE ← GetToken[stream];
gets: ROPE ← GetToken[stream];
inputName: ROPE ← GetToken[stream];
oldPriority: Process.Priority ← Process.GetPriority[];
filter: ROPE ← GetToken[stream];
filterSpec: FilterSpec;
IF 
NOT gets.Equal["←"] 
THEN {
cmd.out.PutRope["Specify output ← input <filterspec>, please.\n"];
RETURN;
};
 
filterSpec ← ParseFilterSpec[filter, CommandTool.FileWithSearchRules["Filter", "txt", cmd]];
Process.SetPriority[Process.priorityBackground];
ApplyFilterSpec[inputName, outputName, filterSpec ! UNWIND => Process.SetPriority[oldPriority]];
Process.SetPriority[oldPriority];
cmd.out.PutRope["Done.\n"];
};
 
Commander.Register["ConvolutionFilter", ConvolutionFilterCommand, "Apply convolution filter to (output ← input filter"];