123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- (*
- * PNG File Format Library
- * Copyright (c)2005 Nicolas Cannasse
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- *)
- type grey_bits =
- | GBits1
- | GBits2
- | GBits4
- | GBits8
- | GBits16
- type grey_alpha_bits =
- | GABits8
- | GABits16
- type true_bits =
- | TBits8
- | TBits16
- type index_bits =
- | IBits1
- | IBits2
- | IBits4
- | IBits8
- type alpha =
- | NoAlpha
- | HaveAlpha
- type color =
- | ClGreyScale of grey_bits
- | ClGreyAlpha of grey_alpha_bits
- | ClTrueColor of true_bits * alpha
- | ClIndexed of index_bits
- type header = {
- png_width : int;
- png_height : int;
- png_color : color;
- png_interlace : bool;
- }
- type chunk_id = string
- type chunk =
- | CEnd
- | CHeader of header
- | CData of string
- | CPalette of string
- | CUnknown of chunk_id * string
- type png = chunk list
- type error_msg =
- | Invalid_header
- | Invalid_file
- | Truncated_file
- | Invalid_CRC
- | Invalid_colors
- | Unsupported_colors
- | Invalid_datasize
- | Invalid_filter of int
- | Invalid_array
- exception Error of error_msg
- let error_msg = function
- | Invalid_header -> "Invalid header"
- | Invalid_file -> "Invalid file"
- | Truncated_file -> "Truncated file"
- | Invalid_CRC -> "Invalid CRC"
- | Invalid_colors -> "Invalid color model"
- | Unsupported_colors -> "Unsupported color model"
- | Invalid_datasize -> "Invalid data size"
- | Invalid_filter f -> "Invalid filter " ^ string_of_int f
- | Invalid_array -> "Invalid array"
- let error msg = raise (Error msg)
- let is_upper c = ((int_of_char c) land 32) <> 0
- let is_critical id = is_upper id.[0]
- let is_public id = is_upper id.[1]
- let is_reseverd id = is_upper id.[2]
- let is_safe_to_copy id = is_upper id.[3]
- let is_id_char c =
- (c >= '\065' && c <= '\090') || (c >= '\097' && c <= '\122')
- let rec header = function
- | [] -> error Invalid_file
- | CHeader h :: _ -> h
- | _ :: l -> header l
- let data f =
- let rec loop acc = function
- | [] ->
- (match List.rev acc with
- | [] -> error Invalid_file
- | l -> String.concat "" l)
- | CData s :: l -> loop (s :: acc) l
- | _ :: l -> loop acc l
- in
- loop [] f
- let color_bits = function
- | ClGreyScale g -> (match g with
- | GBits1 -> 1
- | GBits2 -> 2
- | GBits4 -> 4
- | GBits8 -> 8
- | GBits16 -> 16)
- | ClGreyAlpha g -> (match g with
- | GABits8 -> 8
- | GABits16 -> 16)
- | ClTrueColor (t,_) -> (match t with
- | TBits8 -> 8
- | TBits16 -> 16)
- | ClIndexed i -> (match i with
- | IBits1 -> 1
- | IBits2 -> 2
- | IBits4 -> 4
- | IBits8 -> 8)
- let crc_table = Array.init 256 (fun n ->
- let c = ref (Int32.of_int n) in
- for k = 0 to 7 do
- if Int32.logand !c 1l <> 0l then
- c := Int32.logxor 0xEDB88320l (Int32.shift_right_logical !c 1)
- else
- c := (Int32.shift_right_logical !c 1);
- done;
- !c)
- let input_crc ch =
- let crc = ref 0xFFFFFFFFl in
- let update c =
- let c = Int32.of_int (int_of_char c) in
- let k = Array.unsafe_get crc_table (Int32.to_int (Int32.logand (Int32.logxor !crc c) 0xFFl)) in
- crc := Int32.logxor k (Int32.shift_right_logical !crc 8)
- in
- let ch2 = IO.create_in
- ~read:(fun () ->
- let c = IO.read ch in
- update c;
- c
- )
- ~input:(fun s p l ->
- let l = IO.input ch s p l in
- for i = 0 to l - 1 do
- update (Bytes.get s (p+i))
- done;
- l
- )
- ~close:(fun () ->
- IO.close_in ch
- )
- in
- ch2 , (fun () -> Int32.logxor !crc 0xFFFFFFFFl)
- let output_crc ch =
- let crc = ref 0xFFFFFFFFl in
- let update c =
- let c = Int32.of_int (int_of_char c) in
- let k = Array.unsafe_get crc_table (Int32.to_int (Int32.logand (Int32.logxor !crc c) 0xFFl)) in
- crc := Int32.logxor k (Int32.shift_right_logical !crc 8)
- in
- let ch2 = IO.create_out
- ~write:(fun c ->
- IO.write ch c;
- update c;
- )
- ~output:(fun s p l ->
- let l = IO.output ch s p l in
- for i = 0 to l - 1 do
- update (Bytes.get s (p+i))
- done;
- l
- )
- ~flush:(fun () ->
- IO.flush ch
- )
- ~close:(fun () ->
- IO.close_out ch
- )
- in
- ch2 , (fun () -> Int32.logxor !crc 0xFFFFFFFFl)
- let parse_header ch =
- let width = IO.BigEndian.read_i32 ch in
- let height = IO.BigEndian.read_i32 ch in
- if width < 0 || height < 0 then error Invalid_header;
- let bits = IO.read_byte ch in
- let color = IO.read_byte ch in
- let color = (match color with
- | 0 -> ClGreyScale (match bits with 1 -> GBits1 | 2 -> GBits2 | 4 -> GBits4 | 8 -> GBits8 | 16 -> GBits16 | _ -> error Invalid_colors)
- | 2 -> ClTrueColor ((match bits with 8 -> TBits8 | 16 -> TBits16 | _ -> error Invalid_colors) , NoAlpha)
- | 3 -> ClIndexed (match bits with 1 -> IBits1 | 2 -> IBits2 | 4 -> IBits4 | 8 -> IBits8 | _ -> error Invalid_colors)
- | 4 -> ClGreyAlpha (match bits with 8 -> GABits8 | 16 -> GABits16 | _ -> error Invalid_colors)
- | 6 -> ClTrueColor ((match bits with 8 -> TBits8 | 16 -> TBits16 | _ -> error Invalid_colors) , HaveAlpha)
- | _ -> error Invalid_colors)
- in
- let compress = IO.read_byte ch in
- let filter = IO.read_byte ch in
- if compress <> 0 || filter <> 0 then error Invalid_header;
- let interlace = IO.read_byte ch in
- let interlace = (match interlace with 0 -> false | 1 -> true | _ -> error Invalid_header) in
- {
- png_width = width;
- png_height = height;
- png_color = color;
- png_interlace = interlace;
- }
- let parse_chunk ch =
- let len = IO.BigEndian.read_i32 ch in
- let ch2 , crc = input_crc ch in
- let id = IO.nread_string ch2 4 in
- if len < 0 || not (is_id_char id.[0]) || not (is_id_char id.[1]) || not (is_id_char id.[2]) || not (is_id_char id.[3]) then error Invalid_file;
- let data = IO.nread_string ch2 len in
- let crc_val = IO.BigEndian.read_real_i32 ch in
- if crc_val <> crc() then error Invalid_CRC;
- match id with
- | "IEND" -> CEnd
- | "IHDR" -> CHeader (parse_header (IO.input_string data))
- | "IDAT" -> CData data
- | "PLTE" -> CPalette data
- | _ -> CUnknown (id,data)
- let png_sign = "\137\080\078\071\013\010\026\010"
- let parse ch =
- let sign = (try IO.nread_string ch (String.length png_sign) with IO.No_more_input -> error Invalid_header) in
- if sign <> png_sign then error Invalid_header;
- let rec loop acc =
- match parse_chunk ch with
- | CEnd -> List.rev acc
- | c -> loop (c :: acc)
- in
- try
- loop []
- with
- | IO.No_more_input -> error Truncated_file
- | IO.Overflow _ -> error Invalid_file
- let write_chunk ch cid cdata =
- IO.BigEndian.write_i32 ch (String.length cdata);
- let ch2 , crc = output_crc ch in
- IO.nwrite_string ch2 cid;
- IO.nwrite_string ch2 cdata;
- IO.BigEndian.write_real_i32 ch (crc())
- let write_header real_ch h =
- let ch = IO.output_string() in
- IO.BigEndian.write_i32 ch h.png_width;
- IO.BigEndian.write_i32 ch h.png_height;
- IO.write_byte ch (color_bits h.png_color);
- IO.write_byte ch (match h.png_color with
- | ClGreyScale _ -> 0
- | ClTrueColor (_,NoAlpha) -> 2
- | ClIndexed _ -> 3
- | ClGreyAlpha _ -> 4
- | ClTrueColor (_,HaveAlpha) -> 6);
- IO.write_byte ch 0;
- IO.write_byte ch 0;
- IO.write_byte ch (if h.png_interlace then 1 else 0);
- let data = IO.close_out ch in
- write_chunk real_ch "IHDR" data
- let write ch png =
- IO.nwrite_string ch png_sign;
- List.iter (function
- | CEnd -> write_chunk ch "IEND" ""
- | CHeader h -> write_header ch h
- | CData s -> write_chunk ch "IDAT" s
- | CPalette s -> write_chunk ch "PLTE" s
- | CUnknown (id,data) -> write_chunk ch id data
- ) png
- let filter png data =
- let head = header png in
- let w = head.png_width in
- let h = head.png_height in
- match head.png_color with
- | ClGreyScale _
- | ClGreyAlpha _
- | ClIndexed _
- | ClTrueColor (TBits16,_) -> error Unsupported_colors
- | ClTrueColor (TBits8,alpha) ->
- let alpha = (match alpha with NoAlpha -> false | HaveAlpha -> true) in
- let buf = Bytes.create (w * h * 4) in
- let nbytes = if alpha then 4 else 3 in
- let stride = nbytes * w + 1 in
- if String.length data < h * stride then error Invalid_datasize;
- let bp = ref 0 in
- let get p = int_of_char (String.unsafe_get data p) in
- let bget p = int_of_char (Bytes.unsafe_get buf p) in
- let set v = Bytes.unsafe_set buf !bp (Char.unsafe_chr v); incr bp in
- let filters = [|
- (fun x y v -> v
- );
- (fun x y v ->
- let v2 = if x = 0 then 0 else bget (!bp - 4) in
- v + v2
- );
- (fun x y v ->
- let v2 = if y = 0 then 0 else bget (!bp - 4*w) in
- v + v2
- );
- (fun x y v ->
- let v2 = if x = 0 then 0 else bget (!bp - 4) in
- let v3 = if y = 0 then 0 else bget (!bp - 4*w) in
- v + (v2 + v3) / 2
- );
- (fun x y v ->
- let a = if x = 0 then 0 else bget (!bp - 4) in
- let b = if y = 0 then 0 else bget (!bp - 4*w) in
- let c = if x = 0 || y = 0 then 0 else bget (!bp - 4 - 4*w) in
- let p = a + b - c in
- let pa = abs (p - a) in
- let pb = abs (p - b) in
- let pc = abs (p - c) in
- let d = (if pa <= pb && pa <= pc then a else if pb <= pc then b else c) in
- v + d
- );
- |] in
- for y = 0 to h - 1 do
- let f = get (y * stride) in
- let f = (if f < 5 then filters.(f) else error (Invalid_filter f)) in
- for x = 0 to w - 1 do
- let p = x * nbytes + y * stride in
- if not alpha then begin
- set 255;
- for c = 1 to 3 do
- let v = get (p + c) in
- set (f x y v)
- done;
- end else begin
- let v = get (p + 4) in
- let a = f x y v in
- set a;
- for c = 1 to 3 do
- let v = get (p + c) in
- set (f x y v)
- done;
- end;
- done;
- done;
- Bytes.to_string buf
- let make ~width ~height ~pixel ~compress =
- let data = Bytes.create (width * height * 4 + height) in
- let p = ref 0 in
- let set v = Bytes.unsafe_set data !p (Char.unsafe_chr v); incr p in
- for y = 0 to height - 1 do
- set 0;
- for x = 0 to width - 1 do
- let c = pixel x y in
- let ic = Int32.to_int c in
- (* RGBA *)
- set (ic lsr 16);
- set (ic lsr 8);
- set ic;
- set (Int32.to_int (Int32.shift_right_logical c 24));
- done;
- done;
- let data = Bytes.to_string data in
- let data = compress data in
- let header = {
- png_width = width;
- png_height = height;
- png_color = ClTrueColor (TBits8,HaveAlpha);
- png_interlace = false;
- } in
- [CHeader header; CData data; CEnd]
|