package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.graphics_fli/Fli.ml.html
Source file Fli.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Fli.mli *) type format = Fli | Flc type header = { format : format; width : int; height : int; frames : int; delay : float } (* the chunks' types *) let color_256 = 4 let delta_flc = 7 let color_64 = 11 let lc = 12 let black = 13 let brun = 15 let copy = 16 let frame_magic = 0xF1FA (*****************************************************************************) (* Reading *) (*****************************************************************************) let u8 (s : string) (i : int) : int = Char.code s.[i] let u16 (s : string) (i : int) : int = u8 s i lor (u8 s (i + 1) lsl 8) let u32 (s : string) (i : int) : int = u16 s i lor (u16 s (i + 2) lsl 16) let signed (v : int) : int = if v >= 128 then v - 256 else v (* the decoder's state after a frame: the palette (r g b, 256 of them) * and the pixels (a palette index each), and the frame next *) type state = { palette : Bytes.t; pixels : Bytes.t; next_frame : int } (* one chunk, at [at], applied to the palette and the pixels *) let apply (h : header) (s : string) (palette : Bytes.t) (pixels : Bytes.t) (typ : int) (at : int) : unit = let w = h.width in let body = at + 6 in let set x y v = Bytes.set pixels ((y * w) + x) (Char.chr v) in let blit_from p x y n = Bytes.blit_string s p pixels ((y * w) + x) n in if typ = color_256 || typ = color_64 then ( let p = ref (body + 2) and entry = ref 0 in for _ = 1 to u16 s body do entry := !entry + u8 s !p; let n = match u8 s (!p + 1) with 0 -> 256 | n -> n in p := !p + 2; for _ = 1 to n do for c = 0 to 2 do let v = u8 s (!p + c) in (* 0-63, the VGA's: stretched to 0-255, its top bits repeated *) let v = if typ = color_64 then (v lsl 2) lor (v lsr 4) else v in Bytes.set palette ((3 * !entry) + c) (Char.chr (v land 0xFF)) done; p := !p + 3; incr entry done done) else if typ = brun then ( let p = ref body in for y = 0 to h.height - 1 do incr p (* the packets' count: the line's width says when it ends *); let x = ref 0 in while !x < w do let n = signed (u8 s !p) in incr p; if n > 0 then ( for i = 0 to n - 1 do set (!x + i) y (u8 s !p) done; incr p; x := !x + n) else ( blit_from !p !x y (-n); p := !p - n; x := !x - n) done done) else if typ = lc then ( let y = ref (u16 s body) and p = ref (body + 4) in for _ = 1 to u16 s (body + 2) do let x = ref 0 and packets = u8 s !p in incr p; for _ = 1 to packets do x := !x + u8 s !p; let n = signed (u8 s (!p + 1)) in p := !p + 2; if n > 0 then ( blit_from !p !x !y n; p := !p + n; x := !x + n) else ( for i = 0 to -n - 1 do set (!x + i) !y (u8 s !p) done; incr p; x := !x - n) done; incr y done) else if typ = delta_flc then ( let y = ref 0 and p = ref (body + 2) in for _ = 1 to u16 s body do (* the opcodes before the line's packets: lines skipped, an odd * line's last pixel, and then the packets' count *) let rec packets () = let word = u16 s !p in p := !p + 2; if word land 0xC000 = 0xC000 then ( y := !y + (0x10000 - word); packets ()) else if word land 0xC000 = 0x8000 then ( set (w - 1) !y (word land 0xFF); packets ()) else word in let x = ref 0 in for _ = 1 to packets () do x := !x + u8 s !p; let n = signed (u8 s (!p + 1)) in p := !p + 2; if n > 0 then ( blit_from !p !x !y (2 * n); p := !p + (2 * n); x := !x + (2 * n)) else ( for i = 0 to -n - 1 do set (!x + (2 * i)) !y (u8 s !p); set (!x + (2 * i) + 1) !y (u8 s (!p + 1)) done; p := !p + 2; x := !x - (2 * n)) done; incr y done) else if typ = black then Bytes.fill pixels 0 (Bytes.length pixels) '\000' else if typ = copy then Bytes.blit_string s body pixels 0 (Bytes.length pixels) let of_string (s : string) : header * Movie.t = if String.length s < 128 then failwith "FLI: too short"; let format = match u16 s 4 with 0xAF11 -> Fli | 0xAF12 -> Flc | _ -> failwith "FLI: not an FLI or FLC file" in let frames = u16 s 6 in let width = match u16 s 8 with 0 -> 320 | w -> w and height = match u16 s 10 with 0 -> 200 | h -> h in let delay = match format with Fli -> float_of_int (u16 s 16) /. 70. | Flc -> float_of_int (u32 s 16) /. 1000. in let h = { format; width; height; frames; delay = Float.max 0.01 delay } in if frames = 0 then failwith "FLI: no frames"; (* where each frame is: the frame chunks, other chunks (an FLC's * prefix, 0xF100) skipped, the ring frame after the last not read *) let first = match format with Flc when u32 s 80 >= 128 && u32 s 80 < String.length s -> u32 s 80 | _ -> 128 in let rec walk at acc n = if n = frames || at + 16 > String.length s then List.rev acc else let size = u32 s at in if size < 16 then failwith "FLI: a chunk of no size" else if u16 s (at + 4) = frame_magic then walk (at + size) (at :: acc) (n + 1) else walk (at + size) acc n in let starts = Array.of_list (walk first [] 0) in if starts = [||] then failwith "FLI: no frames"; (* an FLC frame can have a delay of its own *) let delays = Array.map (fun at -> match format with Flc when u16 s (at + 8) > 0 -> float_of_int (u16 s (at + 8)) /. 1000. | _ -> h.delay) starts in let times = Array.make (Array.length starts) 0. in for i = 1 to Array.length starts - 1 do times.(i) <- times.(i - 1) +. delays.(i - 1) done; let duration = times.(Array.length starts - 1) +. delays.(Array.length starts - 1) in let start () = { palette = Bytes.make 768 '\000'; pixels = Bytes.make (width * height) '\000'; next_frame = 0 } in let next (st : state) : state * Rgba_image.t = (* the frame before, changed: copies, the state before kept intact *) let palette = Bytes.copy st.palette and pixels = Bytes.copy st.pixels in let at = starts.(st.next_frame) in let chunk = ref (at + 16) in (try for _ = 1 to u16 s (at + 6) do apply h s palette pixels (u16 s (!chunk + 4)) !chunk; chunk := !chunk + u32 s !chunk done with Invalid_argument _ -> () (* a chunk cut short: the frame as far as it went *)); let img = Rgba_image.create ~width ~height in for i = 0 to (width * height) - 1 do let c = 3 * Char.code (Bytes.get pixels i) in for k = 0 to 2 do img.rgba.{(4 * i) + k} <- Char.code (Bytes.get palette (c + k)) done; img.rgba.{(4 * i) + 3} <- 255 done; ({ palette; pixels; next_frame = st.next_frame + 1 }, img) in (h, Movie.sequential ~width ~height ~times ~duration ~start ~next) (*****************************************************************************) (* Writing *) (*****************************************************************************) (* the length of the run of equal values from [x], at most [limit] *) let run_length (get : int -> int) (x : int) (limit : int) : int = let n = ref 1 in while !n < limit && get (x + !n) = get x do incr n done; !n (* the values [x, e) as packets, each a count byte and its values: * a run of 2 or more as one value repeated, else literals up to where a * run of 3 starts; [repeat_sign] -1 when a run's count is negative * (LC, DELTA_FLC), 1 when the literals' are (BRUN); [prefix] a byte * before each packet (LC's and DELTA_FLC's column skip), [emit] one * value's bytes. Gives the packets' count. *) let packets (b : Buffer.t) ~(repeat_sign : int) ~(prefix : int -> int option) ~(emit : int -> unit) (get : int -> int) (x : int) (e : int) : int = let count = ref 0 and x = ref x in let add_prefix () = Option.iter (fun v -> Buffer.add_uint8 b v) (prefix !count) in while !x < e do let r = run_length get !x (min 127 (e - !x)) in add_prefix (); if r >= 2 then ( Buffer.add_uint8 b ((repeat_sign * r) land 0xFF); emit (get !x); x := !x + r) else ( let n = ref 1 in while !x + !n < e && !n < 127 && run_length get (!x + !n) (min 3 (e - !x - !n)) < 3 do incr n done; Buffer.add_uint8 b ((- repeat_sign * !n) land 0xFF); for i = 0 to !n - 1 do emit (get (!x + i)) done; x := !x + !n); incr count done; !count (* BRUN: every line, runs and literals *) let brun_chunk (w : int) (h : int) (cur : Bytes.t) : Buffer.t = let b = Buffer.create (w * h / 2) in for y = 0 to h - 1 do let line = Buffer.create w in let get x = Char.code (Bytes.get cur ((y * w) + x)) in let n = packets line ~repeat_sign:1 ~prefix:(fun _ -> None) ~emit:(Buffer.add_uint8 line) get 0 w in Buffer.add_uint8 b (min n 255); Buffer.add_buffer b line done; b (* the changed stretches of a line, [unit] pixels at a time (1: LC's * bytes, 2: DELTA_FLC's words, [get x] the value at column x), as * packets each preceded by its columns skipped -- a byte: a skip * longer than 254 is a packet copying one value unchanged, after 254 *) let changed_packets (b : Buffer.t) ~(unit : int) ~(width : int) (changed : int -> bool) (get : int -> int) ~(emit : int -> unit) : int = let count = ref 0 and x = ref 0 in let stop = width - (width mod unit) in while !x < stop do let x' = ref !x in while !x' < stop && not (changed !x') do x' := !x' + unit done; if !x' < stop then ( while !x' - !x > 254 do Buffer.add_uint8 b 254; Buffer.add_uint8 b 1; emit (get (!x + 254)); incr count; x := !x + 254 + unit done; let e = ref !x' in while !e < stop && changed !e do e := !e + unit done; let skip = !x' - !x in let n = packets b ~repeat_sign:(-1) ~prefix:(fun k -> Some (if k = 0 then skip else 0)) ~emit (fun k -> get (!x' + (k * unit))) 0 ((!e - !x') / unit) in count := !count + n; x := !e) else x := stop done; !count (* LC: from the first changed line to the last *) let lc_chunk (w : int) (h : int) (prev : Bytes.t) (cur : Bytes.t) : Buffer.t option = let at x y = Char.code (Bytes.get cur ((y * w) + x)) in let changed x y = Bytes.get cur ((y * w) + x) <> Bytes.get prev ((y * w) + x) in let line_changed y = List.exists (fun x -> changed x y) (List.init w Fun.id) in let lines = List.filter line_changed (List.init h Fun.id) in match lines with | [] -> None | y0 :: _ -> let y1 = List.fold_left max y0 lines in let b = Buffer.create 1024 in Buffer.add_uint16_le b y0; Buffer.add_uint16_le b (y1 - y0 + 1); for y = y0 to y1 do let line = Buffer.create w in let n = changed_packets line ~unit:1 ~width:w (fun x -> changed x y) (fun x -> at x y) ~emit:(Buffer.add_uint8 line) in if n > 255 then invalid_arg "Fli.to_string: a line of more than 255 packets"; Buffer.add_uint8 b n; Buffer.add_buffer b line done; Some b (* DELTA_FLC: the changed lines, by words, the unchanged ones skipped *) let delta_flc_chunk (w : int) (h : int) (prev : Bytes.t) (cur : Bytes.t) : Buffer.t option = let at x y = Char.code (Bytes.get cur ((y * w) + x)) in let pixel_changed x y = Bytes.get cur ((y * w) + x) <> Bytes.get prev ((y * w) + x) in let b = Buffer.create 1024 and lines = ref 0 and skipped = ref 0 in for y = 0 to h - 1 do if not (List.exists (fun x -> pixel_changed x y) (List.init w Fun.id)) then incr skipped else ( incr lines; if !skipped > 0 then Buffer.add_uint16_le b ((- !skipped) land 0xFFFF); skipped := 0; if w mod 2 = 1 && pixel_changed (w - 1) y then Buffer.add_uint16_le b (0x8000 lor at (w - 1) y); let line = Buffer.create w in let changed x = pixel_changed x y || pixel_changed (x + 1) y in let word x = at x y lor (at (x + 1) y lsl 8) in let n = changed_packets line ~unit:2 ~width:w changed word ~emit:(Buffer.add_uint16_le line) in Buffer.add_uint16_le b n; Buffer.add_buffer b line) done; if !lines = 0 then None else let chunk = Buffer.create (Buffer.length b + 2) in Buffer.add_uint16_le chunk !lines; Buffer.add_buffer chunk b; Some chunk (* a chunk: its size, its type, its body padded to an even size *) let add_chunk (b : Buffer.t) (typ : int) (body : Buffer.t) : unit = let pad = Buffer.length body mod 2 in Buffer.add_int32_le b (Int32.of_int (6 + Buffer.length body + pad)); Buffer.add_uint16_le b typ; Buffer.add_buffer b body; if pad = 1 then Buffer.add_uint8 b 0 let add_frame (b : Buffer.t) (chunks : (int * Buffer.t) list) : unit = let body = Buffer.create 4096 in List.iter (fun (typ, c) -> add_chunk body typ c) chunks; Buffer.add_int32_le b (Int32.of_int (16 + Buffer.length body)); Buffer.add_uint16_le b frame_magic; Buffer.add_uint16_le b (List.length chunks); Buffer.add_string b (String.make 8 '\000'); Buffer.add_buffer b body let to_string ?(format = Flc) ~(delay : float) (frames : Rgba_image.t list) : string = let first = match frames with [] -> invalid_arg "Fli.to_string: no frames" | f :: _ -> f in let w = first.width and h = first.height in (* the palette: every color met, in the order met *) let colors = Hashtbl.create 256 and palette = ref [] in let indexed (img : Rgba_image.t) : Bytes.t = if img.width <> w || img.height <> h then invalid_arg "Fli.to_string: frames of different sizes"; Bytes.init (w * h) (fun i -> let c = (img.rgba.{4 * i}, img.rgba.{(4 * i) + 1}, img.rgba.{(4 * i) + 2}) in match Hashtbl.find_opt colors c with | Some k -> Char.chr k | None -> let k = Hashtbl.length colors in if k = 256 then invalid_arg "Fli.to_string: more than 256 colors"; Hashtbl.add colors c k; palette := c :: !palette; Char.chr k) in let pixels = List.map indexed frames in let color_chunk = let b = Buffer.create 770 in Buffer.add_uint16_le b 1; Buffer.add_uint8 b 0; Buffer.add_uint8 b 0 (* 256 entries: the unused ones black *); let entries = Array.make 256 (0, 0, 0) in List.iteri (fun k c -> entries.(k) <- c) (List.rev !palette); Array.iter (fun (r, g, bl) -> List.iter (fun v -> Buffer.add_uint8 b (match format with Flc -> v | Fli -> v lsr 2)) [ r; g; bl ]) entries; ((match format with Flc -> color_256 | Fli -> color_64), b) in let body = Buffer.create 65536 in let frame_starts = ref [] in List.iteri (fun i cur -> frame_starts := (128 + Buffer.length body) :: !frame_starts; if i = 0 then add_frame body [ color_chunk; (brun, brun_chunk w h cur) ] else let prev = List.nth pixels (i - 1) in let delta = match format with Flc -> Option.map (fun c -> (delta_flc, c)) (delta_flc_chunk w h prev cur) | Fli -> Option.map (fun c -> (lc, c)) (lc_chunk w h prev cur) in add_frame body (Option.to_list delta)) pixels; let header = Bytes.make 128 '\000' in let total = 128 + Buffer.length body in Bytes.set_int32_le header 0 (Int32.of_int total); Bytes.set_uint16_le header 4 (match format with Fli -> 0xAF11 | Flc -> 0xAF12); Bytes.set_uint16_le header 6 (List.length frames); Bytes.set_uint16_le header 8 w; Bytes.set_uint16_le header 10 h; Bytes.set_uint16_le header 12 8; (match format with | Fli -> Bytes.set_uint16_le header 16 (max 1 (int_of_float (Float.round (delay *. 70.)))) | Flc -> Bytes.set_uint16_le header 14 3; Bytes.set_int32_le header 16 (Int32.of_int (int_of_float (Float.round (delay *. 1000.)))); let starts = Array.of_list (List.rev !frame_starts) in Bytes.set_int32_le header 80 (Int32.of_int starts.(0)); if Array.length starts > 1 then Bytes.set_int32_le header 84 (Int32.of_int starts.(1))); Bytes.to_string header ^ Buffer.contents body
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>