So how do you use Perlin's Simplex Noise? By using the GIF encoder, of course! I generated the test image shown in the last post:
with a minimal application of both modules in test.ml (source code follows). For a visually richer but not much more complicated example, I also tried replicating Iñigo Quílez's turbulent textures in fbm.ml. The animation traces a tiny loop in noise space so that it seamlessly repeats over and over. The first frame looks like this:
I'd like to know of a very easy way to share source tarballs with you (no, Forges require waiting for new project approval; no, Github requires adhering to a philosophy of sharing for which I don't care; no, Google Code forces me to give up my own version control; no, file hosting is not an option. I'm open to suggestions.); in the meantime, here's a wall of code with the complete project.
gif.mli
val fold_string : ('a -> char -> 'a) -> 'a -> string -> 'a type color = { red : int; green : int; blue : int; } type palette val palette : ?background:int -> color array -> palette val num_colors : palette -> int type t val make : ?ct:palette -> width:int -> height:int -> (t -> unit) -> (out_channel -> unit) val image : ?ct:palette -> ?transparent:int -> ?fps:int -> ?x:int -> ?y:int -> ?width:int -> ?height:int -> string -> t -> unit val comment : string -> t -> unit val repeat : ?count:int -> t -> unit val verify_gif : in_channel -> bool val grayscale : palette val heatmap : palette val rainbow : palette val black_white : palette val unwind : protect:('a -> unit) -> ('a -> 'b) -> ('a -> 'b) val with_output_file : (out_channel -> 'a) -> (string -> 'a) val with_input_file : (in_channel -> 'a) -> (string -> 'a)
gif.ml
let unwind ~(protect : 'a -> unit) f x = try let y = f x in protect x; y with e -> protect x; raise e class output otch = object (self) val buffer = String.create 255 val mutable current = -1 method private flush = if current > 0 then begin output_byte otch current; output otch buffer 0 current; current <- 0 end method byte n = let b = n land 255 in if current == -1 then output_byte otch b else begin if current == 255 then self#flush; buffer.[current] <- char_of_int b; current <- current + 1 end method le16 n = self#byte n; self#byte (n lsr 8) method string str = String.iter (fun c -> self#byte (int_of_char c)) str method begin_block = if current != -1 then failwith "begin_block"; current <- 0 method end_block = if current == -1 then failwith "end_block" else self#flush; current <- -1; self#byte 0 method close = if current != -1 then self#end_block; flush otch end let fold_string f e s = let res = ref e in String.iter (fun c -> res := f !res c) s; !res class lzw ?(bits=8) out = let count = 1 lsl bits in let clear_code = count and end_of_info = count + 1 in object (self) val table : (int * int, int) Hashtbl.t = Hashtbl.create 5003 val mutable code_size = 0 val mutable next_code = 0 val mutable buffer = 0 val mutable length = 0 method append n = buffer <- buffer lor ((n land pred (1 lsl code_size)) lsl length); length <- length + code_size; while length >= 8 do out#byte (buffer land 0xff); buffer <- buffer lsr 8; length <- length - 8 done method finish = if length > 0 then out#byte (buffer land pred (1 lsl length)) method private reset = Hashtbl.clear table; for i = 0 to count - 1 do Hashtbl.add table (-1, i) i done; code_size <- bits + 1; next_code <- count + 2 method private add_string s = (* Check code for overflow *) if next_code == 1 lsl code_size then code_size <- code_size + 1; Hashtbl.add table s next_code; next_code <- next_code + 1; (* Limit maximum code size to 12 bits *) if next_code == 0x1000 then begin self#append clear_code; self#reset; end method compress data = self#reset; self#append clear_code; let last = fold_string (fun prefix c -> let k = int_of_char c in try Hashtbl.find table (prefix, k) with Not_found -> self#append prefix; self#add_string (prefix, k); k) (-1) data in self#append last; self#append end_of_info; self#finish end (* 0 <= red, green, blue < 256 *) type color = { red : int; green : int; blue : int; } type palette = { background : int; bits : int; table : color array; } let num_colors { table; _ } = Array.length table let palette ?(background=0) table = let size = Array.length table in if size < 2 || size > 256 || size land (size - 1) != 0 || background < 0 || background >= size then invalid_arg "color_table" else let bits = truncate (log (float size) /. log 2.) in assert (size == 1 lsl bits); { background; bits; table = Array.copy table; } type t = { width : int; height : int; ct : palette option; out : output; } let header { out; _ } = out#string "GIF89a" let trailer { out; _ } = out#byte 0x3b let color_table { table; _ } { out; _ } = Array.iter (fun { red; green; blue } -> out#byte red ; out#byte green; out#byte blue) table let logical_screen ({ width; height; ct; out } as gif) = out#le16 width; out#le16 height; match ct with | None -> out#byte 0; out#byte 0; out#byte 0 (* default aspect ratio (1:1 = 49) *) | Some ({ background; bits; _ } as ct) -> out#byte (0xf0 lor pred bits); out#byte background; out#byte 0; color_table ct gif let make ?ct ~width ~height proc otch = unwind ~protect:(fun { out; _ } -> out#close) (fun gif -> header gif; logical_screen gif; proc gif; trailer gif) { width; height; ct; out = new output otch; } let graphics_control_extension transparent fps { out; _ } = let delay = if fps = 0 then 0 else (200 + fps) / (fps + fps) in out#byte 0x21; (* GIF Extension Code *) out#byte 0xf9; (* Graphic Control Label *) out#byte 0x04; (* Length of Data Sub-Block *) out#byte (match transparent with None -> 0 | Some _ -> 9); out#le16 delay; out#byte (match transparent with None -> 0 | Some c -> c); out#byte 0x00 (* Data Sub-Block Terminator *) let image_descriptor ct x y width height ({ out; _ } as gif) = out#byte 0x2c; out#le16 x; out#le16 y; out#le16 width; out#le16 height; match ct with | None -> out#byte 0x00 | Some ({ bits; _ } as ct) -> out#byte (0x80 lor pred bits); color_table ct gif let image ?ct ?transparent ?(fps=0) ?(x=0) ?(y=0) ?width ?height bytes ({ out; _ } as gif) = let w = match width with None -> gif.width | Some w -> w and h = match height with None -> gif.height | Some h -> h in if x < 0 || x + w > gif.width || y < 0 || x + h > gif.height || String.length bytes != w * h then invalid_arg "image"; let bits = match ct, gif.ct with | Some ct, _ | None , Some ct -> max 2 ct.bits | None , None -> invalid_arg "image" in (match transparent, fps with | None, 0 -> () | _ , _ -> graphics_control_extension transparent fps gif); image_descriptor ct x y w h gif; out#byte bits; out#begin_block; (new lzw ~bits out)#compress bytes; out#end_block let comment text { out; _ } = out#byte 0x21; (* GIF Extension Code *) out#byte 0xfe; (* Comment Label *) out#begin_block; out#string text; out#end_block let repeat ?(count=0) { out; _ } = out#byte 0x21; (* GIF Extension code *) out#byte 0xff; (* Application Extension Label *) out#byte 0x0b; (* Length of application block *) out#string "NETSCAPE"; (* Application Identifier *) out#string "2.0"; (* Appl. Authentication Code *) out#byte 0x03; (* Length of Data Sub-Block *) out#byte 0x01; (* Loop sub-block ID *) out#le16 count; (* Loop count (0 = forever) *) out#byte 0x00 (* Data Sub-Block Terminator *) let verify_gif inch = let failf fmt = Printf.kprintf failwith fmt in let check p fmt = if not p then failf fmt in let buffer = Buffer.create 16 in let input_le16 inch = let b0 = input_byte inch in let b1 = input_byte inch in b0 lor (b1 lsl 8) in let verify_header inch = let buf = String.create 6 in really_input inch buf 0 6; check (buf = "GIF89a") "Expected GIF header" in let verify_color_table len inch = let cnt = 3 * (1 lsl len) in for i = 1 to cnt do ignore (input_byte inch) done; Printf.printf "CT %d colors in %d bytes\n" (cnt / 3) cnt in let verify_blocks inch = let tot = ref 0 in Buffer.clear buffer; try while true do let cnt = input_byte inch in if cnt == 0 then raise Exit else Buffer.add_channel buffer inch cnt; incr tot done; assert false with Exit -> let contents = Buffer.contents buffer in Printf.printf "%d bytes in %d blocks, EOB = %010x\n%!" (String.length contents) !tot (pos_in inch); contents in let verify_logical_screen_descriptor inch = let width = input_le16 inch in let height = input_le16 inch in let fields = input_byte inch in let backgr = input_byte inch in let aspect = input_byte inch in Printf.printf "LSD w = %d, h = %d, f = %2x, b = %d, a = %d\n" width height fields backgr aspect; if fields land 0x80 == 0x80 then verify_color_table (1 + fields land 0x07) inch in let verify_image_descriptor inch = let left = input_le16 inch in let top = input_le16 inch in let width = input_le16 inch in let height = input_le16 inch in let fields = input_byte inch in Printf.printf "ID x = %d, y = %d, w = %d, h = %d, f = %2x\n" left top width height fields; if fields land 0x80 = 0x80 then verify_color_table (1 + fields land 0x07) inch in let verify_table_based_image inch = verify_image_descriptor inch; let bits = input_byte inch in Printf.printf "IMG code size = %d, " bits; ignore (verify_blocks inch) in let verify_plain_text_extension inch = check (12 == input_byte inch) "Expected block size = 12\n"; let left = input_le16 inch in let top = input_le16 inch in let width = input_le16 inch in let height = input_le16 inch in let _celwid = input_byte inch in let _celhgt = input_byte inch in let _fgcol = input_byte inch in let _bgcol = input_byte inch in Printf.printf "PTE l = %d, t = %d, w = %d, h = %d " left top width height; Printf.printf " \"%s\"\n%!" (verify_blocks inch) in let verify_application_extension inch = check (11 == input_byte inch) "Expected block size = 11\n"; let label = String.create 11 in really_input inch label 0 11; Printf.printf "AE %s " label; ignore (verify_blocks inch) in let verify_graphic_control_extension inch = check (4 == input_byte inch) "Expected block size = 4\n"; let fields = input_byte inch in let delay = input_le16 inch in let transp = input_byte inch in check (0x00 == input_byte inch) "Expected block terminator\n"; Printf.printf "GCE f = %2x, d = %d, t = %d\n" fields delay transp in let verify_comment_extension inch = Printf.printf "CE "; Printf.printf " \"%s\"\n%!" (verify_blocks inch) in let verify_extension inch = match input_byte inch with | 0xff -> verify_application_extension inch | 0xfe -> verify_comment_extension inch | 0xf9 -> verify_graphic_control_extension inch | 0x01 -> verify_plain_text_extension inch | b -> failf "Unknown extension introducer %2x" b in let verify_eof inch = try ignore (input_char inch); failf "Extra contents after EOF" with End_of_file -> () in let rec verify_data inch = match input_byte inch with | 0x3b -> verify_eof inch | 0x2c -> verify_table_based_image inch; verify_data inch | 0x21 -> verify_extension inch; verify_data inch | b -> failf "Unknown content block %2x" b in try verify_header inch; verify_logical_screen_descriptor inch; verify_data inch; true with Failure e -> let off = pos_in inch in Printf.printf "Offset %5d (%010x): %s\n%!" off off e; false | End_of_file -> let off = pos_in inch in Printf.printf "Offset %5d (%010x): Unexpected EOF\n%!" off off; false let grayscale = palette (Array.init 256 (fun i -> { red = i; green = i; blue = i })) let heatmap = let ramp i l h = let j = 255 * (i - l) / (h - l) in if j < 0 then 0 else if j > 255 then 255 else j in palette (Array.init 256 (fun i -> { red = ramp i 0 84; green = ramp i 85 170; blue = ramp i 171 255 })) let rainbow = let pi = 3.14159265358979324 and q1_3 = 0.333333333333333333 in let cs x = let c = cos (pi *. x) in truncate (255. *. c *. c +. 0.5) in palette (Array.init 256 (fun i -> let x = float i /. float 256 in { red = cs x; green = cs (x -. q1_3); blue = cs (x +. q1_3) })) let black_white = palette [| { red = 255; green = 255; blue = 255; }; { red = 0; green = 0; blue = 0; }; |] let with_output_file proc fname = unwind ~protect:close_out proc (open_out_bin fname) let with_input_file proc fname = unwind ~protect:close_in proc (open_in_bin fname)
noise.mli
val noise : float * float * float -> float val fBM : ?octaves:int -> float * float * float -> float (** Clamp an integer into the range [0, 255] *) val clampb : int -> int (** Convert a float in the range [-1, 1] into an integer in the range [0, 255] *) val rescale : float -> int
noise.ml
let patterns = [| 0o25; 0o70; 0o62; 0o54; 0o15; 0o23; 0o07; 0o52 |] let btst n b = (n lsr b) land 1 let bmix i j k b = patterns.((btst i b lsl 2) lor (btst j b lsl 1) lor (btst k b)) let shuffle (i, j, k) = bmix i j k 0 + bmix j k i 1 + bmix k i j 2 + bmix i j k 3 + bmix j k i 4 + bmix k i j 5 + bmix i j k 6 + bmix j k i 7 let magnitude h (x, y, z) = let p, q, r = match h land 7 with | 0 -> z , x , y | 1 -> x , y , 0. | 2 -> y , z , 0. | 3 -> z , x , 0. | 4 -> z , x , y | 5 -> x , 0., z | 6 -> y , 0., x | 7 -> z , 0., y | _ -> assert false in match (h lsr 3) land 7 with | 0 -> -. p -. q +. r | 1 -> +. p -. q -. r | 2 -> -. p +. q -. r | 3 -> +. p +. q +. r | 4 -> +. p +. q -. r | 5 -> -. p +. q +. r | 6 -> +. p -. q +. r | 7 -> -. p -. q -. r | _ -> assert false let simplices = [| [| (0,0,0); (1,0,0); (1,1,0); (1,1,1) |]; [| (0,0,0); (1,0,0); (1,0,1); (1,1,1) |]; [| (0,0,0); (0,1,0); (1,1,0); (1,1,1) |]; [| (0,0,0); (0,1,0); (0,1,1); (1,1,1) |]; [| (0,0,0); (0,0,1); (1,0,1); (1,1,1) |]; [| (0,0,0); (0,0,1); (0,1,1); (1,1,1) |] |] let permindex (u, v, w) = if u >= w then if u >= v then if v >= w then 0 else 1 else 2 else if v >= w then 3 else if u >= v then 4 else 5 let int x = if x < 0. then pred (truncate x) else truncate x let skew (x, y, z) = let s = (x +. y +. z) /. 3. in let i = int (x +. s) and j = int (y +. s) and k = int (z +. s) in (i, j, k) let unskew (x, y, z) (i, j, k) = let s = float (i + j + k) /. 6. in let u = x -. float i +. s and v = y -. float j +. s and w = z -. float k +. s in (u, v, w) let norm2 (x, y, z) = x *. x +. y *. y +. z *. z let addi3 (i, j, k) (i', j', k') = (i + i', j + j', k + k') let noise p = let l = skew p in let x = unskew p l in let s = simplices.(permindex x) in let f = ref 0. in for i = 0 to 3 do let v = s.(i) in let y = unskew x v in let t = 0.6 -. norm2 y in if t > 0. then let h = shuffle (addi3 l v) in let t = t *. t in f := !f +. 8. *. t *. t *. magnitude h y done; !f let fBM ?(octaves=5) = let rec go f w i (x, y, z as p) = if i == 0 then f else let f = f +. w *. noise p in go f (0.5 *. w) (pred i) (2. *. x, 2. *. y, 2. *. z) in go 0. 1. octaves let clampb n = (n lor ((255-n) asr (Sys.word_size-2))) land lnot (n asr (Sys.word_size-2)) land 255 let rescale f = clampb (int (0.5 +. ldexp (f +. 1.) 7))
test.ml
let () = let width = 256 and height = 256 in let pixels = String.create (width * height) in let x0, y0, z0 = -2., -2., 0. and sc = 4.0 in Gif.with_output_file (Gif.make ~ct:Gif.grayscale ~width ~height (fun gif -> for i = 0 to height - 1 do let y = y0 +. sc *. float i /. float height in for j = 0 to width - 1 do let x = x0 +. sc *. float j /. float width in let p = Noise.noise (x, y, z0) in pixels.[i * width + j] <- char_of_int (Noise.rescale p) done done; Gif.image pixels gif)) "perlin.gif"
fbm.ml
let ( +/ ) (x0, y0, z0) (x1, y1, z1) = (x0 +. x1, y0 +. y1, z0 +. z1) let ( */ ) k (x, y, z) = (k *. x, k *. y, k *. z) let two_pi = 6.283185307179586476925286766552 let () = let count = 100 in let width = 256 and height = 256 in let pixels = String.create (width * height) in Gif.with_output_file (Gif.make ~ct:Gif.grayscale ~width ~height (fun gif -> if count > 1 then Gif.repeat gif; let sc = 1.0 in for c = 0 to count - 1 do let q = two_pi *. float c /. float count in let sq = 0.05 *. sin q and cq = 0.05 *. cos q in for i = 0 to height - 1 do let y = sc *. float i /. float height in for j = 0 to width - 1 do let x = sc *. float j /. float width in let p = (x, y, 0.) in let q = p +/ 2. */ ( Noise.fBM (p +/ ( cq, sq, 0.)), Noise.fBM (p +/ (-. sq, cq, 0.)), Noise.fBM (p +/ ( 0., 0., 1.))) in let r = p +/ 2. */ ( Noise.fBM (q +/ ( cq, 0., sq)), Noise.fBM (q +/ ( 0., 1., 0.)), Noise.fBM (q +/ (-. sq, 0., cq))) in let f = 2. *. Noise.fBM r in pixels.[i * width + j] <- char_of_int (Noise.rescale f) done done; Printf.printf "%d/%d\n%!" (succ c) count; Gif.image ~fps:25 pixels gif done)) "fbm.gif"
Makefile
# :mode=makefile: OCAMLC= ocamlfind ocamlc -w Aelz -g OCAMLOPT= ocamlfind ocamlopt -w Aelz -unsafe -inline 1000 -ccopt -O2 -ccopt -fomit-frame-pointer OCAMLLEX= ocamllex.opt OCAMLYACC= ocamlyacc OCAMLDEP= ocamldep $(PP) OCAMLMKLIB=ocamlmklib #-custom -linkall COMN=\ gif.ml noise.ml SRCS=\ $(COMN) test.ml fbm.ml OBJS=$(COMN:.ml=.cmo) LIBS= PROGRAMS=\ test fbm all: $(PROGRAMS) $(PROGRAMS:=.bc) fbm.bc: $(OBJS) fbm.cmo $(OCAMLC) -o $@ $(LIBS) $^ fbm: $(OBJS:.cmo=.cmx) fbm.cmx $(OCAMLOPT) -o $@ $(LIBS:.cma=.cmxa) $^ test.bc: $(OBJS) test.cmo $(OCAMLC) -o $@ $(LIBS) $^ test: $(OBJS:.cmo=.cmx) test.cmx $(OCAMLOPT) -o $@ $(LIBS:.cma=.cmxa) $^ clean: rm -f *.cm[iox] *.o *.a *~ distclean: clean rm -f $(PROGRAMS) $(PROGRAMS:=.bc) *.gif .SUFFIXES: .cmo .cmi .cmx .ml .mli .mll .mly .mly.ml: .mly.mli: $(OCAMLYACC) $< .mll.ml: $(OCAMLLEX) $< .ml.cmo: $(OCAMLC) -c $< .mli.cmi: $(OCAMLC) -c $< .ml.cmx: $(OCAMLOPT) -c $< .depend: $(OCAMLDEP) $(SRCS) > .depend include .depend
4 comments:
Use http://gitorious.org/ ?
Thanks for posting the complete example code.
If you just want to share a tarball of the source then perhaps dropbox would be the way to go?
github forces you to adhere to what? I do not remember having to pass a phylosophy check when registering my account but maybe they changed their policy since then? In that case, gitorious is still free to use.
@Cedric: I profoundly dislike git. If it works for you, more power to you. In my case, I don't need process, I don't need source control and I don't need collaboration tools. I need a code sharing facility. I'm not going to adopt a radically new process to share four source files.
Post a Comment