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