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
type format = Fli | Flc
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
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
type state = { palette : Bytes.t; pixels : Bytes.t; next_frame : int }
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
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 ;
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
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";
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";
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 =
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 _ -> () );
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)
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
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
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
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
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
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
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
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 ;
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 = 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