···11+(** PNG chunk types and parsing. *)
22+33+(** Chunk type as 4-byte identifier. *)
44+type chunk_type = int32
55+66+(** Critical chunks *)
77+let ihdr : chunk_type = 0x49484452l (* IHDR *)
88+let plte : chunk_type = 0x504C5445l (* PLTE *)
99+let idat : chunk_type = 0x49444154l (* IDAT *)
1010+let iend : chunk_type = 0x49454E44l (* IEND *)
1111+1212+(** Ancillary chunks *)
1313+let trns : chunk_type = 0x74524E53l (* tRNS *)
1414+let chrm : chunk_type = 0x6348524Dl (* cHRM *)
1515+let gama : chunk_type = 0x67414D41l (* gAMA *)
1616+let iccp : chunk_type = 0x69434350l (* iCCP *)
1717+let sbit : chunk_type = 0x73424954l (* sBIT *)
1818+let srgb : chunk_type = 0x73524742l (* sRGB *)
1919+let bkgd : chunk_type = 0x624B4744l (* bKGD *)
2020+let hist : chunk_type = 0x68495354l (* hIST *)
2121+let phys : chunk_type = 0x70485973l (* pHYs *)
2222+let time : chunk_type = 0x74494D45l (* tIME *)
2323+let text : chunk_type = 0x74455874l (* tEXt *)
2424+let ztxt : chunk_type = 0x7A545874l (* zTXt *)
2525+let itxt : chunk_type = 0x69545874l (* iTXt *)
2626+let exif : chunk_type = 0x65584966l (* eXIf *)
2727+2828+(** APNG extension chunks *)
2929+let actl : chunk_type = 0x6163544Cl (* acTL *)
3030+let fctl : chunk_type = 0x6663544Cl (* fcTL *)
3131+let fdat : chunk_type = 0x66644154l (* fdAT *)
3232+3333+(** Check if chunk type is critical (uppercase first letter). *)
3434+let[@inline] is_critical (ct : chunk_type) =
3535+ Int32.(logand (shift_right_logical ct 24) 0x20l = 0l)
3636+3737+(** Check if chunk type is public (uppercase second letter). *)
3838+let[@inline] is_public (ct : chunk_type) =
3939+ Int32.(logand (shift_right_logical ct 16) 0x20l = 0l)
4040+4141+(** Check if reserved bit is set (third letter uppercase means reserved OK). *)
4242+let[@inline] reserved_set (ct : chunk_type) =
4343+ Int32.(logand (shift_right_logical ct 8) 0x20l <> 0l)
4444+4545+(** Check if chunk is safe to copy (lowercase fourth letter). *)
4646+let[@inline] safe_to_copy (ct : chunk_type) =
4747+ Int32.(logand ct 0x20l <> 0l)
4848+4949+(** Convert chunk type to 4-character string. *)
5050+let to_string (ct : chunk_type) =
5151+ let s = Bytes.create 4 in
5252+ Bytes.set s 0 (Char.chr (Int32.(to_int (logand (shift_right_logical ct 24) 0xFFl))));
5353+ Bytes.set s 1 (Char.chr (Int32.(to_int (logand (shift_right_logical ct 16) 0xFFl))));
5454+ Bytes.set s 2 (Char.chr (Int32.(to_int (logand (shift_right_logical ct 8) 0xFFl))));
5555+ Bytes.set s 3 (Char.chr (Int32.(to_int (logand ct 0xFFl))));
5656+ Bytes.to_string s
5757+5858+(** Convert 4-character string to chunk type. *)
5959+let of_string s =
6060+ if String.length s <> 4 then invalid_arg "Chunk.of_string: string must be 4 chars";
6161+ let b0 = Char.code s.[0] in
6262+ let b1 = Char.code s.[1] in
6363+ let b2 = Char.code s.[2] in
6464+ let b3 = Char.code s.[3] in
6565+ Int32.(logor (logor (logor
6666+ (shift_left (of_int b0) 24)
6767+ (shift_left (of_int b1) 16))
6868+ (shift_left (of_int b2) 8))
6969+ (of_int b3))
7070+7171+(** Read chunk type from bytes at given position. *)
7272+let read_type bytes ~pos =
7373+ let b0 = Bytes.get_uint8 bytes pos in
7474+ let b1 = Bytes.get_uint8 bytes (pos + 1) in
7575+ let b2 = Bytes.get_uint8 bytes (pos + 2) in
7676+ let b3 = Bytes.get_uint8 bytes (pos + 3) in
7777+ Int32.(logor (logor (logor
7878+ (shift_left (of_int b0) 24)
7979+ (shift_left (of_int b1) 16))
8080+ (shift_left (of_int b2) 8))
8181+ (of_int b3))
8282+8383+(** Write chunk type to bytes at given position. *)
8484+let write_type bytes ~pos (ct : chunk_type) =
8585+ Bytes.set_uint8 bytes pos Int32.(to_int (logand (shift_right_logical ct 24) 0xFFl));
8686+ Bytes.set_uint8 bytes (pos + 1) Int32.(to_int (logand (shift_right_logical ct 16) 0xFFl));
8787+ Bytes.set_uint8 bytes (pos + 2) Int32.(to_int (logand (shift_right_logical ct 8) 0xFFl));
8888+ Bytes.set_uint8 bytes (pos + 3) Int32.(to_int (logand ct 0xFFl))
+271
vendor/opam/ocaml-png/src/chunk.mli
···11+(** {1 PNG Chunk Types}
22+33+ Chunk type definitions and utilities as specified in
44+ {{:https://www.w3.org/TR/png/#5Chunk-layout} PNG Specification Section 5}.
55+66+ {2 Overview}
77+88+ A PNG file consists of a signature followed by a sequence of chunks.
99+ Each chunk has a 4-byte type code that identifies its purpose.
1010+ The type code is encoded as four ASCII letters, stored as a 32-bit
1111+ big-endian integer.
1212+1313+ {2 Chunk Categories}
1414+1515+ Chunks are categorized by properties encoded in the case of their type
1616+ letters ({{:https://www.w3.org/TR/png/#5Chunk-naming-conventions}
1717+ Section 5.4}):
1818+1919+ - {b Bit 5 of byte 0} (first letter): Critical (uppercase) vs Ancillary (lowercase)
2020+ - {b Bit 5 of byte 1} (second letter): Public (uppercase) vs Private (lowercase)
2121+ - {b Bit 5 of byte 2} (third letter): Reserved (must be uppercase)
2222+ - {b Bit 5 of byte 3} (fourth letter): Safe-to-copy (lowercase) vs Unsafe (uppercase)
2323+2424+ {2 Chunk Ordering}
2525+2626+ Chunk ordering requirements ({{:https://www.w3.org/TR/png/#5ChsOrdering}
2727+ Section 5.6}):
2828+2929+ 1. IHDR must be first
3030+ 2. PLTE must precede IDAT (for indexed color)
3131+ 3. IDAT chunks must be consecutive
3232+ 4. IEND must be last
3333+3434+ {2 References}
3535+3636+ - {{:https://www.w3.org/TR/png/#5Chunk-layout} Section 5: Chunk Layout}
3737+ - {{:https://www.w3.org/TR/png/#11Chunks} Section 11: Chunk Specifications}
3838+ - {{:https://www.w3.org/TR/png/#5Chunk-naming-conventions} Section 5.4: Naming} *)
3939+4040+(** {1 Types} *)
4141+4242+(** Chunk type represented as a 32-bit integer.
4343+4444+ The four ASCII characters are packed big-endian:
4545+ byte 0 in bits 24-31, byte 1 in bits 16-23, etc.
4646+4747+ For example, "IHDR" = 0x49484452. *)
4848+type chunk_type = int32
4949+5050+(** {1 Critical Chunks}
5151+5252+ Critical chunks are essential for displaying the image.
5353+ If an unrecognized critical chunk is encountered, the decoder
5454+ must signal an error.
5555+5656+ See {{:https://www.w3.org/TR/png/#11Critical-chunks} Section 11.2}. *)
5757+5858+(** Image Header chunk - must be first chunk.
5959+6060+ Contains width, height, bit depth, color type, compression method,
6161+ filter method, and interlace method.
6262+6363+ {{:https://www.w3.org/TR/png/#11IHDR} Section 11.2.2} *)
6464+val ihdr : chunk_type
6565+6666+(** Palette chunk - required for indexed color images.
6767+6868+ Contains 1-256 RGB entries (3 bytes each).
6969+7070+ {{:https://www.w3.org/TR/png/#11PLTE} Section 11.2.3} *)
7171+val plte : chunk_type
7272+7373+(** Image Data chunk - contains compressed pixel data.
7474+7575+ Multiple IDAT chunks may be present and must be consecutive.
7676+ Their data is concatenated before decompression.
7777+7878+ {{:https://www.w3.org/TR/png/#11IDAT} Section 11.2.4} *)
7979+val idat : chunk_type
8080+8181+(** Image End chunk - marks end of PNG datastream.
8282+8383+ Must be final chunk. Contains no data.
8484+8585+ {{:https://www.w3.org/TR/png/#11IEND} Section 11.2.5} *)
8686+val iend : chunk_type
8787+8888+(** {1 Ancillary Chunks}
8989+9090+ Ancillary chunks provide optional metadata. Decoders may ignore
9191+ unrecognized ancillary chunks.
9292+9393+ See {{:https://www.w3.org/TR/png/#11Ancillary-chunks} Section 11.3}. *)
9494+9595+(** Transparency chunk - defines transparency information.
9696+9797+ - Grayscale: 2 bytes (transparent gray value)
9898+ - RGB: 6 bytes (transparent RGB value)
9999+ - Indexed: alpha values for palette entries
100100+101101+ {{:https://www.w3.org/TR/png/#11tRNS} Section 11.3.2} *)
102102+val trns : chunk_type
103103+104104+(** Primary chromaticities chunk.
105105+106106+ Specifies 1931 CIE x,y chromaticities of R, G, B primaries
107107+ and white point.
108108+109109+ {{:https://www.w3.org/TR/png/#11cHRM} Section 11.3.3} *)
110110+val chrm : chunk_type
111111+112112+(** Gamma chunk - image gamma value.
113113+114114+ Stored as gamma * 100000.
115115+116116+ {{:https://www.w3.org/TR/png/#11gAMA} Section 11.3.4} *)
117117+val gama : chunk_type
118118+119119+(** ICC profile chunk - embedded ICC color profile.
120120+121121+ Contains profile name, compression method, and compressed profile.
122122+123123+ {{:https://www.w3.org/TR/png/#11iCCP} Section 11.3.5} *)
124124+val iccp : chunk_type
125125+126126+(** Significant bits chunk.
127127+128128+ Indicates original number of significant bits per sample.
129129+130130+ {{:https://www.w3.org/TR/png/#11sBIT} Section 11.3.6} *)
131131+val sbit : chunk_type
132132+133133+(** Standard RGB chunk - indicates sRGB color space.
134134+135135+ Contains rendering intent (0-3).
136136+137137+ {{:https://www.w3.org/TR/png/#11sRGB} Section 11.3.7} *)
138138+val srgb : chunk_type
139139+140140+(** Background color chunk.
141141+142142+ Default background for image compositing.
143143+144144+ {{:https://www.w3.org/TR/png/#11bKGD} Section 11.3.8} *)
145145+val bkgd : chunk_type
146146+147147+(** Image histogram chunk.
148148+149149+ Approximate frequency of palette colors.
150150+151151+ {{:https://www.w3.org/TR/png/#11hIST} Section 11.3.9} *)
152152+val hist : chunk_type
153153+154154+(** Physical pixel dimensions chunk.
155155+156156+ Pixel aspect ratio and optional absolute dimensions.
157157+158158+ {{:https://www.w3.org/TR/png/#11pHYs} Section 11.3.10} *)
159159+val phys : chunk_type
160160+161161+(** Last modification time chunk.
162162+163163+ {{:https://www.w3.org/TR/png/#11tIME} Section 11.3.11} *)
164164+val time : chunk_type
165165+166166+(** Latin-1 text chunk.
167167+168168+ Uncompressed textual metadata.
169169+170170+ {{:https://www.w3.org/TR/png/#11tEXt} Section 11.3.12} *)
171171+val text : chunk_type
172172+173173+(** Compressed text chunk.
174174+175175+ Zlib-compressed textual metadata.
176176+177177+ {{:https://www.w3.org/TR/png/#11zTXt} Section 11.3.13} *)
178178+val ztxt : chunk_type
179179+180180+(** International text chunk.
181181+182182+ UTF-8 textual metadata with language tag.
183183+184184+ {{:https://www.w3.org/TR/png/#11iTXt} Section 11.3.14} *)
185185+val itxt : chunk_type
186186+187187+(** EXIF metadata chunk.
188188+189189+ Contains EXIF data as per EXIF specification.
190190+191191+ {{:https://www.w3.org/TR/png/#11eXIf} Section 11.3.15} *)
192192+val exif : chunk_type
193193+194194+(** {1 APNG Extension Chunks}
195195+196196+ Animation extension chunks as defined in the APNG specification.
197197+198198+ {{:https://wiki.mozilla.org/APNG_Specification} APNG Specification} *)
199199+200200+(** Animation control chunk - defines animation parameters. *)
201201+val actl : chunk_type
202202+203203+(** Frame control chunk - defines frame parameters. *)
204204+val fctl : chunk_type
205205+206206+(** Frame data chunk - contains compressed frame data. *)
207207+val fdat : chunk_type
208208+209209+(** {1 Chunk Properties}
210210+211211+ Functions to check chunk properties based on naming conventions.
212212+ See {{:https://www.w3.org/TR/png/#5Chunk-naming-conventions} Section 5.4}. *)
213213+214214+(** Check if chunk type is critical.
215215+216216+ Critical chunks have an uppercase first letter (bit 5 = 0).
217217+ Critical chunks must be understood by the decoder. *)
218218+val is_critical : chunk_type -> bool
219219+220220+(** Check if chunk type is public.
221221+222222+ Public chunks have an uppercase second letter (bit 5 = 0).
223223+ Public chunks are defined by the PNG specification. *)
224224+val is_public : chunk_type -> bool
225225+226226+(** Check if reserved bit is set.
227227+228228+ The reserved bit (bit 5 of third letter) must be 0 in current PNG.
229229+ Returns [true] if the bit is set (lowercase = invalid). *)
230230+val reserved_set : chunk_type -> bool
231231+232232+(** Check if chunk is safe to copy.
233233+234234+ Safe-to-copy chunks have a lowercase fourth letter (bit 5 = 1).
235235+ These can be copied to modified images even if the chunk is unknown. *)
236236+val safe_to_copy : chunk_type -> bool
237237+238238+(** {1 Conversion} *)
239239+240240+(** Convert chunk type to 4-character ASCII string.
241241+242242+ @param ct Chunk type as 32-bit integer
243243+ @return 4-character string (e.g., "IHDR") *)
244244+val to_string : chunk_type -> string
245245+246246+(** Convert 4-character string to chunk type.
247247+248248+ @param s 4-character ASCII string
249249+ @return Chunk type as 32-bit integer
250250+ @raise Invalid_argument if string length is not 4 *)
251251+val of_string : string -> chunk_type
252252+253253+(** {1 I/O} *)
254254+255255+(** Read chunk type from bytes at given position.
256256+257257+ Reads 4 bytes as big-endian 32-bit integer.
258258+259259+ @param bytes Source buffer
260260+ @param pos Starting position
261261+ @return Chunk type *)
262262+val read_type : bytes -> pos:int -> chunk_type
263263+264264+(** Write chunk type to bytes at given position.
265265+266266+ Writes 4 bytes as big-endian 32-bit integer.
267267+268268+ @param bytes Destination buffer
269269+ @param pos Starting position
270270+ @param ct Chunk type to write *)
271271+val write_type : bytes -> pos:int -> chunk_type -> unit
+62
vendor/opam/ocaml-png/src/crc32.ml
···11+(** CRC32 implementation for PNG (ISO 3309 polynomial).
22+33+ PNG uses the ISO 3309 CRC-32 polynomial:
44+ x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
55+66+ This is the same polynomial used in zlib/gzip/PKZIP. *)
77+88+(** CRC32 lookup table for fast computation.
99+ Generated using the ISO 3309 polynomial 0xEDB88320 (bit-reversed). *)
1010+let table =
1111+ let t = Array.make 256 0l in
1212+ for n = 0 to 255 do
1313+ let c = ref (Int32.of_int n) in
1414+ for _ = 0 to 7 do
1515+ if Int32.(logand !c 1l <> 0l) then
1616+ c := Int32.(logxor (shift_right_logical !c 1) 0xEDB88320l)
1717+ else
1818+ c := Int32.shift_right_logical !c 1
1919+ done;
2020+ t.(n) <- !c
2121+ done;
2222+ t
2323+2424+(** Initial CRC value (all 1s). *)
2525+let init = 0xFFFFFFFFl
2626+2727+(** Update CRC with a single byte. *)
2828+let[@inline] update_byte crc byte =
2929+ let index = Int32.(to_int (logand (logxor crc (of_int byte)) 0xFFl)) in
3030+ Int32.(logxor (shift_right_logical crc 8) table.(index))
3131+3232+(** Update CRC with bytes from a buffer. *)
3333+let update crc bytes ~pos ~len =
3434+ let crc = ref crc in
3535+ for i = pos to pos + len - 1 do
3636+ crc := update_byte !crc (Bytes.get_uint8 bytes i)
3737+ done;
3838+ !crc
3939+4040+(** Update CRC with a string. *)
4141+let update_string crc str =
4242+ let crc = ref crc in
4343+ for i = 0 to String.length str - 1 do
4444+ crc := update_byte !crc (Char.code str.[i])
4545+ done;
4646+ !crc
4747+4848+(** Finalize CRC (XOR with all 1s). *)
4949+let[@inline] finalize crc =
5050+ Int32.logxor crc 0xFFFFFFFFl
5151+5252+(** Compute CRC32 of bytes in one call. *)
5353+let compute bytes ~pos ~len =
5454+ finalize (update init bytes ~pos ~len)
5555+5656+(** Compute CRC32 of entire bytes. *)
5757+let compute_bytes bytes =
5858+ compute bytes ~pos:0 ~len:(Bytes.length bytes)
5959+6060+(** Compute CRC32 of a string. *)
6161+let compute_string str =
6262+ finalize (update_string init str)
+123
vendor/opam/ocaml-png/src/crc32.mli
···11+(** {1 CRC-32 Checksum for PNG}
22+33+ Implementation of the CRC-32 checksum algorithm as specified in
44+ {{:https://www.w3.org/TR/png/#5CRC-algorithm} PNG Specification Section 5.5}.
55+66+ {2 Overview}
77+88+ PNG uses CRC-32 checksums to verify chunk integrity. Each chunk
99+ (except the length field) is protected by a 4-byte CRC following
1010+ the chunk data. The CRC covers the chunk type and chunk data fields.
1111+1212+ {2 Algorithm}
1313+1414+ PNG uses the ISO 3309 CRC-32 polynomial, which is the same polynomial
1515+ used in zlib, gzip, PKZIP, and Ethernet:
1616+1717+ {v
1818+ x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1
1919+ v}
2020+2121+ In reversed (reflected) form: [0xEDB88320]
2222+2323+ The algorithm:
2424+ 1. Initialize CRC to [0xFFFFFFFF]
2525+ 2. For each byte, XOR into low byte of CRC and lookup in table
2626+ 3. Finalize by XORing with [0xFFFFFFFF]
2727+2828+ {2 Sample Code}
2929+3030+ From {{:https://www.w3.org/TR/png/#D-CRCAppendix} Appendix D}:
3131+3232+ {[
3333+ (* Full computation example *)
3434+ let data = Bytes.of_string "IEND" in
3535+ let crc = Crc32.compute_bytes data in
3636+ (* crc = 0xAE426082 *)
3737+ ]}
3838+3939+ {2 References}
4040+4141+ - {{:https://www.w3.org/TR/png/#5CRC-algorithm} PNG Spec Section 5.5}
4242+ - {{:https://www.w3.org/TR/png/#D-CRCAppendix} Appendix D: CRC Algorithm}
4343+ - ISO 3309 / ITU-T V.42
4444+ - RFC 1952 (gzip CRC) *)
4545+4646+(** {1 Incremental Interface}
4747+4848+ For computing CRC over data in chunks. *)
4949+5050+(** Initial CRC value ([0xFFFFFFFF]).
5151+5252+ Start with this value when computing a new CRC. *)
5353+val init : int32
5454+5555+(** Update CRC with bytes from a buffer.
5656+5757+ @param crc Current CRC state
5858+ @param bytes Source buffer
5959+ @param pos Starting position in buffer
6060+ @param len Number of bytes to process
6161+ @return Updated CRC state (not finalized) *)
6262+val update : int32 -> bytes -> pos:int -> len:int -> int32
6363+6464+(** Update CRC with bytes from a string.
6565+6666+ @param crc Current CRC state
6767+ @param str Source string
6868+ @return Updated CRC state (not finalized) *)
6969+val update_string : int32 -> string -> int32
7070+7171+(** Update CRC with a single byte.
7272+7373+ @param crc Current CRC state
7474+ @param byte Byte value (0-255)
7575+ @return Updated CRC state *)
7676+val update_byte : int32 -> int -> int32
7777+7878+(** Finalize CRC computation.
7979+8080+ XORs with [0xFFFFFFFF] as per PNG specification.
8181+8282+ @param crc CRC state from [update] calls
8383+ @return Final CRC-32 value *)
8484+val finalize : int32 -> int32
8585+8686+(** {1 One-Shot Interface}
8787+8888+ Convenience functions for computing CRC in a single call. *)
8989+9090+(** Compute CRC-32 of bytes in a buffer.
9191+9292+ Equivalent to: [finalize (update init bytes ~pos ~len)]
9393+9494+ @param bytes Source buffer
9595+ @param pos Starting position
9696+ @param len Number of bytes
9797+ @return CRC-32 checksum *)
9898+val compute : bytes -> pos:int -> len:int -> int32
9999+100100+(** Compute CRC-32 of entire bytes buffer.
101101+102102+ @param bytes Source buffer
103103+ @return CRC-32 checksum *)
104104+val compute_bytes : bytes -> int32
105105+106106+(** Compute CRC-32 of a string.
107107+108108+ @param str Source string
109109+ @return CRC-32 checksum *)
110110+val compute_string : string -> int32
111111+112112+(** {1 Implementation Notes}
113113+114114+ This implementation uses a 256-entry lookup table for efficient
115115+ byte-at-a-time computation. The table is computed at module
116116+ initialization using the ISO 3309 polynomial.
117117+118118+ The algorithm processes one byte at a time:
119119+ {v
120120+ crc = table[(crc XOR byte) AND 0xFF] XOR (crc >> 8)
121121+ v}
122122+123123+ For verification, the CRC of "123456789" should be [0xCBF43926]. *)
···11+(** PNG row filtering algorithms.
22+33+ PNG uses row filters to improve compression. Each row is prefixed with
44+ a filter type byte, followed by the filtered data.
55+66+ Filter types:
77+ - 0: None - No filtering
88+ - 1: Sub - Difference from left pixel
99+ - 2: Up - Difference from above pixel
1010+ - 3: Average - Average of left and above
1111+ - 4: Paeth - Paeth predictor
1212+1313+ Filtering operates on bytes, not pixels. For multi-byte pixels,
1414+ the "left" pixel is bpp bytes back. *)
1515+1616+(** Filter type. *)
1717+type t =
1818+ | None
1919+ | Sub
2020+ | Up
2121+ | Average
2222+ | Paeth
2323+2424+(** Filter type from byte value. *)
2525+let of_byte = function
2626+ | 0 -> Some None
2727+ | 1 -> Some Sub
2828+ | 2 -> Some Up
2929+ | 3 -> Some Average
3030+ | 4 -> Some Paeth
3131+ | _ -> None
3232+3333+(** Filter type to byte value. *)
3434+let to_byte = function
3535+ | None -> 0
3636+ | Sub -> 1
3737+ | Up -> 2
3838+ | Average -> 3
3939+ | Paeth -> 4
4040+4141+(** Paeth predictor function.
4242+ Returns the value closest to p = a + b - c, where:
4343+ - a = left pixel
4444+ - b = above pixel
4545+ - c = upper-left pixel *)
4646+let[@inline] paeth_predictor a b c =
4747+ let p = a + b - c in
4848+ let pa = abs (p - a) in
4949+ let pb = abs (p - b) in
5050+ let pc = abs (p - c) in
5151+ if pa <= pb && pa <= pc then a
5252+ else if pb <= pc then b
5353+ else c
5454+5555+(** Unfilter a row in place.
5656+ [prev] is the previous (already unfiltered) row, or empty for the first row.
5757+ [curr] is the current row data (without filter byte), modified in place.
5858+ [bpp] is bytes per pixel (for filter calculations). *)
5959+let unfilter_row filter ~prev ~curr ~bpp =
6060+ let len = Bytes.length curr in
6161+ match filter with
6262+ | None -> ()
6363+6464+ | Sub ->
6565+ (* curr[i] += curr[i - bpp] *)
6666+ for i = bpp to len - 1 do
6767+ let left = Bytes.get_uint8 curr (i - bpp) in
6868+ let x = Bytes.get_uint8 curr i in
6969+ Bytes.set_uint8 curr i ((x + left) land 0xFF)
7070+ done
7171+7272+ | Up ->
7373+ (* curr[i] += prev[i] *)
7474+ if Bytes.length prev > 0 then
7575+ for i = 0 to len - 1 do
7676+ let above = Bytes.get_uint8 prev i in
7777+ let x = Bytes.get_uint8 curr i in
7878+ Bytes.set_uint8 curr i ((x + above) land 0xFF)
7979+ done
8080+8181+ | Average ->
8282+ (* curr[i] += floor((left + above) / 2) *)
8383+ for i = 0 to len - 1 do
8484+ let left = if i >= bpp then Bytes.get_uint8 curr (i - bpp) else 0 in
8585+ let above = if Bytes.length prev > 0 then Bytes.get_uint8 prev i else 0 in
8686+ let x = Bytes.get_uint8 curr i in
8787+ Bytes.set_uint8 curr i ((x + (left + above) / 2) land 0xFF)
8888+ done
8989+9090+ | Paeth ->
9191+ (* curr[i] += paeth(left, above, upper_left) *)
9292+ for i = 0 to len - 1 do
9393+ let left = if i >= bpp then Bytes.get_uint8 curr (i - bpp) else 0 in
9494+ let above = if Bytes.length prev > 0 then Bytes.get_uint8 prev i else 0 in
9595+ let upper_left =
9696+ if i >= bpp && Bytes.length prev > 0 then Bytes.get_uint8 prev (i - bpp)
9797+ else 0
9898+ in
9999+ let x = Bytes.get_uint8 curr i in
100100+ Bytes.set_uint8 curr i ((x + paeth_predictor left above upper_left) land 0xFF)
101101+ done
102102+103103+(** Filter a row for encoding.
104104+ [prev] is the previous (unfiltered) row, or empty for the first row.
105105+ [curr] is the current unfiltered row.
106106+ [output] receives the filtered data.
107107+ [bpp] is bytes per pixel. *)
108108+let filter_row filter ~prev ~curr ~output ~bpp =
109109+ let len = Bytes.length curr in
110110+ match filter with
111111+ | None ->
112112+ Bytes.blit curr 0 output 0 len
113113+114114+ | Sub ->
115115+ for i = 0 to len - 1 do
116116+ let left = if i >= bpp then Bytes.get_uint8 curr (i - bpp) else 0 in
117117+ let x = Bytes.get_uint8 curr i in
118118+ Bytes.set_uint8 output i ((x - left) land 0xFF)
119119+ done
120120+121121+ | Up ->
122122+ for i = 0 to len - 1 do
123123+ let above = if Bytes.length prev > 0 then Bytes.get_uint8 prev i else 0 in
124124+ let x = Bytes.get_uint8 curr i in
125125+ Bytes.set_uint8 output i ((x - above) land 0xFF)
126126+ done
127127+128128+ | Average ->
129129+ for i = 0 to len - 1 do
130130+ let left = if i >= bpp then Bytes.get_uint8 curr (i - bpp) else 0 in
131131+ let above = if Bytes.length prev > 0 then Bytes.get_uint8 prev i else 0 in
132132+ let x = Bytes.get_uint8 curr i in
133133+ Bytes.set_uint8 output i ((x - (left + above) / 2) land 0xFF)
134134+ done
135135+136136+ | Paeth ->
137137+ for i = 0 to len - 1 do
138138+ let left = if i >= bpp then Bytes.get_uint8 curr (i - bpp) else 0 in
139139+ let above = if Bytes.length prev > 0 then Bytes.get_uint8 prev i else 0 in
140140+ let upper_left =
141141+ if i >= bpp && Bytes.length prev > 0 then Bytes.get_uint8 prev (i - bpp)
142142+ else 0
143143+ in
144144+ let x = Bytes.get_uint8 curr i in
145145+ Bytes.set_uint8 output i ((x - paeth_predictor left above upper_left) land 0xFF)
146146+ done
147147+148148+(** Compute sum of absolute differences for filter selection heuristic.
149149+ Lower is better (more compressible). *)
150150+let sum_abs_diff bytes =
151151+ let sum = ref 0 in
152152+ for i = 0 to Bytes.length bytes - 1 do
153153+ let b = Bytes.get_uint8 bytes i in
154154+ (* Interpret as signed for better heuristic *)
155155+ let signed = if b > 127 then b - 256 else b in
156156+ sum := !sum + abs signed
157157+ done;
158158+ !sum
159159+160160+(** All filter types for adaptive selection. *)
161161+let all_filters = [None; Sub; Up; Average; Paeth]
162162+163163+(** Select the best filter for a row using minimum sum heuristic.
164164+ Returns the filter type and filtered data. *)
165165+let select_best_filter ~prev ~curr ~bpp =
166166+ let len = Bytes.length curr in
167167+ let output = Bytes.create len in
168168+ let best_filter, _best_sum =
169169+ List.fold_left (fun (best_f, best_s) f ->
170170+ filter_row f ~prev ~curr ~output ~bpp;
171171+ let sum = sum_abs_diff output in
172172+ if sum < best_s then (f, sum) else (best_f, best_s)
173173+ ) (None, max_int) all_filters
174174+ in
175175+ filter_row best_filter ~prev ~curr ~output ~bpp;
176176+ (best_filter, output)
+150
vendor/opam/ocaml-png/src/filter.mli
···11+(** {1 PNG Row Filtering}
22+33+ Implementation of PNG row filtering algorithms as defined in
44+ {{:https://www.w3.org/TR/png/#9Filters} PNG Specification Section 9}.
55+66+ {2 Overview}
77+88+ PNG uses row filters to preprocess image data before compression.
99+ Each scanline is prefixed with a filter type byte (0-4) followed by
1010+ the filtered row data. Filtering improves compression by exploiting
1111+ correlation between adjacent pixels.
1212+1313+ {2 Filter Types}
1414+1515+ As defined in {{:https://www.w3.org/TR/png/#9Filter-types} Section 9.2}:
1616+1717+ - {b None (0)}: No filtering, [Filt(x) = Orig(x)]
1818+ - {b Sub (1)}: Difference from left pixel, [Filt(x) = Orig(x) - Orig(a)]
1919+ - {b Up (2)}: Difference from above pixel, [Filt(x) = Orig(x) - Orig(b)]
2020+ - {b Average (3)}: Average of left and above, [Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2)]
2121+ - {b Paeth (4)}: Paeth predictor, [Filt(x) = Orig(x) - PaethPredictor(a, b, c)]
2222+2323+ Where:
2424+ - [x] = byte being filtered
2525+ - [a] = byte to the left (or 0 if at left edge)
2626+ - [b] = byte above (or 0 if first row)
2727+ - [c] = byte above and to left (or 0 if at edge)
2828+2929+ {2 Byte-Level Operation}
3030+3131+ Filtering operates on {i bytes}, not pixels. For multi-byte pixels,
3232+ [a] refers to the corresponding byte in the pixel [bpp] bytes to the left.
3333+ For sub-byte pixels (bit depth < 8), [bpp = 1] is used.
3434+3535+ {2 References}
3636+3737+ - {{:https://www.w3.org/TR/png/#9Filters} PNG Spec Section 9: Filtering}
3838+ - {{:https://www.w3.org/TR/png/#9Filter-types} Section 9.2: Filter Types}
3939+ - {{:https://www.w3.org/TR/png/#9Filter-type-4-Paeth} Section 9.4: Paeth Predictor} *)
4040+4141+(** {1 Types} *)
4242+4343+(** Filter type as defined in {{:https://www.w3.org/TR/png/#9Filter-types}
4444+ PNG Spec Section 9.2}.
4545+4646+ Each filter type represents a different prediction strategy for
4747+ improving compression. *)
4848+type t =
4949+ | None (** Type 0: No filtering *)
5050+ | Sub (** Type 1: Byte difference from left *)
5151+ | Up (** Type 2: Byte difference from above *)
5252+ | Average (** Type 3: Average of left and above *)
5353+ | Paeth (** Type 4: Paeth predictor *)
5454+5555+(** {1 Conversion} *)
5656+5757+(** Convert a filter type byte to the filter type.
5858+5959+ @param byte Filter type byte (0-4)
6060+ @return [Some filter] if valid, [None] otherwise *)
6161+val of_byte : int -> t option
6262+6363+(** Convert a filter type to its byte representation.
6464+6565+ @param filter Filter type
6666+ @return Integer 0-4 *)
6767+val to_byte : t -> int
6868+6969+(** {1 Decoding (Unfiltering)}
7070+7171+ Unfiltering reverses the filter transformation to recover the
7272+ original pixel data. This is applied during PNG decoding. *)
7373+7474+(** Unfilter a row in place.
7575+7676+ Reverses the filter transformation on [curr] using the previous row
7777+ [prev] as reference. The [curr] buffer is modified in place.
7878+7979+ As defined in {{:https://www.w3.org/TR/png/#9Filter-types} Section 9.2},
8080+ the reconstruction functions are:
8181+ - None: [Recon(x) = Filt(x)]
8282+ - Sub: [Recon(x) = Filt(x) + Recon(a)]
8383+ - Up: [Recon(x) = Filt(x) + Recon(b)]
8484+ - Average: [Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2)]
8585+ - Paeth: [Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c))]
8686+8787+ @param filter The filter type used on this row
8888+ @param prev Previous (already unfiltered) row, or empty for first row
8989+ @param curr Current row data (modified in place)
9090+ @param bpp Bytes per pixel (for multi-byte pixel alignment) *)
9191+val unfilter_row : t -> prev:bytes -> curr:bytes -> bpp:int -> unit
9292+9393+(** {1 Encoding (Filtering)}
9494+9595+ Filtering transforms pixel data to improve compression.
9696+ This is applied during PNG encoding. *)
9797+9898+(** Filter a row for encoding.
9999+100100+ Applies the filter transformation to [curr] and writes the result
101101+ to [output].
102102+103103+ @param filter Filter type to apply
104104+ @param prev Previous (unfiltered) row, or empty for first row
105105+ @param curr Current unfiltered row
106106+ @param output Buffer to receive filtered data (same length as [curr])
107107+ @param bpp Bytes per pixel *)
108108+val filter_row : t -> prev:bytes -> curr:bytes -> output:bytes -> bpp:int -> unit
109109+110110+(** {1 Adaptive Filter Selection}
111111+112112+ PNG encoders typically select the best filter for each row to
113113+ maximize compression. The standard heuristic is to minimize the
114114+ sum of absolute differences of the filtered output.
115115+116116+ See {{:https://www.w3.org/TR/png/#12Filter-selection} Section 12.8}
117117+ for filter selection recommendations. *)
118118+119119+(** All filter types, for iterating during adaptive selection. *)
120120+val all_filters : t list
121121+122122+(** Select the best filter for a row using the minimum sum heuristic.
123123+124124+ Tries all five filter types and selects the one that produces
125125+ the lowest sum of absolute differences (interpreted as signed bytes).
126126+ This heuristic approximates minimum entropy and typically produces
127127+ good compression.
128128+129129+ @param prev Previous (unfiltered) row, or empty for first row
130130+ @param curr Current unfiltered row
131131+ @param bpp Bytes per pixel
132132+ @return Tuple of (best filter type, filtered data) *)
133133+val select_best_filter : prev:bytes -> curr:bytes -> bpp:int -> t * bytes
134134+135135+(** {1 Paeth Predictor}
136136+137137+ The Paeth predictor is defined in {{:https://www.w3.org/TR/png/#9Filter-type-4-Paeth}
138138+ PNG Spec Section 9.4}. It selects the value (a, b, or c) closest to
139139+ the linear prediction [p = a + b - c]. *)
140140+141141+(** Paeth predictor function.
142142+143143+ Computes the Paeth prediction given three neighboring bytes.
144144+ Returns the value closest to [p = a + b - c].
145145+146146+ @param a Byte to the left (or 0)
147147+ @param b Byte above (or 0)
148148+ @param c Byte above-left (or 0)
149149+ @return Predicted byte value *)
150150+val paeth_predictor : int -> int -> int -> int
+648
vendor/opam/ocaml-png/src/png.ml
···11+(** OCaml PNG Library
22+33+ A pure OCaml implementation of PNG encoding and decoding.
44+ Based on the W3C PNG Specification (Third Edition). *)
55+66+(** {1 Types} *)
77+88+(** Color type as per PNG spec section 11.2.2 *)
99+type color_type =
1010+ | Grayscale (** 0 - 1 sample per pixel *)
1111+ | RGB (** 2 - 3 samples per pixel *)
1212+ | Indexed (** 3 - 1 sample (palette index) *)
1313+ | Grayscale_alpha (** 4 - 2 samples per pixel *)
1414+ | RGBA (** 6 - 4 samples per pixel *)
1515+1616+(** Bit depth: 1, 2, 4, 8, or 16 bits per sample *)
1717+type bit_depth =
1818+ | One
1919+ | Two
2020+ | Four
2121+ | Eight
2222+ | Sixteen
2323+2424+(** Interlace method *)
2525+type interlace =
2626+ | No_interlace (** 0 - Sequential *)
2727+ | Adam7 (** 1 - Adam7 interlacing *)
2828+2929+(** Image header information *)
3030+type info = {
3131+ width : int;
3232+ height : int;
3333+ bit_depth : bit_depth;
3434+ color_type : color_type;
3535+ interlace : interlace;
3636+ palette : bytes option;
3737+ trns : bytes option;
3838+ gamma : float option;
3939+ srgb : int option;
4040+ chrm : (int * int * int * int * int * int * int * int) option;
4141+ bkgd : bytes option;
4242+ phys : (int * int * int) option;
4343+}
4444+4545+(** Decoded image *)
4646+type image = {
4747+ info : info;
4848+ data : bytes; (** Row-major pixel data *)
4949+}
5050+5151+(** {1 Errors} *)
5252+5353+type error =
5454+ | Invalid_signature
5555+ | Invalid_ihdr
5656+ | Invalid_chunk_crc
5757+ | Invalid_chunk_length
5858+ | Invalid_filter of int
5959+ | Invalid_color_type of int
6060+ | Invalid_bit_depth of int
6161+ | Invalid_interlace of int
6262+ | Unsupported of string
6363+ | Truncated
6464+ | Decompression_error of string
6565+6666+exception Png_error of error
6767+6868+let string_of_error = function
6969+ | Invalid_signature -> "Invalid PNG signature"
7070+ | Invalid_ihdr -> "Invalid IHDR chunk"
7171+ | Invalid_chunk_crc -> "Invalid chunk CRC"
7272+ | Invalid_chunk_length -> "Invalid chunk length"
7373+ | Invalid_filter n -> Printf.sprintf "Invalid filter type: %d" n
7474+ | Invalid_color_type n -> Printf.sprintf "Invalid color type: %d" n
7575+ | Invalid_bit_depth n -> Printf.sprintf "Invalid bit depth: %d" n
7676+ | Invalid_interlace n -> Printf.sprintf "Invalid interlace method: %d" n
7777+ | Unsupported s -> Printf.sprintf "Unsupported: %s" s
7878+ | Truncated -> "Truncated PNG data"
7979+ | Decompression_error s -> Printf.sprintf "Decompression error: %s" s
8080+8181+let pp_error fmt e = Format.pp_print_string fmt (string_of_error e)
8282+8383+(** {1 Internal Helpers} *)
8484+8585+(** Extract value from option or raise Png_error. *)
8686+let require_some ~error = function
8787+ | Some x -> x
8888+ | None -> raise (Png_error error)
8989+9090+(** PNG signature: 137 80 78 71 13 10 26 10 *)
9191+let signature = "\137PNG\r\n\026\n"
9292+9393+let color_type_of_int = function
9494+ | 0 -> Some Grayscale
9595+ | 2 -> Some RGB
9696+ | 3 -> Some Indexed
9797+ | 4 -> Some Grayscale_alpha
9898+ | 6 -> Some RGBA
9999+ | _ -> None
100100+101101+let int_of_color_type = function
102102+ | Grayscale -> 0
103103+ | RGB -> 2
104104+ | Indexed -> 3
105105+ | Grayscale_alpha -> 4
106106+ | RGBA -> 6
107107+108108+let bit_depth_of_int = function
109109+ | 1 -> Some One
110110+ | 2 -> Some Two
111111+ | 4 -> Some Four
112112+ | 8 -> Some Eight
113113+ | 16 -> Some Sixteen
114114+ | _ -> None
115115+116116+let int_of_bit_depth = function
117117+ | One -> 1
118118+ | Two -> 2
119119+ | Four -> 4
120120+ | Eight -> 8
121121+ | Sixteen -> 16
122122+123123+let interlace_of_int = function
124124+ | 0 -> Some No_interlace
125125+ | 1 -> Some Adam7
126126+ | _ -> None
127127+128128+(** Check if color type and bit depth combination is valid. *)
129129+let is_valid_combination color_type bit_depth =
130130+ match color_type, bit_depth with
131131+ | Grayscale, (One | Two | Four | Eight | Sixteen) -> true
132132+ | RGB, (Eight | Sixteen) -> true
133133+ | Indexed, (One | Two | Four | Eight) -> true
134134+ | Grayscale_alpha, (Eight | Sixteen) -> true
135135+ | RGBA, (Eight | Sixteen) -> true
136136+ | _ -> false
137137+138138+(** Number of samples per pixel for color type. *)
139139+let samples_per_pixel = function
140140+ | Grayscale -> 1
141141+ | RGB -> 3
142142+ | Indexed -> 1
143143+ | Grayscale_alpha -> 2
144144+ | RGBA -> 4
145145+146146+(** Bytes per pixel (for filtering). Minimum 1 for sub-byte pixels. *)
147147+let bytes_per_pixel color_type bit_depth =
148148+ let bits = samples_per_pixel color_type * int_of_bit_depth bit_depth in
149149+ max 1 (bits / 8)
150150+151151+(** Bytes per row (excluding filter byte). *)
152152+let bytes_per_row width color_type bit_depth =
153153+ let bits_per_row = width * samples_per_pixel color_type * int_of_bit_depth bit_depth in
154154+ (bits_per_row + 7) / 8
155155+156156+(** Read 32-bit big-endian integer from bytes. *)
157157+let get_int32_be bytes pos =
158158+ let b0 = Bytes.get_uint8 bytes pos in
159159+ let b1 = Bytes.get_uint8 bytes (pos + 1) in
160160+ let b2 = Bytes.get_uint8 bytes (pos + 2) in
161161+ let b3 = Bytes.get_uint8 bytes (pos + 3) in
162162+ Int32.(logor (logor (logor
163163+ (shift_left (of_int b0) 24)
164164+ (shift_left (of_int b1) 16))
165165+ (shift_left (of_int b2) 8))
166166+ (of_int b3))
167167+168168+(** Write 32-bit big-endian integer to bytes. *)
169169+let set_int32_be bytes pos value =
170170+ Bytes.set_uint8 bytes pos Int32.(to_int (logand (shift_right_logical value 24) 0xFFl));
171171+ Bytes.set_uint8 bytes (pos + 1) Int32.(to_int (logand (shift_right_logical value 16) 0xFFl));
172172+ Bytes.set_uint8 bytes (pos + 2) Int32.(to_int (logand (shift_right_logical value 8) 0xFFl));
173173+ Bytes.set_uint8 bytes (pos + 3) Int32.(to_int (logand value 0xFFl))
174174+175175+(** {1 Decoder} *)
176176+177177+(** Read and validate PNG signature. *)
178178+let read_signature bytes pos =
179179+ if pos + 8 > Bytes.length bytes then raise (Png_error Truncated);
180180+ for i = 0 to 7 do
181181+ if Bytes.get bytes (pos + i) <> signature.[i] then
182182+ raise (Png_error Invalid_signature)
183183+ done;
184184+ pos + 8
185185+186186+(** Read a chunk: returns (chunk_type, data, next_pos). *)
187187+let read_chunk bytes pos =
188188+ if pos + 4 > Bytes.length bytes then raise (Png_error Truncated);
189189+ let length = Int32.to_int (get_int32_be bytes pos) in
190190+ if length < 0 || pos + 12 + length > Bytes.length bytes then
191191+ raise (Png_error Invalid_chunk_length);
192192+ let chunk_type = Chunk.read_type bytes ~pos:(pos + 4) in
193193+ let data_start = pos + 8 in
194194+ let data = Bytes.sub bytes data_start length in
195195+ let crc_pos = data_start + length in
196196+ let stored_crc = get_int32_be bytes crc_pos in
197197+ (* CRC covers chunk type + data *)
198198+ let computed_crc =
199199+ let crc = Crc32.update Crc32.init bytes ~pos:(pos + 4) ~len:(4 + length) in
200200+ Crc32.finalize crc
201201+ in
202202+ if stored_crc <> computed_crc then raise (Png_error Invalid_chunk_crc);
203203+ (chunk_type, data, crc_pos + 4)
204204+205205+(** Parse IHDR chunk data. *)
206206+let parse_ihdr data =
207207+ if Bytes.length data <> 13 then raise (Png_error Invalid_ihdr);
208208+ let width = Int32.to_int (get_int32_be data 0) in
209209+ let height = Int32.to_int (get_int32_be data 4) in
210210+ let bit_depth_int = Bytes.get_uint8 data 8 in
211211+ let color_type_int = Bytes.get_uint8 data 9 in
212212+ let compression = Bytes.get_uint8 data 10 in
213213+ let filter_method = Bytes.get_uint8 data 11 in
214214+ let interlace_int = Bytes.get_uint8 data 12 in
215215+216216+ if compression <> 0 then
217217+ raise (Png_error (Unsupported "unknown compression method"));
218218+ if filter_method <> 0 then
219219+ raise (Png_error (Unsupported "unknown filter method"));
220220+221221+ let bit_depth =
222222+ bit_depth_of_int bit_depth_int
223223+ |> require_some ~error:(Invalid_bit_depth bit_depth_int)
224224+ in
225225+ let color_type =
226226+ color_type_of_int color_type_int
227227+ |> require_some ~error:(Invalid_color_type color_type_int)
228228+ in
229229+ let interlace =
230230+ interlace_of_int interlace_int
231231+ |> require_some ~error:(Invalid_interlace interlace_int)
232232+ in
233233+234234+ if not (is_valid_combination color_type bit_depth) then
235235+ raise (Png_error (Unsupported "invalid color type / bit depth combination"));
236236+237237+ {
238238+ width;
239239+ height;
240240+ bit_depth;
241241+ color_type;
242242+ interlace;
243243+ palette = None;
244244+ trns = None;
245245+ gamma = None;
246246+ srgb = None;
247247+ chrm = None;
248248+ bkgd = None;
249249+ phys = None;
250250+ }
251251+252252+(** Decode PNG from bytes. *)
253253+let decode bytes =
254254+ let len = Bytes.length bytes in
255255+ if len < 8 then raise (Png_error Truncated);
256256+257257+ (* Read signature *)
258258+ let pos = read_signature bytes 0 in
259259+260260+ (* Read IHDR (must be first chunk) *)
261261+ let (chunk_type, ihdr_data, pos) = read_chunk bytes pos in
262262+ if chunk_type <> Chunk.ihdr then raise (Png_error Invalid_ihdr);
263263+ let info = ref (parse_ihdr ihdr_data) in
264264+265265+ (* Collect IDAT chunks and ancillary chunks *)
266266+ let idat_chunks = ref [] in
267267+ let pos = ref pos in
268268+ let done_reading = ref false in
269269+270270+ while not !done_reading && !pos < len do
271271+ let (chunk_type, data, next_pos) = read_chunk bytes !pos in
272272+ pos := next_pos;
273273+274274+ if chunk_type = Chunk.idat then
275275+ idat_chunks := data :: !idat_chunks
276276+ else if chunk_type = Chunk.iend then
277277+ done_reading := true
278278+ else if chunk_type = Chunk.plte then
279279+ info := { !info with palette = Some data }
280280+ else if chunk_type = Chunk.trns then
281281+ info := { !info with trns = Some data }
282282+ else if chunk_type = Chunk.gama then begin
283283+ if Bytes.length data >= 4 then
284284+ let gamma_int = Int32.to_int (get_int32_be data 0) in
285285+ info := { !info with gamma = Some (float_of_int gamma_int /. 100000.0) }
286286+ end
287287+ else if chunk_type = Chunk.srgb then begin
288288+ if Bytes.length data >= 1 then
289289+ info := { !info with srgb = Some (Bytes.get_uint8 data 0) }
290290+ end
291291+ else if chunk_type = Chunk.bkgd then
292292+ info := { !info with bkgd = Some data }
293293+ else if chunk_type = Chunk.phys then begin
294294+ if Bytes.length data >= 9 then
295295+ let xppu = Int32.to_int (get_int32_be data 0) in
296296+ let yppu = Int32.to_int (get_int32_be data 4) in
297297+ let unit_type = Bytes.get_uint8 data 8 in
298298+ info := { !info with phys = Some (xppu, yppu, unit_type) }
299299+ end
300300+ (* Ignore other ancillary chunks *)
301301+ done;
302302+303303+ let info = !info in
304304+305305+ (* Concatenate IDAT data *)
306306+ let idat_chunks = List.rev !idat_chunks in
307307+ let idat_total_len = List.fold_left (fun acc d -> acc + Bytes.length d) 0 idat_chunks in
308308+ let idat_data = Bytes.create idat_total_len in
309309+ let _ = List.fold_left (fun off d ->
310310+ Bytes.blit d 0 idat_data off (Bytes.length d);
311311+ off + Bytes.length d
312312+ ) 0 idat_chunks in
313313+314314+ (* Decompress IDAT data using decompress library Higher API *)
315315+ let decompressed =
316316+ try
317317+ let str = Bytes.to_string idat_data in
318318+ let i = De.bigstring_create De.io_buffer_size in
319319+ let o = De.bigstring_create De.io_buffer_size in
320320+ let allocate bits = De.make_window ~bits in
321321+ let r = Buffer.create (info.height * (bytes_per_row info.width info.color_type info.bit_depth + 1)) in
322322+ let p = ref 0 in
323323+ let refill buf =
324324+ let len = min (String.length str - !p) De.io_buffer_size in
325325+ Bigstringaf.blit_from_string str ~src_off:!p buf ~dst_off:0 ~len;
326326+ p := !p + len;
327327+ len
328328+ in
329329+ let flush buf len =
330330+ let s = Bigstringaf.substring buf ~off:0 ~len in
331331+ Buffer.add_string r s
332332+ in
333333+ match Zl.Higher.uncompress ~allocate ~refill ~flush i o with
334334+ | Ok () -> Bytes.of_string (Buffer.contents r)
335335+ | Error (`Msg msg) -> raise (Png_error (Decompression_error msg))
336336+ with
337337+ | Png_error _ as e -> raise e
338338+ | e -> raise (Png_error (Decompression_error (Printexc.to_string e)))
339339+ in
340340+341341+ (* Calculate dimensions *)
342342+ let row_bytes = bytes_per_row info.width info.color_type info.bit_depth in
343343+ let bpp = bytes_per_pixel info.color_type info.bit_depth in
344344+345345+ (* Handle interlacing *)
346346+ let image_data =
347347+ match info.interlace with
348348+ | No_interlace ->
349349+ (* Non-interlaced: each row is filter_byte + row_data *)
350350+ let expected_len = info.height * (1 + row_bytes) in
351351+ if Bytes.length decompressed < expected_len then
352352+ raise (Png_error Truncated);
353353+354354+ let output = Bytes.create (info.height * row_bytes) in
355355+ let prev_row = ref Bytes.empty in
356356+357357+ for y = 0 to info.height - 1 do
358358+ let row_start = y * (1 + row_bytes) in
359359+ let filter_byte = Bytes.get_uint8 decompressed row_start in
360360+ let filter =
361361+ Filter.of_byte filter_byte
362362+ |> require_some ~error:(Invalid_filter filter_byte)
363363+ in
364364+ let curr_row = Bytes.sub decompressed (row_start + 1) row_bytes in
365365+ Filter.unfilter_row filter ~prev:!prev_row ~curr:curr_row ~bpp;
366366+ Bytes.blit curr_row 0 output (y * row_bytes) row_bytes;
367367+ prev_row := curr_row
368368+ done;
369369+ output
370370+371371+ | Adam7 ->
372372+ (* Adam7 interlacing: 7 passes with sub-images *)
373373+ let output = Bytes.create (info.height * row_bytes) in
374374+ (* Initialize to zero *)
375375+ Bytes.fill output 0 (Bytes.length output) '\000';
376376+377377+ (* Adam7 pass parameters: (x_offset, y_offset, x_step, y_step) *)
378378+ let passes = [|
379379+ (0, 0, 8, 8); (* Pass 1 *)
380380+ (4, 0, 8, 8); (* Pass 2 *)
381381+ (0, 4, 4, 8); (* Pass 3 *)
382382+ (2, 0, 4, 4); (* Pass 4 *)
383383+ (0, 2, 2, 4); (* Pass 5 *)
384384+ (1, 0, 2, 2); (* Pass 6 *)
385385+ (0, 1, 1, 2); (* Pass 7 *)
386386+ |] in
387387+388388+ let src_pos = ref 0 in
389389+390390+ for pass = 0 to 6 do
391391+ let (x_off, y_off, x_step, y_step) = passes.(pass) in
392392+393393+ (* Calculate sub-image dimensions for this pass *)
394394+ let pass_width = (info.width - x_off + x_step - 1) / x_step in
395395+ let pass_height = (info.height - y_off + y_step - 1) / y_step in
396396+397397+ if pass_width > 0 && pass_height > 0 then begin
398398+ let pass_row_bytes = bytes_per_row pass_width info.color_type info.bit_depth in
399399+ let pass_bpp = bpp in
400400+ let prev_row = ref Bytes.empty in
401401+402402+ for py = 0 to pass_height - 1 do
403403+ if !src_pos >= Bytes.length decompressed then
404404+ raise (Png_error Truncated);
405405+406406+ let filter_byte = Bytes.get_uint8 decompressed !src_pos in
407407+ let filter =
408408+ Filter.of_byte filter_byte
409409+ |> require_some ~error:(Invalid_filter filter_byte)
410410+ in
411411+ incr src_pos;
412412+413413+ if !src_pos + pass_row_bytes > Bytes.length decompressed then
414414+ raise (Png_error Truncated);
415415+416416+ let curr_row = Bytes.sub decompressed !src_pos pass_row_bytes in
417417+ src_pos := !src_pos + pass_row_bytes;
418418+419419+ Filter.unfilter_row filter ~prev:!prev_row ~curr:curr_row ~bpp:pass_bpp;
420420+421421+ (* Expand this row into the output image *)
422422+ let dest_y = y_off + py * y_step in
423423+ let bits_per_pixel = samples_per_pixel info.color_type * int_of_bit_depth info.bit_depth in
424424+425425+ if bits_per_pixel >= 8 then begin
426426+ (* Byte-aligned pixels *)
427427+ let pixel_bytes = bits_per_pixel / 8 in
428428+ for px = 0 to pass_width - 1 do
429429+ let dest_x = x_off + px * x_step in
430430+ let src_offset = px * pixel_bytes in
431431+ let dest_offset = dest_y * row_bytes + dest_x * pixel_bytes in
432432+ Bytes.blit curr_row src_offset output dest_offset pixel_bytes
433433+ done
434434+ end else begin
435435+ (* Sub-byte pixels (1, 2, or 4 bits) *)
436436+ let pixels_per_byte = 8 / bits_per_pixel in
437437+ for px = 0 to pass_width - 1 do
438438+ let dest_x = x_off + px * x_step in
439439+ (* Extract pixel from source *)
440440+ let src_byte_idx = px / pixels_per_byte in
441441+ let src_bit_idx = (pixels_per_byte - 1 - (px mod pixels_per_byte)) * bits_per_pixel in
442442+ let src_byte = Bytes.get_uint8 curr_row src_byte_idx in
443443+ let mask = (1 lsl bits_per_pixel) - 1 in
444444+ let pixel = (src_byte lsr src_bit_idx) land mask in
445445+ (* Write pixel to destination *)
446446+ let dest_byte_idx = dest_y * row_bytes + dest_x / pixels_per_byte in
447447+ let dest_bit_idx = (pixels_per_byte - 1 - (dest_x mod pixels_per_byte)) * bits_per_pixel in
448448+ let dest_byte = Bytes.get_uint8 output dest_byte_idx in
449449+ let clear_mask = lnot (mask lsl dest_bit_idx) in
450450+ let new_byte = (dest_byte land clear_mask) lor (pixel lsl dest_bit_idx) in
451451+ Bytes.set_uint8 output dest_byte_idx new_byte
452452+ done
453453+ end;
454454+455455+ prev_row := curr_row
456456+ done
457457+ end
458458+ done;
459459+ output
460460+ in
461461+462462+ { info; data = image_data }
463463+464464+(** Decode PNG from file. *)
465465+let decode_file filename =
466466+ let ic = open_in_bin filename in
467467+ let len = in_channel_length ic in
468468+ let bytes = Bytes.create len in
469469+ really_input ic bytes 0 len;
470470+ close_in ic;
471471+ decode bytes
472472+473473+(** {1 Encoder} *)
474474+475475+(** Write a chunk to a buffer list. *)
476476+let write_chunk chunks chunk_type data =
477477+ let length = Bytes.length data in
478478+ let chunk = Bytes.create (12 + length) in
479479+ set_int32_be chunk 0 (Int32.of_int length);
480480+ Chunk.write_type chunk ~pos:4 chunk_type;
481481+ Bytes.blit data 0 chunk 8 length;
482482+ (* Compute CRC over type + data *)
483483+ let crc = Crc32.update Crc32.init chunk ~pos:4 ~len:(4 + length) in
484484+ let crc = Crc32.finalize crc in
485485+ set_int32_be chunk (8 + length) crc;
486486+ chunks := chunk :: !chunks
487487+488488+(** Encode image to PNG bytes. *)
489489+let encode ?(compression=6) ?(filter_strategy=`Adaptive) image =
490490+ let info = image.info in
491491+ let chunks = ref [] in
492492+493493+ (* Write IHDR *)
494494+ let ihdr = Bytes.create 13 in
495495+ set_int32_be ihdr 0 (Int32.of_int info.width);
496496+ set_int32_be ihdr 4 (Int32.of_int info.height);
497497+ Bytes.set_uint8 ihdr 8 (int_of_bit_depth info.bit_depth);
498498+ Bytes.set_uint8 ihdr 9 (int_of_color_type info.color_type);
499499+ Bytes.set_uint8 ihdr 10 0; (* compression method *)
500500+ Bytes.set_uint8 ihdr 11 0; (* filter method *)
501501+ Bytes.set_uint8 ihdr 12 (match info.interlace with No_interlace -> 0 | Adam7 -> 1);
502502+ write_chunk chunks Chunk.ihdr ihdr;
503503+504504+ (* Write PLTE if indexed *)
505505+ (match info.palette with
506506+ | Some palette -> write_chunk chunks Chunk.plte palette
507507+ | None -> ());
508508+509509+ (* Write tRNS if present *)
510510+ (match info.trns with
511511+ | Some trns -> write_chunk chunks Chunk.trns trns
512512+ | None -> ());
513513+514514+ (* Write gAMA if present *)
515515+ (match info.gamma with
516516+ | Some gamma ->
517517+ let gama = Bytes.create 4 in
518518+ set_int32_be gama 0 (Int32.of_int (int_of_float (gamma *. 100000.0)));
519519+ write_chunk chunks Chunk.gama gama
520520+ | None -> ());
521521+522522+ (* Write sRGB if present *)
523523+ (match info.srgb with
524524+ | Some intent ->
525525+ let srgb = Bytes.create 1 in
526526+ Bytes.set_uint8 srgb 0 intent;
527527+ write_chunk chunks Chunk.srgb srgb
528528+ | None -> ());
529529+530530+ (* Prepare filtered image data *)
531531+ let row_bytes = bytes_per_row info.width info.color_type info.bit_depth in
532532+ let bpp = bytes_per_pixel info.color_type info.bit_depth in
533533+534534+ (* Only support non-interlaced for now *)
535535+ let filtered_data =
536536+ match info.interlace with
537537+ | No_interlace ->
538538+ let output = Bytes.create (info.height * (1 + row_bytes)) in
539539+ let prev_row = ref Bytes.empty in
540540+ let filter_output = Bytes.create row_bytes in
541541+542542+ for y = 0 to info.height - 1 do
543543+ let curr_row = Bytes.sub image.data (y * row_bytes) row_bytes in
544544+545545+ let (filter, filtered) = match filter_strategy with
546546+ | `Adaptive ->
547547+ Filter.select_best_filter ~prev:!prev_row ~curr:curr_row ~bpp
548548+ | `Fixed f ->
549549+ Filter.filter_row f ~prev:!prev_row ~curr:curr_row ~output:filter_output ~bpp;
550550+ (f, filter_output)
551551+ in
552552+553553+ let out_pos = y * (1 + row_bytes) in
554554+ Bytes.set_uint8 output out_pos (Filter.to_byte filter);
555555+ Bytes.blit filtered 0 output (out_pos + 1) row_bytes;
556556+ prev_row := curr_row
557557+ done;
558558+ output
559559+ | Adam7 ->
560560+ raise (Png_error (Unsupported "Adam7 encoding not yet implemented"))
561561+ in
562562+563563+ (* Compress using decompress (zlib) Higher API *)
564564+ let compressed =
565565+ try
566566+ let str = Bytes.to_string filtered_data in
567567+ let i = De.bigstring_create De.io_buffer_size in
568568+ let o = De.bigstring_create De.io_buffer_size in
569569+ let w = De.Lz77.make_window ~bits:15 in
570570+ let q = De.Queue.create 0x1000 in
571571+ let r = Buffer.create (Bytes.length filtered_data) in
572572+ let p = ref 0 in
573573+ let refill buf =
574574+ let len = min (String.length str - !p) De.io_buffer_size in
575575+ Bigstringaf.blit_from_string str ~src_off:!p buf ~dst_off:0 ~len;
576576+ p := !p + len;
577577+ len
578578+ in
579579+ let flush buf len =
580580+ let s = Bigstringaf.substring buf ~off:0 ~len in
581581+ Buffer.add_string r s
582582+ in
583583+ Zl.Higher.compress ~level:compression ~dynamic:true ~w ~q ~refill ~flush i o;
584584+ Bytes.of_string (Buffer.contents r)
585585+ with e ->
586586+ raise (Png_error (Decompression_error ("Compression failed: " ^ Printexc.to_string e)))
587587+ in
588588+589589+ (* Write IDAT chunks (split at 8KB boundaries) *)
590590+ let idat_chunk_size = 8192 in
591591+ let compressed_len = Bytes.length compressed in
592592+ let rec write_idat pos =
593593+ if pos < compressed_len then begin
594594+ let chunk_len = min idat_chunk_size (compressed_len - pos) in
595595+ let chunk_data = Bytes.sub compressed pos chunk_len in
596596+ write_chunk chunks Chunk.idat chunk_data;
597597+ write_idat (pos + chunk_len)
598598+ end
599599+ in
600600+ write_idat 0;
601601+602602+ (* Write IEND *)
603603+ write_chunk chunks Chunk.iend Bytes.empty;
604604+605605+ (* Combine signature + chunks *)
606606+ let chunks = List.rev !chunks in
607607+ let total_len = 8 + List.fold_left (fun acc c -> acc + Bytes.length c) 0 chunks in
608608+ let output = Bytes.create total_len in
609609+ Bytes.blit_string signature 0 output 0 8;
610610+ let _ = List.fold_left (fun pos c ->
611611+ Bytes.blit c 0 output pos (Bytes.length c);
612612+ pos + Bytes.length c
613613+ ) 8 chunks in
614614+ output
615615+616616+(** Encode image to PNG file. *)
617617+let encode_file ?compression ?filter_strategy image filename =
618618+ let data = encode ?compression ?filter_strategy image in
619619+ let oc = open_out_bin filename in
620620+ output_bytes oc data;
621621+ close_out oc
622622+623623+(** {1 Utilities} *)
624624+625625+(** Create a simple info structure. *)
626626+let make_info ~width ~height ?(bit_depth=Eight) ?(color_type=RGBA) () =
627627+ {
628628+ width;
629629+ height;
630630+ bit_depth;
631631+ color_type;
632632+ interlace = No_interlace;
633633+ palette = None;
634634+ trns = None;
635635+ gamma = None;
636636+ srgb = None;
637637+ chrm = None;
638638+ bkgd = None;
639639+ phys = None;
640640+ }
641641+642642+(** Create an image from raw pixel data. *)
643643+let make_image ~info ~data =
644644+ let expected = bytes_per_row info.width info.color_type info.bit_depth * info.height in
645645+ if Bytes.length data <> expected then
646646+ invalid_arg (Printf.sprintf "make_image: data length %d, expected %d"
647647+ (Bytes.length data) expected);
648648+ { info; data }
+332
vendor/opam/ocaml-png/src/png.mli
···11+(** {1 OCaml PNG Library}
22+33+ A pure OCaml implementation of PNG (Portable Network Graphics) encoding
44+ and decoding, based on the {{:https://www.w3.org/TR/png/} W3C PNG
55+ Specification (Third Edition)}.
66+77+ {2 Overview}
88+99+ This library provides:
1010+ - Decoding of PNG images to raw pixel data
1111+ - Encoding of raw pixel data to PNG format
1212+ - Support for all standard color types and bit depths
1313+ - Adam7 interlace decoding
1414+ - Adaptive filter selection for optimal compression
1515+1616+ {2 Quick Start}
1717+1818+ {[
1919+ (* Decode a PNG file *)
2020+ let image = Png.decode_file "input.png"
2121+2222+ (* Access image properties *)
2323+ let width = image.info.width
2424+ let height = image.info.height
2525+2626+ (* Encode and save *)
2727+ Png.encode_file image "output.png"
2828+ ]}
2929+3030+ {2 References}
3131+3232+ - {{:https://www.w3.org/TR/png/} W3C PNG Specification (Third Edition)}
3333+ - {{:https://www.w3.org/TR/png/#5Chunk-layout} Chunk Layout (Section 5)}
3434+ - {{:https://www.w3.org/TR/png/#9Filters} Filtering (Section 9)}
3535+ - {{:https://www.w3.org/TR/png/#8Interlace} Interlacing (Section 8)} *)
3636+3737+(** {1 Types} *)
3838+3939+(** Color type defines how pixel data is stored.
4040+4141+ As defined in {{:https://www.w3.org/TR/png/#6Colour-values} PNG Spec
4242+ Section 6.1}, the color type is a single-byte integer that describes
4343+ the interpretation of the image data.
4444+4545+ Valid combinations with bit depth are defined in
4646+ {{:https://www.w3.org/TR/png/#table111} Table 11.1}. *)
4747+type color_type =
4848+ | Grayscale (** Color type 0: Each pixel is a grayscale sample *)
4949+ | RGB (** Color type 2: Each pixel is an R,G,B triple *)
5050+ | Indexed (** Color type 3: Each pixel is a palette index *)
5151+ | Grayscale_alpha (** Color type 4: Grayscale sample followed by alpha *)
5252+ | RGBA (** Color type 6: R,G,B triple followed by alpha *)
5353+5454+(** Bit depth specifies the number of bits per sample or palette index.
5555+5656+ As defined in {{:https://www.w3.org/TR/png/#11IHDR} PNG Spec Section 11.2.2},
5757+ valid bit depths depend on the color type:
5858+ - Grayscale: 1, 2, 4, 8, 16
5959+ - RGB: 8, 16
6060+ - Indexed: 1, 2, 4, 8
6161+ - Grayscale+Alpha: 8, 16
6262+ - RGBA: 8, 16 *)
6363+type bit_depth =
6464+ | One (** 1 bit per sample *)
6565+ | Two (** 2 bits per sample *)
6666+ | Four (** 4 bits per sample *)
6767+ | Eight (** 8 bits per sample *)
6868+ | Sixteen (** 16 bits per sample *)
6969+7070+(** Interlace method as defined in {{:https://www.w3.org/TR/png/#8Interlace}
7171+ PNG Spec Section 8}.
7272+7373+ Adam7 interlacing allows progressive display of images by transmitting
7474+ pixels in 7 passes, each filling in more detail. *)
7575+type interlace =
7676+ | No_interlace (** Method 0: No interlacing, pixels stored sequentially *)
7777+ | Adam7 (** Method 1: Adam7 interlacing with 7 passes *)
7878+7979+(** Image header information containing all metadata from IHDR and
8080+ ancillary chunks.
8181+8282+ The core fields (width, height, bit_depth, color_type, interlace) come
8383+ from the IHDR chunk as specified in {{:https://www.w3.org/TR/png/#11IHDR}
8484+ PNG Spec Section 11.2.2}.
8585+8686+ Optional fields come from ancillary chunks:
8787+ - [palette]: PLTE chunk ({{:https://www.w3.org/TR/png/#11PLTE} Section 11.2.3})
8888+ - [trns]: tRNS chunk ({{:https://www.w3.org/TR/png/#11tRNS} Section 11.3.2})
8989+ - [gamma]: gAMA chunk ({{:https://www.w3.org/TR/png/#11gAMA} Section 11.3.3})
9090+ - [srgb]: sRGB chunk ({{:https://www.w3.org/TR/png/#11sRGB} Section 11.3.4})
9191+ - [chrm]: cHRM chunk ({{:https://www.w3.org/TR/png/#11cHRM} Section 11.3.5})
9292+ - [bkgd]: bKGD chunk ({{:https://www.w3.org/TR/png/#11bKGD} Section 11.3.6})
9393+ - [phys]: pHYs chunk ({{:https://www.w3.org/TR/png/#11pHYs} Section 11.3.7}) *)
9494+type info = {
9595+ width : int;
9696+ (** Image width in pixels (1 to 2^31-1) *)
9797+9898+ height : int;
9999+ (** Image height in pixels (1 to 2^31-1) *)
100100+101101+ bit_depth : bit_depth;
102102+ (** Number of bits per sample or palette index *)
103103+104104+ color_type : color_type;
105105+ (** Defines how pixel values are interpreted *)
106106+107107+ interlace : interlace;
108108+ (** Interlacing method used *)
109109+110110+ palette : bytes option;
111111+ (** PLTE: Color palette for indexed images. Contains 1-256 RGB entries
112112+ (3 bytes each). Required for color type 3. *)
113113+114114+ trns : bytes option;
115115+ (** tRNS: Transparency information.
116116+ - Grayscale: 2 bytes (gray value)
117117+ - RGB: 6 bytes (RGB value)
118118+ - Indexed: 0-256 bytes (alpha for each palette entry) *)
119119+120120+ gamma : float option;
121121+ (** gAMA: Image gamma value. Stored as gamma * 100000 in PNG. *)
122122+123123+ srgb : int option;
124124+ (** sRGB: Rendering intent (0-3). Indicates image uses sRGB color space. *)
125125+126126+ chrm : (int * int * int * int * int * int * int * int) option;
127127+ (** cHRM: Chromaticity values (white_x, white_y, red_x, red_y,
128128+ green_x, green_y, blue_x, blue_y) scaled by 100000. *)
129129+130130+ bkgd : bytes option;
131131+ (** bKGD: Background color for compositing. Format depends on color type. *)
132132+133133+ phys : (int * int * int) option;
134134+ (** pHYs: Physical pixel dimensions (x_pixels_per_unit, y_pixels_per_unit,
135135+ unit_specifier). Unit 0 = unknown, 1 = meter. *)
136136+}
137137+138138+(** A decoded PNG image containing header information and raw pixel data.
139139+140140+ The [data] field contains uncompressed, unfiltered pixel data in
141141+ row-major order. For multi-byte samples, bytes are in big-endian order
142142+ as per {{:https://www.w3.org/TR/png/#7Integers-and-byte-order}
143143+ PNG Spec Section 7}. *)
144144+type image = {
145145+ info : info;
146146+ (** Image metadata *)
147147+148148+ data : bytes;
149149+ (** Raw pixel data in row-major order. Size is
150150+ [height * bytes_per_row width color_type bit_depth]. *)
151151+}
152152+153153+(** {1 Errors} *)
154154+155155+(** Errors that can occur during PNG processing. *)
156156+type error =
157157+ | Invalid_signature
158158+ (** PNG signature (first 8 bytes) is incorrect.
159159+ See {{:https://www.w3.org/TR/png/#5PNG-file-signature} Section 5.2}. *)
160160+161161+ | Invalid_ihdr
162162+ (** IHDR chunk is missing, malformed, or not first chunk. *)
163163+164164+ | Invalid_chunk_crc
165165+ (** CRC32 checksum of chunk does not match stored value.
166166+ See {{:https://www.w3.org/TR/png/#5CRC-algorithm} Section 5.5}. *)
167167+168168+ | Invalid_chunk_length
169169+ (** Chunk length is negative or exceeds file bounds. *)
170170+171171+ | Invalid_filter of int
172172+ (** Unknown filter type byte (valid: 0-4).
173173+ See {{:https://www.w3.org/TR/png/#9Filter-types} Section 9.2}. *)
174174+175175+ | Invalid_color_type of int
176176+ (** Unknown color type (valid: 0, 2, 3, 4, 6). *)
177177+178178+ | Invalid_bit_depth of int
179179+ (** Invalid bit depth for the color type. *)
180180+181181+ | Invalid_interlace of int
182182+ (** Unknown interlace method (valid: 0, 1). *)
183183+184184+ | Unsupported of string
185185+ (** Feature is not supported (e.g., Adam7 encoding). *)
186186+187187+ | Truncated
188188+ (** Unexpected end of data. *)
189189+190190+ | Decompression_error of string
191191+ (** Zlib decompression failed. *)
192192+193193+(** Exception raised for PNG processing errors. *)
194194+exception Png_error of error
195195+196196+(** Convert an error to a human-readable string. *)
197197+val string_of_error : error -> string
198198+199199+(** Pretty-printer for errors (for use with [Format]). *)
200200+val pp_error : Format.formatter -> error -> unit
201201+202202+(** {1 Decoding}
203203+204204+ PNG decoding follows the process defined in
205205+ {{:https://www.w3.org/TR/png/#4Concepts} PNG Spec Section 4}:
206206+ 1. Validate PNG signature
207207+ 2. Parse IHDR chunk
208208+ 3. Collect and decompress IDAT chunks
209209+ 4. Apply inverse filtering to each row
210210+ 5. Handle interlacing if present *)
211211+212212+(** Decode a PNG image from bytes.
213213+214214+ @param bytes Raw PNG file data
215215+ @return Decoded image with pixel data
216216+ @raise Png_error if the data is not a valid PNG *)
217217+val decode : bytes -> image
218218+219219+(** Decode a PNG image from a file.
220220+221221+ @param filename Path to PNG file
222222+ @return Decoded image with pixel data
223223+ @raise Png_error if the file is not a valid PNG
224224+ @raise Sys_error if file cannot be opened *)
225225+val decode_file : string -> image
226226+227227+(** {1 Encoding}
228228+229229+ PNG encoding follows the process defined in
230230+ {{:https://www.w3.org/TR/png/#4Concepts} PNG Spec Section 4}:
231231+ 1. Write PNG signature
232232+ 2. Write IHDR chunk
233233+ 3. Write ancillary chunks (PLTE, tRNS, gAMA, etc.)
234234+ 4. Apply filtering to each row
235235+ 5. Compress filtered data with zlib
236236+ 6. Write IDAT chunks
237237+ 7. Write IEND chunk *)
238238+239239+(** Encode an image to PNG bytes.
240240+241241+ @param compression Zlib compression level (0-9, default 6)
242242+ @param filter_strategy Filter selection strategy:
243243+ - [`Adaptive]: Try all filters, pick best (default)
244244+ - [`Fixed f]: Use filter [f] for all rows
245245+ @param image Image to encode
246246+ @return PNG file data as bytes
247247+ @raise Png_error if encoding fails (e.g., Adam7 not supported) *)
248248+val encode :
249249+ ?compression:int ->
250250+ ?filter_strategy:[`Adaptive | `Fixed of Filter.t] ->
251251+ image -> bytes
252252+253253+(** Encode an image and write to a file.
254254+255255+ @param compression Zlib compression level (0-9, default 6)
256256+ @param filter_strategy Filter selection strategy
257257+ @param image Image to encode
258258+ @param filename Output file path
259259+ @raise Png_error if encoding fails
260260+ @raise Sys_error if the file cannot be written *)
261261+val encode_file :
262262+ ?compression:int ->
263263+ ?filter_strategy:[`Adaptive | `Fixed of Filter.t] ->
264264+ image -> string -> unit
265265+266266+(** {1 Utilities} *)
267267+268268+(** Create an image info structure with default values.
269269+270270+ @param width Image width in pixels
271271+ @param height Image height in pixels
272272+ @param bit_depth Bit depth (default: [Eight])
273273+ @param color_type Color type (default: [RGBA])
274274+ @return Info structure with no ancillary data *)
275275+val make_info :
276276+ width:int ->
277277+ height:int ->
278278+ ?bit_depth:bit_depth ->
279279+ ?color_type:color_type ->
280280+ unit -> info
281281+282282+(** Create an image from info and raw pixel data.
283283+284284+ @param info Image metadata
285285+ @param data Raw pixel data (must match expected size)
286286+ @return Complete image structure
287287+ @raise Invalid_argument if data size does not match expected dimensions *)
288288+val make_image : info:info -> data:bytes -> image
289289+290290+(** {2 Pixel Format Helpers} *)
291291+292292+(** Number of samples per pixel for the given color type.
293293+294294+ - Grayscale: 1
295295+ - RGB: 3
296296+ - Indexed: 1
297297+ - Grayscale+Alpha: 2
298298+ - RGBA: 4 *)
299299+val samples_per_pixel : color_type -> int
300300+301301+(** Bytes per pixel for filtering purposes.
302302+303303+ For sub-byte pixels (bit depth < 8), returns 1.
304304+ Otherwise returns [samples_per_pixel * bit_depth / 8]. *)
305305+val bytes_per_pixel : color_type -> bit_depth -> int
306306+307307+(** Bytes per row of pixel data (excluding filter byte).
308308+309309+ Calculated as: [(width * samples * bit_depth + 7) / 8] *)
310310+val bytes_per_row : int -> color_type -> bit_depth -> int
311311+312312+(** Check if a color type / bit depth combination is valid per PNG spec.
313313+314314+ See {{:https://www.w3.org/TR/png/#table111} Table 11.1}. *)
315315+val is_valid_combination : color_type -> bit_depth -> bool
316316+317317+(** {2 Type Conversions} *)
318318+319319+(** Convert integer to color type. Returns [None] for invalid values. *)
320320+val color_type_of_int : int -> color_type option
321321+322322+(** Convert color type to its integer representation. *)
323323+val int_of_color_type : color_type -> int
324324+325325+(** Convert integer to bit depth. Returns [None] for invalid values. *)
326326+val bit_depth_of_int : int -> bit_depth option
327327+328328+(** Convert bit depth to its integer representation. *)
329329+val int_of_bit_depth : bit_depth -> int
330330+331331+(** Convert integer to interlace method. Returns [None] for invalid values. *)
332332+val interlace_of_int : int -> interlace option
+57
vendor/opam/ocaml-png/test/debug.ml
···11+let deflate_string ?(level=6) str =
22+ let i = De.bigstring_create De.io_buffer_size in
33+ let o = De.bigstring_create De.io_buffer_size in
44+ let w = De.Lz77.make_window ~bits:15 in
55+ let q = De.Queue.create 0x1000 in
66+ let r = Buffer.create 0x1000 in
77+ let p = ref 0 in
88+ let refill buf =
99+ let len = min (String.length str - !p) De.io_buffer_size in
1010+ Bigstringaf.blit_from_string str ~src_off:!p buf ~dst_off:0 ~len;
1111+ p := !p + len;
1212+ len
1313+ in
1414+ let flush buf len =
1515+ let s = Bigstringaf.substring buf ~off:0 ~len in
1616+ Buffer.add_string r s
1717+ in
1818+ Zl.Higher.compress ~level ~dynamic:true ~w ~q ~refill ~flush i o;
1919+ Buffer.contents r
2020+2121+let inflate_string str =
2222+ let i = De.bigstring_create De.io_buffer_size in
2323+ let o = De.bigstring_create De.io_buffer_size in
2424+ let allocate bits = De.make_window ~bits in
2525+ let r = Buffer.create 0x1000 in
2626+ let p = ref 0 in
2727+ let refill buf =
2828+ let len = min (String.length str - !p) De.io_buffer_size in
2929+ Bigstringaf.blit_from_string str ~src_off:!p buf ~dst_off:0 ~len;
3030+ p := !p + len;
3131+ len
3232+ in
3333+ let flush buf len =
3434+ let s = Bigstringaf.substring buf ~off:0 ~len in
3535+ Buffer.add_string r s
3636+ in
3737+ match Zl.Higher.uncompress ~allocate ~refill ~flush i o with
3838+ | Ok () -> Ok (Buffer.contents r)
3939+ | Error _ as err -> err
4040+4141+let () =
4242+ let test_data = "Hello, this is a test of the zlib compression. Let's make it longer to test properly!" in
4343+ Printf.printf "Original: %d bytes\n%!" (String.length test_data);
4444+4545+ let compressed = deflate_string test_data in
4646+ Printf.printf "Compressed: %d bytes\n%!" (String.length compressed);
4747+4848+ match inflate_string compressed with
4949+ | Ok recovered ->
5050+ Printf.printf "Recovered: %d bytes\n%!" (String.length recovered);
5151+ Printf.printf "Match: %b\n%!" (test_data = recovered);
5252+ if test_data = recovered then
5353+ Printf.printf "SUCCESS!\n%!"
5454+ else
5555+ Printf.printf "Data mismatch!\n%!"
5656+ | Error (`Msg msg) ->
5757+ Printf.printf "Error: %s\n%!" msg