cpdf-source/cpdfpng.ml

143 lines
4.7 KiB
OCaml
Raw Normal View History

2023-03-17 20:09:01 +01:00
(* Read and write non-interlaced, non-transparent 24 bit PNGs. Such a PNG may
be loaded into a PDF simply by extracting its width and height from the
IHDR, and concatenating all its IDAT data sections together. *)
2022-12-23 16:24:59 +01:00
open Pdfutil
open Pdfio
2022-12-23 15:29:47 +01:00
type t =
{width : int;
height : int;
2022-12-23 16:24:59 +01:00
idat : bytes}
2022-12-23 15:29:47 +01:00
2023-03-19 18:13:15 +01:00
(* Writing *)
2023-03-20 22:02:10 +01:00
let tbl =
ref ([||] : int32 array)
let mktbl () =
2023-03-19 18:13:15 +01:00
let f n =
2023-03-19 21:04:45 +01:00
let a = ref (i32ofi n) in
2023-03-19 18:13:15 +01:00
for _ = 0 to 7 do
2023-03-20 22:02:10 +01:00
a := lxor32 (lsr32 !a 1) (land32 0xEDB88320l (i32succ (lnot32 (land32 !a 1l))))
2023-03-19 18:13:15 +01:00
done;
2023-03-19 21:04:45 +01:00
!a
2023-03-19 18:13:15 +01:00
in
2023-03-20 22:02:10 +01:00
tbl := Array.init 256 f
2023-03-19 18:13:15 +01:00
2023-03-19 21:04:45 +01:00
let update crc buf len =
let a = ref crc in
2023-03-19 18:13:15 +01:00
for n = 0 to len - 1 do
let e = i32ofi (int_of_char buf.[n]) in
2023-03-20 22:02:10 +01:00
a := lxor32 !tbl.(i32toi (land32 (lxor32 !a e) 0xFFl)) (lsr32 !a 8)
2023-03-19 18:13:15 +01:00
done;
2023-03-19 21:04:45 +01:00
!a
2023-03-19 18:13:15 +01:00
let bytes_of_word x =
2023-03-19 21:04:45 +01:00
i32toi (sr32 x 24),
i32toi (land32 0x000000FFl (sr32 x 16)),
i32toi (land32 0x000000FFl (sr32 x 8)),
2023-03-19 18:13:15 +01:00
i32toi (land32 0x000000FFl x)
2023-03-20 22:02:10 +01:00
let output_bytes_of_word o w =
let a, b, c, d = bytes_of_word w in
2023-03-19 18:13:15 +01:00
o.output_byte a;
o.output_byte b;
o.output_byte c;
o.output_byte d
2023-03-20 22:02:10 +01:00
let write_crc o ctype cdata =
let crc = update 0xFFFFFFFFl ctype 4 in
let crc = update crc cdata (String.length cdata) in
let crc = lnot32 crc in
output_bytes_of_word o crc
2023-03-19 18:13:15 +01:00
let write_chunk o ctype data =
2023-03-20 22:02:10 +01:00
output_bytes_of_word o (i32ofi (Bytes.length data));
2023-03-19 20:58:15 +01:00
for x = 0 to 3 do o.output_byte (int_of_char ctype.[x]) done;
2023-03-19 18:13:15 +01:00
o.output_string (Bytes.unsafe_to_string data);
write_crc o ctype (Bytes.unsafe_to_string data)
2023-03-20 22:02:10 +01:00
let write_word b x n =
2023-03-19 18:13:15 +01:00
let p, q, r, s = bytes_of_word n in
2023-03-20 22:02:10 +01:00
Bytes.set b x (char_of_int p);
Bytes.set b (x + 1) (char_of_int q);
Bytes.set b (x + 2) (char_of_int r);
Bytes.set b (x + 3) (char_of_int s)
2023-03-19 18:13:15 +01:00
let write_png png o =
2023-06-24 22:21:22 +02:00
if bytes_size png.idat > 1073741823 then raise (Invalid_argument "write_png: too large") else
2023-03-20 22:02:10 +01:00
if Array.length !tbl = 0 then mktbl ();
2023-03-19 18:13:15 +01:00
o.output_string "\137\080\078\071\013\010\026\010";
2023-03-20 22:02:10 +01:00
let ihdr = Bytes.make 13 '\000' in
2023-03-19 18:13:15 +01:00
write_word ihdr 0 (i32ofi png.width);
write_word ihdr 4 (i32ofi png.height);
Bytes.set ihdr 8 (char_of_int 8); (* bit depth *)
Bytes.set ihdr 9 (char_of_int 2); (* colour type *)
Bytes.set ihdr 10 (char_of_int 0); (* compression method *)
Bytes.set ihdr 11 (char_of_int 0); (* filter method *)
Bytes.set ihdr 12 (char_of_int 0); (* interlace method *)
write_chunk o "IHDR" ihdr;
2023-03-20 22:02:10 +01:00
write_chunk o "IDAT" (Bytes.unsafe_of_string (string_of_bytes png.idat));
2023-03-19 18:13:15 +01:00
write_chunk o "IEND" (Bytes.create 0)
(* Reading *)
2022-12-23 16:24:59 +01:00
let string_of_tag t =
2023-03-20 22:02:10 +01:00
Printf.sprintf "%c%c%c%c"
(char_of_int (i32toi (sr32 t 24)))
(char_of_int (i32toi (land32 0x000000FFl (sr32 t 16))))
(char_of_int (i32toi (land32 0x000000FFl (sr32 t 8))))
(char_of_int (i32toi (land32 0x000000FFl t)))
2022-12-23 16:24:59 +01:00
2022-12-23 17:19:40 +01:00
let read_unsigned_4byte i =
let a = i32ofi (i.input_byte ()) in
let b = i32ofi (i.input_byte ()) in
let c = i32ofi (i.input_byte ()) in
let d = i32ofi (i.input_byte ()) in
lor32 (lor32 (lsl32 a 24) (lsl32 b 16)) (lor32 (lsl32 c 8) d)
2022-12-23 16:24:59 +01:00
let read_chunk i =
2023-03-20 22:02:10 +01:00
let chunklen = i32toi (read_unsigned_4byte i) in
2022-12-23 16:24:59 +01:00
let chunktype = read_unsigned_4byte i in
2023-03-20 22:02:10 +01:00
let chunkdata = mkbytes chunklen in
setinit i chunkdata 0 chunklen;
2022-12-23 16:24:59 +01:00
let _ (* crc *) = read_unsigned_4byte i in
(string_of_tag chunktype, chunkdata)
2022-12-23 20:27:03 +01:00
let concat_bytes ss =
2023-03-12 17:29:32 +01:00
let total_length = sum (map bytes_size ss) in
2022-12-23 20:27:03 +01:00
let s' = mkbytes total_length in
let p = ref 0 in
iter
(fun s ->
for x = 0 to bytes_size s - 1 do bset_unsafe s' !p (bget s x); incr p done)
ss;
s'
2022-12-23 15:29:47 +01:00
let read_png i =
2022-12-23 16:24:59 +01:00
try
i.seek_in 8;
let ihdr, ihdrdata = read_chunk i in
if ihdr <> "IHDR" then raise (Pdf.PDFError "read_png: first table not IHDR") else
2022-12-23 20:27:03 +01:00
let hdr = input_of_bytes ihdrdata in
let width = read_unsigned_4byte hdr in
let height = read_unsigned_4byte hdr in
let bitdepth = hdr.input_byte () in
2023-03-20 22:02:10 +01:00
if bitdepth <> 8 then raise (Pdf.PDFError "read_png: bit depth not 8") else
2022-12-23 20:27:03 +01:00
let colortype = hdr.input_byte () in
2023-03-20 22:02:10 +01:00
if colortype <> 2 then raise (Pdf.PDFError "read_png: only 24 bit non-alpha PNGs") else
2022-12-23 20:27:03 +01:00
let _ (*compressionmethod*) = hdr.input_byte () in
let _ (*filtermethod*) = hdr.input_byte () in
let interlacemethod = hdr.input_byte () in
2023-03-20 22:02:10 +01:00
if interlacemethod <> 0 then raise (Pdf.PDFError "read_png: interlaced PDFs not supported") else
2022-12-23 20:27:03 +01:00
let idat = ref [] in
2022-12-23 16:24:59 +01:00
begin try
while true do
let chunkname, chunkdata = read_chunk i in
2022-12-23 20:27:03 +01:00
if chunkname = "IDAT" then idat := chunkdata::!idat
2022-12-23 16:24:59 +01:00
done
with
_ -> ()
end;
2023-03-20 22:02:10 +01:00
{width = i32toi width; height = i32toi height; idat = concat_bytes (rev !idat)}
2022-12-23 16:24:59 +01:00
with
e -> raise (Pdf.PDFError (Printf.sprintf "read_png: failed on %s" (Printexc.to_string e)))