MODULE PZSplit EXPORTS Main; (* Splits a PGM image of N fragments into N separate images. *) IMPORT ParseParams, Process, Wr, Thread, Stdio, Fmt; IMPORT PZImage; FROM Stdio IMPORT stderr; <* FATAL Wr.Failure, Thread.Alerted *> TYPE Options = RECORD inFile: TEXT; (* Input image name, without the ".pgm" extension *) outPrefix: TEXT; (* Output image name prefix, without the ".pgm" extension *) startNum: CARDINAL; (* First fragment number *) black: CARDINAL; (* Max "black" intensity value [0..254] *) END; IntPoint = ARRAY [0..1] OF INTEGER; Pixel = RECORD pos: IntPoint; level: PZImage.GrayLevel; END; Pixels = ARRAY OF Pixel; PROCEDURE Main() = VAR image : REF PZImage.T; BEGIN WITH o = GetOptions() DO image := PZImage.ReadPGM(o.inFile); SplitPuzzle(o.outPrefix, o.startNum, image^, o.black); Wr.PutText(stderr, " ok !! \n") END; END Main; PROCEDURE SplitPuzzle( outPrefix: TEXT; startNum: CARDINAL; VAR M: PZImage.T; black: CARDINAL; ) = (* Gets a PGM image "M" of a puzzle, identifies the pieces, and writes each piece to a separate file "outPrefix-0001.pgm", "outPrefix-0002.pgm", etc. The "inside" of a piece is assumed to consist of all pixels with value greater than "black". The "boundary" of a piece consists of all outside pixels that are 4-adjacent to an inside pixel. In the output files, the inside and boundary pixels are copied from the original image, while the remaining outside pixels are set to "black". *) CONST SmallImage = 100; (* Ignore pieces with this perimeter or less *) VAR s, f: CARDINAL; cont: CARDINAL := startNum; newImage : REF PZImage.T; rimg: REF Pixels; BEGIN WITH NY = NUMBER(M), NX = NUMBER(M[0]) DO rimg := NEW(REF Pixels, (NX DIV 10) * (NY DIV 10) + 1); PROCEDURE GetPixel(y,x :CARDINAL) = BEGIN IF f >= NUMBER(rimg^) THEN WITH nimg = NEW(REF Pixels, 2*f) DO SUBARRAY(nimg^,0,f) := SUBARRAY(rimg^,0,f); rimg := nimg; END; END; rimg[f].pos := IntPoint{x, y}; rimg[f].level := M[y,x]; M[y,x] := 0; END GetPixel; PROCEDURE FindNeighbourhood() = (* Given a pixel with coordinates "(x,y)" inside some piece of the puzzle "M", finds all pixels that are connected to it (in the 4-neighbor topology). Those pixels are copied into the array "img" starting at index "n", and then set to "black". A pixel is assumed to be inside if its value is greater than "black". *) BEGIN WHILE s < f DO WITH x = rimg[s].pos[0], y = rimg[s].pos[1] DO IF y > 0 THEN IF (M[y-1,x] > black) THEN GetPixel(y-1,x); INC(f) END; END; IF x > 0 THEN IF (M[y,x-1] > black) THEN GetPixel(y,x-1); INC(f) END; END; IF y < NY-1 THEN IF (M[y+1,x] > black) THEN GetPixel(y+1,x); INC(f) END; END; IF x < NX-1 THEN IF (M[y,x+1] > black) THEN GetPixel(y,x+1); INC(f) END; END; END; INC(s); END; END FindNeighbourhood; BEGIN FOR y:=0 TO NY-1 DO FOR x:=0 TO NX-1 DO IF M[y,x] > black THEN f := 0; s := 0; GetPixel(y,x); INC(f); FindNeighbourhood(); IF f > SmallImage THEN newImage := CreateImage(M, SUBARRAY(rimg^, 0, f)); SavePatchMatrix(outPrefix, cont, newImage^); cont := cont+1; END END END END END END END SplitPuzzle; PROCEDURE SavePatchMatrix( fname: TEXT; cont: CARDINAL; READONLY img: PZImage.T; ) = BEGIN (* Writes an image with the puzzle piece stored in "img". *) WITH name = fname & "-" & Fmt.Pad(Fmt.Int(cont), 4, '0') DO Wr.PutText(stderr, name); Wr.PutText(stderr, "\n"); PZImage.WritePGM(name, img) END END SavePatchMatrix; PROCEDURE CreateImage(READONLY M : PZImage.T; READONLY img : Pixels) : REF PZImage.T= VAR x, y: CARDINAL; minX, maxX, minY, maxY : CARDINAL; PROCEDURE Transformxy(VAR x, y : CARDINAL;xi,yi : CARDINAL )= BEGIN x := xi - minX + 1; y := yi - minY + 1 END Transformxy; BEGIN FindMinMax(img, minX, maxX, minY, maxY); WITH NX = maxX - minX + 3, NY = maxY - minY + 3, rp = NEW(REF PZImage.T, NY, NX), p = rp^ DO FOR y := 0 TO NY-1 DO FOR x := 0 TO NX-1 DO p[y,x] := 0 END END; FOR i := 0 TO LAST(img) DO WITH pos = img[i].pos, xp = pos[0], yp = pos[1] DO Transformxy(x, y, xp, yp); p[y, x] := img[i].level; IF yp > 0 THEN IF M[yp-1,xp] > 0 THEN p[y-1,x] := M[yp-1,xp]; END END; IF xp > 0 THEN IF M[yp,xp-1] > 0 THEN p[y, x-1] := M[yp,xp-1]; END END; IF yp < LAST(M) THEN IF M[yp+1,xp] > 0 THEN p[y+1,x] := M[yp+1,xp]; END END; IF xp < LAST(M[0]) THEN IF M[yp,xp+1] > 0 THEN p[y,x+1] := M[yp,xp+1]; END END END END; RETURN rp END END CreateImage; PROCEDURE FindMinMax( READONLY img: Pixels; VAR minX, maxX, minY, maxY: CARDINAL; )= BEGIN minX := img[0].pos[0]; maxX := img[0].pos[0]; minY := img[0].pos[1]; maxY := img[0].pos[1]; FOR i:= 1 TO LAST(img) DO IF minX > img[i].pos[0] THEN minX := img[i].pos[0] END; IF minY > img[i].pos[1] THEN minY := img[i].pos[1] END; IF maxX < img[i].pos[0] THEN maxX := img[i].pos[0] END; IF maxY < img[i].pos[1] THEN maxY := img[i].pos[1] END; END END FindMinMax; PROCEDURE GetOptions(): Options = VAR o: Options; BEGIN WITH pp = NEW(ParseParams.T).init(stderr) DO TRY pp.getKeyword("-inFile"); o.inFile := pp.getNext(); IF pp.keywordPresent("-outPrefix") THEN o.outPrefix := pp.getNext() ELSE o.outPrefix := o.inFile END; IF pp.keywordPresent("-startNum") THEN o.startNum := pp.getNextInt(0, LAST(CARDINAL)) ELSE o.startNum := 0 END; pp.getKeyword("-black"); o.black := pp.getNextInt(0, 254); pp.finish(); EXCEPT | ParseParams.Error => Wr.PutText(stderr, "Usage: PZSplit \\\n"); Wr.PutText(stderr, " -inFile \\\n"); Wr.PutText(stderr, " [ -outPrefix ] [ -startNum ] \\\n"); Wr.PutText(stderr, " -black \n"); Process.Exit(1); END; END; RETURN o END GetOptions; BEGIN Main() END PZSplit.