(* Last edited on 1999-03-14 12:25:37 by stolfi *) MODULE PGMLineDetect EXPORTS Main; (* Reads a greyscale image "in" and outputs another image "ot" where each input pixel "p" has been replaced a line indicator function. *) IMPORT PGM, PGMImageFilter, Interval3x3Window, LineOps; IMPORT Text, Rd, FileRd, Wr, Thread, OSError, Process, ParseParams; FROM Stdio IMPORT stdin, stdout, stderr; TYPE NAT = CARDINAL; BOOL = BOOLEAN; Interval = PGMImageFilter.Interval; Options = RECORD inRd: Rd.T; (* The input image file. *) noPixel: NAT; (* Invalid pixel value, if any. *) hFactor: BOOL; (* TRUE writes only the "h" factor *) END; PROCEDURE Main() = <* FATAL Wr.Failure, Thread.Alerted, Rd.Failure *> VAR NX, NY: NAT; inMax: NAT; b: Interval3x3Window.T; c: Interval; (* Center pixel *) p: ARRAY [0..7] OF Interval; (* Neighbors in clockwise order *) BEGIN WITH o = GetOptions(), NB = 3, HB = NB DIV 2, inPGM = NEW(PGM.Reader).init(o.inRd, NX, NY, inMax), otPGM = NEW(PGM.Writer).init(stdout, NX, NY, inMax), inBuf = NEW(REF ARRAY OF NAT, NX)^, inInt = NEW(REF ARRAY OF ARRAY OF Interval, NB, NX)^, (* Invariant: while processing scanline "yp", line "k" of the input image is stored in "inInt[k MOD NB]", for "k" in "MAX(0,yp-HB) .. MIN(NY-1,yp+HB)". *) otInt = NEW(REF ARRAY OF Interval, NX)^, ot = NEW(REF ARRAY OF NAT, NX)^ DO (* Read first "NB/2" scanlines *) FOR yp := 0 TO MIN(HB, NY)-1 DO PGMImageFilter.ReadPixels(inPGM, inBuf, NIL, o.noPixel); PGMImageFilter.FloatPixels(inBuf, inMax, inInt[yp MOD NB], o.noPixel); END; (* Perform filtering *) FOR yp := 0 TO NY-1 DO (* Ensure all needed scanlines are in the buffer: *) IF yp + HB < NY THEN PGMImageFilter.ReadPixels(inPGM, inBuf, NIL, o.noPixel); PGMImageFilter.FloatPixels(inBuf, inMax, inInt[(yp + HB) MOD NB], o.noPixel) END; (* Process pixels: *) FOR xp := 0 TO NX-1 DO Interval3x3Window.Get(xp, yp, inInt, NX, NY, b); Interval3x3Window.Normalize(b); LineOps.GetSurround(b, c, p); otInt[xp] := LineOps.LineIndicator(c, p, o.hFactor) END; PGMImageFilter.FixIntervalPixels(otInt, inMax, 0.49999d0, ot, o.noPixel); IF o.noPixel > inMax THEN PGMImageFilter.ReplacePixelValue(o.noPixel, inMax DIV 2, ot) END; PGMImageFilter.WritePixels(otPGM, ot, mkWr := NIL, noPixel := o.noPixel) END; otPGM.finish() END END Main; PROCEDURE GetOptions (): Options = <* FATAL Thread.Alerted, Wr.Failure *> CONST MaxMaxVal: NAT = 256*256-1; VAR o: Options; BEGIN WITH pp = NEW(ParseParams.T).init(stderr) DO TRY IF pp.keywordPresent("-noPixel") THEN o.noPixel := pp.getNextInt(0, MaxMaxVal); ELSE o.noPixel := LAST(NAT); END; o.hFactor := pp.keywordPresent("-hFactor"); pp.skipParsed(); IF pp.next < NUMBER(pp.arg^) THEN o.inRd := GetFileOption(pp) ELSE o.inRd := stdin END; pp.finish(); EXCEPT | ParseParams.Error => Wr.PutText(stderr, "Usage: PGMLineDetect \\\n"); Wr.PutText(stderr, " [ -hFactor ]\\\n"); Wr.PutText(stderr, " [ -noPixel ] \\\n"); Wr.PutText(stderr, " [ ]\n"); Process.Exit(1); END; END; RETURN o END GetOptions; PROCEDURE GetFileOption(pp: ParseParams.T): Rd.T RAISES {ParseParams.Error} = BEGIN WITH name = pp.getNext() DO IF Text.Equal(name, "-") THEN RETURN stdin ELSE TRY RETURN FileRd.Open(name) EXCEPT | OSError.E => pp.error("can't open file \"" & name & "\""); RETURN NIL END END END END GetFileOption; BEGIN Main(); END PGMLineDetect.