Source file imageGIF.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
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
open Stdlib
open ImageUtil
open Image
type decoder_state =
{
carry_bits : int ;
consumed_symbols_of_current_code : int ;
consumed_symbols : int ;
current_partial_symbol : int ;
code_size : int ;
min_code_size : int ;
}
let [@inline always] calc_clear_code lzw_min_size =
1 lsl (lzw_min_size -1)
let bin_of_str ~clear_code (t:decoder_state) str =
let code_size = ref t.code_size in
let eoi_code = clear_code + 1 in
let len = String.length str in
let i = ref 0 in
let acc = ref [] in
let ctr = ref t.carry_bits in
let codectr = ref t.consumed_symbols_of_current_code in
let symctr = ref t.consumed_symbols in
let carry = ref t.current_partial_symbol in
while !i <> len do
let bit_offset = ref 0 in
while !bit_offset <> 8 do
let this_bit = (Char.code str.[!i] lsr !bit_offset) land 1 in
carry := !carry lor (this_bit lsl !ctr) ;
incr ctr ;
if !ctr = !code_size then begin
if !carry = eoi_code then begin
i := len -1 ;
bit_offset := 8 -1 ;
end ;
acc := !carry::!acc ;
incr symctr ; incr codectr; ctr := 0 ;
if !carry = clear_code then begin
code_size := t.min_code_size ;
symctr := 0;
codectr := 1;
end ;
carry := 0
end ;
if !codectr = calc_clear_code !code_size then
(if !code_size < 12 then (incr code_size) ; codectr := 0) ;
incr bit_offset
done ;
incr i ;
done ;
let symbols = List.rev !acc in
match !acc with
| hd::_ when hd = eoi_code ->
`Complete symbols
| _ ->
let state =
{ carry_bits = !ctr ;
consumed_symbols_of_current_code = !codectr ;
consumed_symbols = !symctr ;
current_partial_symbol = !carry;
code_size = !code_size;
min_code_size = t.min_code_size ;
}
in
`Partial (state, symbols)
module ReadGIF : sig
include ReadImage
include ReadImageStreaming
end = struct
let extensions = ["gif"]
let [@inline always] uint16le ?(off=0) buf =
((int_of_char buf.[off+1]) lsl 8) lor (int_of_char buf.[off])
let (ich:ImageUtil.chunk_reader) =
let magic = get_bytes ich 3 in
if magic <> "GIF" then
raise (Corrupted_image "GIF signature expected...");
let version = get_bytes ich 3 in
if version <> "87a" && version <> "89a" then
raise (Corrupted_image "Version of GIF not supported...");
let width = get_bytes ich 2 |> uint16le in
let height = get_bytes ich 2 |> uint16le in
if width = 0 || height = 0 then
raise (Corrupted_image "Invalid global GIF dimensions") ;
let packed = chunk_byte ich in
let global_color_table = (packed lsr 7 = 1) in
let bgcol = chunk_byte ich in
if bgcol <> 0 && not global_color_table then
raise (Corrupted_image
"global background color but no global color table");
let pixar = chunk_byte ich in
let size_glob_col_tbl = packed land 0b111 in
{
version = version ;
image_size = width , height ;
global_color_table ;
color_resolution = (packed lsr 4) mod 8 ;
sort = ((packed lsr 3) mod 2 = 1) ;
size_glob_col_tbl ;
bg_color_index = bgcol ;
pix_aspect_ratio = pixar
}
let size ich =
let hdr = read_header ich in
ImageUtil.close_chunk_reader ich;
hdr.image_size
module Dict : sig
type instance
type key = int
val clear : instance -> instance
val empty : color_table_size:int -> lzw_min_size:int -> instance
val next_code : instance -> key -> int -> instance
val retrieve_first : instance -> int -> int option
val retrieve_entry : instance -> int -> (int * int list) option
val last_entry : instance -> int list
val redim : instance -> color_table_size:int -> clear_code:int -> instance
end = struct
type key = int
type instance = {
t : (int * int list) array;
cardinal : int ;
color_table_size : int ;
clear_code : int ;
eoi_code : int
}
let clear_array () =
Array.init (4096) (fun i -> i, [])
let clear t = { t with t = clear_array(); cardinal = 0; }
let redim t ~color_table_size ~clear_code =
{t with color_table_size ;
clear_code ;
eoi_code = clear_code + 1;
}
let empty ~color_table_size ~lzw_min_size =
{ t = clear_array() ;
cardinal = 0;
color_table_size ;
clear_code = calc_clear_code lzw_min_size ;
eoi_code = (calc_clear_code lzw_min_size) + 1 ;
}
let retrieve_first dict symbol =
match dict.t.( symbol ) with
| _, [] -> None
| first, _ -> Some first
let retrieve_entry dict symbol =
match dict.t.( symbol ) with
| _, [] -> None
| first, lst -> Some (first, List.rev lst)
let last_entry dict =
dict.t.(dict.eoi_code + dict.cardinal -1) |> snd |> List.rev
let next_code (t:instance) (key:key) (v:int) : instance =
let entry = match t.t.(key) with
| _, [] ->
assert (key < t.color_table_size) ;
assert (key <> t.clear_code);
assert (key <> t.eoi_code);
key, [v; key]
| first, lst -> first, v::lst
in
let new_key = (t.eoi_code + t.cardinal) in
if new_key = 4096 then begin
t
end else begin
assert ([] = (snd t.t.(new_key)));
t.t.(new_key) <- entry ;
{t with cardinal = succ t.cardinal }
end
end
type image_descriptor_state = {
must_clear : bool;
dict : Dict.instance ;
x : int ;
y: int ;
previous_symbol : int ;
clear_code : int ;
eoi_code : int ;
lzw_code_size : int ;
decoder : decoder_state ;
}
type read_state =
{ header : gif_header_data ;
gct : string option ;
compression_dict : Dict.instance option ;
buffer : image ;
transparency_index : int;
display_time : int;
}
let process_image_descriptor_subblock ~transparency_index
~color_table ~color_table_size
~image ~lzw_min_size original_state subblock =
let clear_code = calc_clear_code lzw_min_size in
let eoi_code = clear_code + 1 in
let used_coord = ref false in
let [@inline always] next_coord ({x=old_x ; y = old_y; _ } as state) =
assert (!used_coord);
let x, y =
if old_x + 1 = image.width
then 0, old_y+1
else old_x+1, old_y
in
used_coord := false;
let state =
if x >= image.width then
raise (Corrupted_image "blew x dimension");
if y > image.height then
raise (Corrupted_image "blew y dimension");
if y = image.height && x > 0 then
raise (Corrupted_image "blew past end position");
{state with x; y }
in
if state.x >= image.width || state.y >= image.height
|| state.x < 0 || state.y < 0 then
used_coord := true;
state
in
let [@inline always] emit_pixel image x y symbol =
assert (symbol <> clear_code);
assert (symbol <> eoi_code);
if (!used_coord) then begin
invalid_arg @@ Printf.sprintf "EMIT x:%d/%d y:%d/%d sym:%d min:%d\n"
x image.width y image.height symbol lzw_min_size
end;
assert (not !used_coord);
used_coord := true;
let offset = symbol * 3 in
write_rgba image x y
(Char.code color_table.[offset ])
(Char.code color_table.[offset +1])
(Char.code color_table.[offset +2])
(if symbol = transparency_index then 0 else 0xff)
in
let [@inline always] process_symbols process_state symbols =
let [@inline always] rec loop state = function
| [] -> state
| hd::tl ->
begin match hd with
| (symbol:int) when symbol = clear_code ->
{state with previous_symbol = symbol ;
must_clear = false ;
dict = Dict.clear state.dict}
| symbol when state.must_clear ->
raise (Corrupted_image (Printf.sprintf
"Doesn't start with clear code (%#x): %#x (dict:%d)"
clear_code symbol
(Dict.last_entry state.dict |> List.length)))
| symbol when symbol = eoi_code ->
{state with previous_symbol = symbol}
| symbol when state.previous_symbol = clear_code ->
if (symbol >= color_table_size) then
raise (Corrupted_image (
Printf.sprintf "GIF: %s: symbol:%d >= color_table_size:%d"
__LOC__ symbol color_table_size)) ;
emit_pixel image state.x state.y symbol ;
let state = next_coord state in
{state with
previous_symbol = symbol ;
dict = Dict.next_code Dict.(clear state.dict) 0 symbol}
| symbol when symbol <= color_table_size ->
emit_pixel image state.x state.y symbol ;
let state = next_coord state in
{state with
previous_symbol = symbol ;
dict = Dict.next_code state.dict state.previous_symbol symbol}
| coded_symbol ->
begin match Dict.retrieve_entry state.dict coded_symbol with
| Some (first, lst) ->
let state = List.fold_left (fun state symbol ->
emit_pixel image state.x state.y symbol ;
next_coord state
) (state) lst in
{state with
previous_symbol = coded_symbol ;
dict = Dict.next_code state.dict
state.previous_symbol first}
| None ->
let first =
match Dict.retrieve_first state.dict
state.previous_symbol with
| Some first -> first
| None ->
assert (state.previous_symbol <= color_table_size);
state.previous_symbol
in
let next_dict = Dict.next_code state.dict
state.previous_symbol first in
let state = {state with dict = next_dict} in
let state = List.fold_left (fun state symbol ->
emit_pixel image state.x state.y symbol ;
next_coord state
) state (Dict.last_entry next_dict) in
{state with previous_symbol = coded_symbol }
end
end |> fun state ->
loop state tl
in loop process_state symbols
in
match bin_of_str ~clear_code:original_state.clear_code
original_state.decoder subblock with
| `Complete symbols ->
begin match process_symbols original_state symbols with
| state when state.previous_symbol = eoi_code ->
if state.x = 0 && state.y = image.height
then (`Complete state.dict)
else raise (Corrupted_image
"GIF decoder produced image of unexpected dimensions")
| _ -> raise (Corrupted_image "no EOI at end")
end
| `Partial (decoder, symbols) ->
let state = {original_state with decoder} in
begin match process_symbols state symbols with
| state when state.previous_symbol = eoi_code ->
raise (Corrupted_image "premature EOI in partial")
| state -> `Partial state
end
let process_image_descriptor_block (state:read_state) ich =
let original_compression_dict = state.compression_dict in
let block = get_bytes ich (4+4+1+1) in
let left = uint16le ~off:0 block in
let top = uint16le ~off:2 block in
let width = uint16le ~off:4 block in
let height = uint16le ~off:6 block in
let flags = Char.code block.[8] in
let local_color_table_size = 2 lsl (flags land 0b111) in
let lzw_min_size = Char.code block.[9] in
let lzw_min_size = lzw_min_size + 1 in
if let global_width, global_height = state.header.image_size in
width = 0 || height = 0
|| (left + width > global_width)
|| (top + height > global_height) then
raise (Corrupted_image "Invalid image descriptor block dimensions");
if lzw_min_size < 3 || lzw_min_size > 12 then
raise (Corrupted_image (Printf.sprintf "Invalid LZW minimum code size %d"
lzw_min_size)) ;
let implemented_flags =
0x80
lor 0b111
in
if (0 <> flags land 0x40) then
raise (Not_yet_implemented "GIF interlace feature flag") ;
if 0 <> flags land (lnot implemented_flags) then
raise (Not_yet_implemented
"Unsupported ImageDescriptor feature flag(s)") ;
if not state.header.global_color_table && (0 = flags land 0x80) then
raise (Corrupted_image "No color table available for GIF fragment") ;
let color_table, color_table_size =
if 0 <> flags land 0x80
then get_bytes ich (3 * local_color_table_size),
local_color_table_size
else match state.gct with
| None -> raise (Corrupted_image
"GIF: No global color table, and no local either")
| Some gct -> gct, 2 lsl state.header.size_glob_col_tbl
in
let image = create_rgb ~alpha:true ~max_val:255 width height in
let [@inline always] rec process_subblock acc =
match chunk_byte ich, acc with
| 0, `Initial ->
raise (Corrupted_image
"GIF image descriptor block contained single empty subblock")
| 0, `Partial (_:image_descriptor_state) ->
raise
(Corrupted_image "GIF image descriptor block not enough subblocks")
| 0, `Complete dict -> dict
| ch, `Complete _dict ->
raise (Corrupted_image
(Printf.sprintf "complete but not really %#x" ch))
| encoded_size, (`Initial | `Partial _ as descriptor_state) ->
let descriptor_state =
match descriptor_state with
| `Partial d_state -> d_state
| `Initial ->
let clear_code = calc_clear_code lzw_min_size in
{ previous_symbol = clear_code ;
clear_code ;
eoi_code = clear_code +1 ;
lzw_code_size = lzw_min_size ;
must_clear = true ;
dict = (match original_compression_dict with
| None -> Dict.empty ~color_table_size ~lzw_min_size
| Some dict -> Dict.redim dict ~color_table_size ~clear_code) ;
x = 0; y = 0;
decoder = {
carry_bits = 0 ;
consumed_symbols_of_current_code = 0;
consumed_symbols = 0;
current_partial_symbol = 0;
code_size = lzw_min_size ;
min_code_size = lzw_min_size ;
}
} in
let subblock = get_bytes ich encoded_size in
let descriptor_state =
process_image_descriptor_subblock
~transparency_index:state.transparency_index
~color_table ~color_table_size ~image ~lzw_min_size
descriptor_state subblock in
process_subblock descriptor_state
in
let dict = process_subblock `Initial in
dict, (image, left, top)
let fill_background_color image color_table bg_color_index =
let c color_channel = int_of_char
(color_table.[bg_color_index + color_channel]) in
Image.fill_rgb image (c 0) (c 1) (c 2)
let read_streaming ich (gif_state:read_state option)
: image option * int * read_state option =
let gif_state : read_state =
begin match gif_state with
| None ->
let = read_header ich in
let buffer =
let w,h = header.image_size in
create_rgb ~alpha:true ~max_val:255 w h in
let gct_size =
1 lsl (succ header.size_glob_col_tbl) in
let gct_bytesize = gct_size * 3 in
let gct =
match header.global_color_table with
| false -> None
| true ->
let gct = get_bytes ich gct_bytesize in
if header.bg_color_index * 3 > String.length gct then
raise (Image.Corrupted_image
"GIF: bg_color_index larger than GlobalColorTable");
fill_background_color buffer gct header.bg_color_index ;
Some gct
in
{ header; gct; compression_dict = None ;
transparency_index = -1 ;
display_time = 0;
buffer ;
}
| Some state -> state
end in
Printexc.record_backtrace true;
let [@inline always] rec parse_blocks gif_state
: image option * int * read_state option =
match (get_bytes ich 1).[0] with
| '\x3b' -> None, gif_state.display_time, None
| '\x21' ->
let extension_label = chunk_byte ich in
begin match extension_label with
| 0xf9 ->
let len = chunk_byte ich in
assert (len = 4);
let body = get_bytes ich (len+1) in
if body.[len] <> '\x00' then
raise (Corrupted_image "GCE: missing end of block") ;
let packed = int_of_char body.[0] in
let display_time =
uint16le ~off:1 body in
let use_transparent_color = (packed land 1) = 1 in
let transparency_index =
match use_transparent_color with
| true -> int_of_char body.[3]
| false -> -1
in
let user_input_flag = (packed lsr 1) land 1 in
begin if 0 <> packed lsr 5 then
raise (Corrupted_image "GIF: Reserved GIF89a bit set.")
end ;
let graphics_disposal_method = match (packed lsr 2) land 0b111 with
| 0 ->
()
| 1 ->
fill_alpha gif_state.buffer 0xff;
()
| 2 -> Printf.printf "overwrite graphic with background color\n%!"
;
(match gif_state.gct with
| None ->
fill_background_color gif_state.buffer
"\000\000\000" 0
| Some gct ->
fill_background_color gif_state.buffer
gct gif_state.header.bg_color_index) ;
| 4 ->
Image.fill_alpha gif_state.buffer 0xFF
| _ -> raise @@ Corrupted_image
("GIF: Graphics Disposal Method multiple bits set")
in
let _TODO = user_input_flag, graphics_disposal_method in
let gif_state = {gif_state with transparency_index ;
display_time} in
parse_blocks gif_state
| 0xff ->
if chunk_byte ich <> 0x0b then
raise (Corrupted_image "GIF ApplicationExtension length not 0x0B");
let identifier = get_bytes ich 8 in
let authentcode = get_bytes ich 3 in
let subblock_len = chunk_byte ich in
begin match identifier, authentcode, subblock_len with
| "NETSCAPE", "2.0", 3
| "ANIMEXTS", "1.0", 3 ->
let subblock = get_bytes ich subblock_len in
if subblock.[0] <> '\x01' then
raise @@ Corrupted_image
(Printf.sprintf
"GIF NETSCAPE 2.0 block unknown sub-block ID") ;
let _loop_count = uint16le ~off:1 subblock in
let terminator = chunk_byte ich in
if terminator <> 0 then
raise @@ Corrupted_image
(Printf.sprintf
"GIF ApplicationExtension subblock terminator \
expected 0x00, got %#x" terminator)
| "ICCRGBG1", "012", n
| "ImageMag", "ick", n
| "MGK8BIM0", "000", n
| "MGKIPTC0", "000", n
| ( "ZGATEXTI" | "ZGATILEI"
| "ZGACTRLI" | "ZGAVECTI"), "\x35\x00\x00", n
| ( "ZGANPIMG" | "ZGAALPHA"), "I5\x00", n
| ( "ZGATITLE" | "ZGATEXTI"), "4.0", n
| "fract", "int", n
| "XMP Data", "XMP", n ->
let rec skip = function
| 0 -> ()
| n -> let _ = get_bytes ich (n) in
skip (chunk_byte ich)
in skip n
| _ ->
raise @@ Corrupted_image
(Printf.sprintf
"Unknown GIF ApplicationExtension %S:%S [len %d]"
identifier authentcode subblock_len)
end ;
parse_blocks gif_state
| unknown -> raise @@ Not_yet_implemented
(Printf.sprintf "Unknown GIF Extension %#x" unknown)
end
| '\x2c' ->
let dict, (image,left,top) = process_image_descriptor_block gif_state ich in
let gif_state = {gif_state with
compression_dict = Some dict ;
buffer = Image.copy gif_state.buffer} in
let buffer = gif_state.buffer in
assert (image.height <= buffer.height) ;
assert (image.width <= buffer.width) ;
assert (left <= buffer.width) ;
assert (top <= buffer.height) ;
for x = 0 to image.width -1 do
if x+left < 0 || x+left >= buffer.width then () else
for y = 0 to image.height -1 do
if y+top < 0 || y+top >= buffer.height then () else
Image.read_rgba image x y
(fun r g b -> function
| 0x00 -> begin match gif_state.buffer.pixels with
| RGBA (_,_,_,aa) ->
Pixmap.set aa (left+x) (top+y) 0x00
| _ -> failwith "gif_state.buffer.pixels is not rgba"
end
| a ->
Image.write_rgba gif_state.buffer (left+x) (top+y) r g b a)
done
done ;
Some gif_state.buffer, gif_state.display_time, Some gif_state
| c -> raise (Not_yet_implemented
(Printf.sprintf "unknown block type %C" c))
in
parse_blocks gif_state
let parsefile cr = ImageUtil.parsefile_of_read_streaming ~read_streaming cr
end
let write (cw:chunk_writer) (original_image:image) =
if original_image.height > 0xffff || original_image.width > 0xffff then
raise @@ Invalid_argument ("Image dimensions too large for GIF") ;
let module ColorTable : Hashtbl.S with type key = int =
Hashtbl.Make(struct
type t = int
let [@inline] equal (a:int) (b:int) = a = b
let [@inline] hash a = a
end) in
let color_count = ColorTable.create 256 in
let packed_plane =
let [@inline always] pack_int r g b =
b lor (g lsl 8) lor (r lsl 16) in
let [@inline always] register_count packed =
try ColorTable.replace color_count packed
(succ @@ ColorTable.find color_count packed) with
| Not_found -> ColorTable.add color_count packed 1 in
let open Bigarray in
match original_image.pixels with
| Grey (Pix8 old_plane) ->
let open Bigarray in
let width = Array2.dim1 old_plane in
let height = Array2.dim2 old_plane in
let target_plane = Array2.create Int C_layout width height in
for x = 0 to width -1 do
for y = 0 to height -1 do
let v = Array2.get old_plane x y in
let v_r = ((v*77) lsr 8)
and v_g = ((v*151) lsr 8)
and v_b = ((v*28) lsr 8) in
let packed = pack_int v_r v_g v_b in
register_count packed ;
Array2.set target_plane x y packed
done
done ;
target_plane
| Grey (Pix16 old_plane) ->
let width = Array2.dim1 old_plane in
let height = Array2.dim2 old_plane in
let target_plane = Array2.create Int C_layout width height in
for x = 0 to width -1 do
for y = 0 to height -1 do
let v = Array2.get old_plane x y in
let v_r = (v*77) lsr 16
and v_g = (v*151) lsr 16
and v_b = (v*28) lsr 16 in
let packed = pack_int v_r v_g v_b in
register_count packed ;
Array2.set target_plane x y packed
done
done ;
target_plane
| RGB (Pix16 old_red, Pix16 old_green, Pix16 old_blue) ->
let width = Array2.dim1 old_red in
let height = Array2.dim2 old_red in
let target_plane = Array2.create Int C_layout width height in
for x = 0 to Array2.dim1 old_red -1 do
for y = 0 to Array2.dim2 old_red -1 do
let v_r = (Array2.get old_red x y) lsr 8 in
let v_g = (Array2.get old_green x y) lsr 8 in
let v_b = (Array2.get old_blue x y) lsr 8 in
let packed = pack_int v_r v_g v_b in
register_count packed ;
Array2.set target_plane x y packed
done
done ;
target_plane
| RGB (Pix8 red, Pix8 green, Pix8 blue) ->
let width = Array2.dim1 red in
let height = Array2.dim2 red in
let target_plane = Array2.create Int C_layout width height in
for x = 0 to width -1 do
for y = 0 to height -1 do
let packed = pack_int
(Array2.get red x y)
(Array2.get green x y)
(Array2.get blue x y) in
register_count packed ;
Array2.set target_plane x y packed
done
done ;
target_plane
| RGB _ ->
raise (Corrupted_image
"Something is wrong, color planes of different resolution")
| GreyA _
| RGBA _ ->
raise (Not_yet_implemented "GIF does not yet support transparency")
in
let color_table_shift =
let rec loop bits = function
| 0 -> max 1 bits
| v -> loop (succ bits) (v lsr 1)
in
let bits = loop 0 (ColorTable.length color_count-1) in
bits
in let color_table_size = 1 lsl color_table_shift in
assert (color_table_size >= ColorTable.length color_count);
let [@inline always] pack_uint16le v =
let buf = Bytes.create 2 in
Bytes.unsafe_set buf 0 @@ Char.chr (v land 0xff) ;
Bytes.unsafe_set buf 1 @@ Char.chr ((v land 0xff00) lsr 8) ;
Bytes.unsafe_to_string buf
in
let flags =
0xf0
lor (0b111 land (color_table_shift-1))
in
let lzw_min_size = 1 + color_table_shift in
chunk_write cw "GIF89a";
chunk_write cw @@ pack_uint16le original_image.width ;
chunk_write cw @@ pack_uint16le original_image.height ;
chunk_write_char cw @@ Char.chr flags ;
chunk_write_char cw '\x00' ;
chunk_write_char cw '\x00' ;
let color_table = ColorTable.create color_table_size in
let color_table_inv = ColorTable.create color_table_size in
let () =
for i = 0 to color_table_size do
ColorTable.replace color_table i "\x34\x33\000"
done ;
let place = ref 0 in
ColorTable.to_seq_keys color_count
|> Seq.iter (fun key ->
let [@inline always] pack_rgb24 v =
let buf = Bytes.create 3 in
Bytes.unsafe_set buf 0 @@ Char.chr ((v land 0xff0000) lsr 16) ;
Bytes.unsafe_set buf 1 @@ Char.chr ((v land 0xff00) lsr 8) ;
Bytes.unsafe_set buf 2 @@ Char.chr (v land 0xff) ;
Bytes.unsafe_to_string buf in
let packed = pack_rgb24 key in
ColorTable.replace color_table !place packed ;
ColorTable.replace color_table_inv key !place ;
incr place
) ;
for i = 0 to color_table_size -1 do
let color = (ColorTable.find color_table i) in
chunk_write cw color
done ;
in
chunk_write_char cw '\x2c';
chunk_write cw (pack_uint16le 0);
chunk_write cw (pack_uint16le 0);
chunk_write cw (pack_uint16le original_image.width);
chunk_write cw (pack_uint16le original_image.height);
chunk_write_char cw '\x00';
chunk_write_char cw @@ Char.chr lzw_min_size;
let pixels = original_image.height * original_image.width in
assert (pixels > 0);
let pixel = ref 0 in
let output_buffer = Bytes.make 255 '\000'
and output_offset = ref 0 in
let [@inline always] reset_output_buffer () =
output_offset := 0;
and [@inline always] flush_output_buffer () =
let subblock = Bytes.sub_string output_buffer 0 (!output_offset) in
chunk_write_char cw @@ Char.chr (String.length subblock);
chunk_write cw subblock;
and [@inline always] buffer_put char =
Bytes.set output_buffer !output_offset char;
incr output_offset
in
let x = ref 0 in
let y = ref 0 in
let code_size = ref (lzw_min_size+1) in
let clear_code = calc_clear_code !code_size in
let eoi_code = clear_code + 1 in
let carry = ref clear_code in
let carry_bits = ref !code_size in
let symbol_ctr = ref 0 in
let byte_ctr = ref 0 in
while !pixel < pixels do
let packed = Bigarray.Array2.get packed_plane !x !y in
let idx = ColorTable.find color_table_inv packed in
incr symbol_ctr;
if !code_size <> 12 && !symbol_ctr = calc_clear_code !code_size then begin
incr code_size ;
symbol_ctr := 0;
end ;
carry := !carry lor (idx lsl !carry_bits) ;
carry_bits := !carry_bits + !code_size ;
if !pixel+1 = pixels then begin
carry := !carry lor (eoi_code lsl !carry_bits) ;
carry_bits := !carry_bits + !code_size;
end;
while (!carry_bits >= 8) do
buffer_put @@ Char.chr (!carry land 0xff) ;
incr byte_ctr;
carry := !carry lsr 8;
carry_bits := !carry_bits - 8;
done ;
if !byte_ctr > 255 - 100 && !carry_bits <= 0 then begin
flush_output_buffer () ;
reset_output_buffer () ;
byte_ctr := 0;
end ;
incr pixel ;
if !x + 1 = original_image.width then begin
x := 0 ;
incr y ;
end else incr x;
done ;
assert (!pixel = pixels);
while !carry_bits > 0 do
buffer_put @@ Char.chr (!carry land 0xff) ;
carry := !carry lsr 8;
carry_bits := !carry_bits - 8;
incr byte_ctr;
done ;
assert (!carry = 0);
assert (!carry_bits <= 0);
if !byte_ctr <> 0 then begin
flush_output_buffer ();
end ;
chunk_write_char cw '\x00';
chunk_write_char cw '\x3b'
include ReadGIF