(* Random Art: Main driver functions CS 131 Based on code by Kurt M. Dresner and Chris Stone *) structure Art = struct (* An exception that should never be raised if your code is correct *) exception OutOfRange (* for : int * int * (int -> unit) -> unit The following code has the right type, but doesn't actually loop. Fix it! *) fun for (low, high, f : int -> unit) = f low (* build : int * Random.rand -> Expr.expr Given a maximum nesting-depth and a random-number generator, produce a random expression. The following code has the right type, but doesn't generate a very interesting (or very random) expression. Fix it! *) fun build (_,_) = Expr.build_x (***** The rest of the code in this file should work as-is, but you're welcome to take a look and see how it works. ******) (* toCoord : int * int -> real toCoord(i,N) converts an integer i in the range [-N,N] to a real in the interval [-1,1] *) fun toCoord(i,N) = (Real.fromInt i) / (Real.fromInt N) (* toIntensity : real -> int Converts real in [-1,1] to an integer in the range [0,255] *) fun toIntensity(z) = if (z < ~1.0 orelse z > 1.0) then (print "Bad function value: "; print (Real.toString z); print "\n"; raise OutOfRange) else Real.round(127.5 + (127.5 * z)) (* emitGray : (real*real->real) * string * int -> unit emitGray(f, basename, N) emits the values of the function f (converted to intensity) to the file basename.pgm for an 2N+1 by 2N+1 grid of points covering the square [-1,1] x [-1,1]. See "man pgm" on Knuth or Wilkes for a full description of the format, but it's essentially a one-line header followed by one byte (a grayscale value 0..255) per pixel. *) fun emitGray (f, basename, N) = let (* Open the output file and write the header *) val stream = TextIO.openOut (basename ^ ".pgm") val N2P1 = N*2+1 (* Picture will be 2*N+1 pixels on a side *) val _ = TextIO.output(stream,"P5 " ^ (Int.toString N2P1) ^ " " ^ (Int.toString N2P1) ^ " 255\n") (* Nested loop: ix goes from ~N to N iy goes from ~N to N *) val _ = for (~N, N, fn ix => for (~N, N, fn iy => let (* Convert grid locations to [-1,1] *) val x = toCoord(ix,N) val y = ~toCoord(iy,N) (* Apply the given random function *) val z = f(x,y) (* Convert the result to a grayscale value *) val iz = toIntensity(z) in (* Emit one byte for this pixel *) TextIO.output1(stream, Char.chr iz) end)) val _ = TextIO.closeOut stream in () end (* doGray : int * int * int Given a depth and two seeds for the random number generator, create a single random expression and convert it to a .pgm grayscale picture *) fun doGray (depth,seed1,seed2) = let (* Initialize random-number generator g *) val g = Random.rand(seed1,seed2) (* Generate a random expression and display it *) val e = build(depth,g) val _ = Expr.ppexpr e (* Apply the eval function to just the expression. The result is a function expecting the coordinate at which to evaluate the expression *) val f : (real * real -> real) = Expr.eval e (* 301 x 301 pixels *) val N = 150 (* Filename reflects the arguments used to create the function *) val basename = "gray" ^ Int.toString depth ^ "_" ^ Int.toString seed1 ^ "_" ^ Int.toString seed2 in (* Emit the picture *) emitGray(f, basename, N) end (* emitColor : (real*real->real) * (real*real->real) * (real*real->real) * string * int -> unit emitColor(f1, f2, f3, basename N) emits the values of the functions f1, f2, and f3 (converted to RGB intensities) to the output file basename.ppm for an 2N+1 by 2N+1 grid of points taken from [-1,1] x [-1,1]. See "man ppm" on knuth or wilkes for a full description of the format, but it's essentially a one-line header followed by three bytes (representing red, green, and blue values in the range 0..255) per pixel. *) fun emitColor (f1, f2, f3, basename, N) = let val stream = TextIO.openOut (basename ^ ".ppm") val N2P1 = N*2+1 val _ = TextIO.output(stream,"P6 " ^ (Int.toString N2P1) ^ " " ^ (Int.toString N2P1) ^ " 255\n") (* Nested loop: ix goes from ~N to N inclusive iy goes from ~N to N inclusive *) val _ = for (~N, N, fn ix => for (~N, N, fn iy => let (* Map grid locations into [-1,1] *) val x = toCoord(ix,N) val y = toCoord(iy,N) (* Apply the given random functions *) val z1 = f1(x,y) val z2 = f2(x,y) val z3 = f3(x,y) (* Convert the result to R,G,B values *) val ir = toIntensity(z1) val ig = toIntensity(z2) val ib = toIntensity(z3) in (* Emit three byte for this pixel *) TextIO.output1(stream, Char.chr ir); TextIO.output1(stream, Char.chr ig); TextIO.output1(stream, Char.chr ib) end)) val _ = TextIO.closeOut stream in () end (* doColor : int * int * int Given a depth and two seeds for the random number generator, create a single random expression and convert it to a color .ppm file (note the different extension from toGray) *) fun doColor (depth,seed1,seed2) = let (* Initialize random-number generator g *) val g = Random.rand(seed1,seed2) (* Generate a random expressions *) val e1 = build(depth,g) val e2 = build(depth,g) val e3 = build(depth,g) (* Display these expressions. *) val _ = (print "red = "; Expr.ppexpr e1; print "\n") val _ = (print "green = "; Expr.ppexpr e2; print "\n") val _ = (print "blue = "; Expr.ppexpr e3; print "\n") (* Apply the eval function to each expression. The result is still waiting for the coordinate at which to evaluate the expression, i.e., f1, f2, and f3 all have type real * real -> real *) val f1 = Expr.eval e1 val f2 = Expr.eval e2 val f3 = Expr.eval e3 (* Open the output file and write the header *) val N = 150 (* Filename reflects the arguments used to create the functions *) val basename = "color" ^ Int.toString depth ^ "_" ^ Int.toString seed1 ^ "_" ^ Int.toString seed2 in (* Emit the picture *) emitColor(f1, f2, f3, basename, N) end end