Sign in

    png

    A streaming PNG and APNG decoder for MoonBit: arbitrary input chunks, whole rows into bounded output slices, two rows of memory.

    png
    apng
    image
    decoder
    streaming
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    11 hours ago
    Downloads
    9

    #gaato/png

    A streaming PNG decoder in pure MoonBit, for Native, JS, Wasm and Wasm GC. It takes input in chunks of any size, writes whole rows into output slices you provide, checks every CRC and the zlib stream's Adler-32, and holds two rows and a zlib decoder, whatever the image's height.

    It reads every color type and bit depth, interlaced or not, the ancillary chunks of the specification's third edition and APNG's frames, and converts rows to RGBA. Putting an animation's frames together on a canvas is left to the caller.

    #Install

    moon add gaato/png

    #Usage

    Decoder implements the image contract of gaato/codec_image. Each step consumes a prefix of the input and says why it stopped: the image's header is known (Header), its frame begins (Frame), whole rows were written (Rows), the frame's data has passed every check (FrameEnd), the image is complete (Done), or it needs input or room. Pass end=true with the last input, so that an image cut short raises Truncated instead of ending quietly.

    ///|
    /// The rows of a PNG image given in `chunks`, one after another.
    fn rows_of(chunks : Array[Bytes]) -> Bytes raise {
    let decoder = @png.Decoder()
    let rows = Buffer()
    let mut out = FixedArray::make(0, b'\x00')
    for i, chunk in chunks {
    let mut input = chunk[:]
    for ;; {
    // A slice must hold at least one row: `room_needed` says how long.
    if out.length() < decoder.room_needed() {
    out = FixedArray::make(decoder.room_needed() * 16, b'\x00')
    }
    let status = decoder.step_into(input, out, end=i == chunks.length() - 1)
    input = input[decoder.last_consumed():]
    match status {
    Rows =>
    rows.write_bytes(Bytes::from_array(out[:decoder.last_produced()]))
    Done => return rows.to_bytes()
    NeedMoreInput => break
    _ => ()
    }
    }
    }
    rows.to_bytes()
    }

    ///|
    test "decode a 2 x 2 grayscale image in two pieces" {
    let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x02\x00\x00\x00\x02\x08\x00\x00\x00\x00\x57\xdd\x52\xf8\x00\x00\x00\x0e\x49\x44\x41\x54\x78\xda\x63\x60\xf8\xcf\xd0\xe0\x00\x00\x05\x42\x01\xc0\x6d\x5e\x78\xa0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
    let rows = rows_of([png[:30].to_owned(), png[30:].to_owned()])
    assert_eq(rows, b"\x00\xff\x80\x40")
    }

    step_into writes into a whole FixedArray with bulk copies of the rows; step, the contract's method, writes into any MutArrayView.

    After Header, width(), height(), bit_depth(), color_type() and interlaced() describe the image; from Frame, palette() holds an indexed-color image's palette.

    #Rows

    Rows come out in PNG's own layout, without the filter type byte: samples packed when they have fewer than 8 bits, palette indices as they are, 16-bit samples big-endian. After Rows, rows_count() rows of row_bytes() bytes each were written, starting at row rows_first() of pass pass().

    An interlaced image comes out in Adam7's seven passes, each a lattice of the image: row r of the pass is row pass_y() + r * pass_dy() of the image, and its pixel i is column pass_x() + i * pass_dx(). A pass with no pixels is skipped. An image that is not interlaced is the one pass 0, with a lattice of 0, 0, 1, 1.

    An index past the end of the palette is left in the row as it is; the specification shows such a pixel as opaque black, and so does Converter.

    To put a pass's rows in your image, scatter(row, pixels, bits, pass_x, pass_dx, image_row) places a row of a pass into a row of the image, in the same layout, bit by bit below 8 bits; adam7_x, adam7_y, adam7_dx, adam7_dy, adam7_columns and adam7_rows give the passes' lattices for planning ahead.

    #Whole images and RGBA

    decode reads a whole image held in memory and returns it deinterlaced, in PNG's own layout, once every check has passed; max_output bounds its bytes before anything is allocated for them, 512 MiB by default. An APNG gives its default image. is_signature tells a PNG from its first eight bytes.

    A Converter turns rows of PNG's layout into other layouts, the palette and tRNS applied: rgba8 (samples below 8 bits widened by repeating their bits, 16-bit ones rounded to 8), rgba16 (big-endian), and expanded, the shape of the png crate's and libpng's EXPAND (palette to RGB or RGBA, gray widened to 8 bits, tRNS as an alpha channel, 16 bits kept). Decoder::converter() makes one for a streaming decoder, and decode's image carries one.

    ///|
    test "decode a whole image and read it as RGBA" {
    // 2 x 1, indexed-color, 2 bits a pixel: red at half alpha, then blue.
    let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x02\x00\x00\x00\x01\x02\x03\x00\x00\x00\x89\x4c\x97\x19\x00\x00\x00\x06\x50\x4c\x54\x45\xff\x00\x00\x00\x00\xff\x6c\xa1\xfd\x8e\x00\x00\x00\x01\x74\x52\x4e\x53\x80\xad\x5e\x5b\x46\x00\x00\x00\x0a\x49\x44\x41\x54\x78\xda\x63\x10\x00\x00\x00\x12\x00\x11\x08\xde\xbd\xc3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
    assert_true(@png.is_signature(png))
    let image = @png.decode(png, max_output=1 << 20)
    let rgba = FixedArray::make(image.width * 4, b'\x00')
    image.converter.rgba8(image.row(0), image.width, rgba.mut_view())
    assert_eq(Bytes::from_array(rgba[:]), b"\xff\x00\x00\x80\x00\x00\xff\xff")
    }

    #Ancillary chunks

    The fixed-length chunks are read as they come, and their values returned from Frame on: transparency() (tRNS), significant_bits() (sBIT), gamma() (gAMA), chromaticities() (cHRM), srgb_intent() (sRGB), coding_points() (cICP), mastering_display() (mDCV), light_level() (cLLI), physical_size() (pHYs) and background() (bKGD). The decoder does not apply them: gamma, color spaces and which of cICP, iCCP, sRGB and cHRM wins are the caller's.

    The others are reported only when asked for: Decoder(report=["iCCP", "eXIf", "tEXt", "zTXt", "iTXt"]) hands out each chunk of those types as one block of Metadata, once its CRC has passed, with metadata_type() naming it. iCCP's profile, zTXt's text and iTXt's compressed text come out inflated: iCCP's block is the profile's name, a NUL and the profile; zTXt's is the keyword, a NUL and the text, as tEXt's is; iTXt's has its compression flag and method set to 0. Any ancillary type can be asked for, unknown ones too; a fixed-length chunk asked for is both read and reported.

    ///|
    test "report the text chunks" {
    let png = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55\x00\x00\x00\x0e\x74\x45\x58\x74\x41\x75\x74\x68\x6f\x72\x00\x73\x6f\x6d\x65\x6f\x6e\x65\xbf\x83\xd1\xc0\x00\x00\x00\x0a\x49\x44\x41\x54\x78\xda\x63\x60\x00\x00\x00\x02\x00\x01\xe5\x27\xde\xfc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"
    let decoder = @png.Decoder(report=["tEXt"])
    let out = FixedArray::make(256, b'\x00')
    let mut rest = png[:]
    let texts = []
    for ;; {
    match decoder.step_into(rest, out, end=true) {
    Metadata =>
    texts.push(
    "\{decoder.metadata_type()}: \{Bytes::from_array(out[:decoder.last_produced()])}",
    )
    Done => break
    _ => ()
    }
    rest = rest[decoder.last_consumed():]
    }
    inspect(
    texts.join("\n"),
    content=(
    #|tEXt: b"Author\x00someone"
    ),
    )
    }

    #APNG

    An animated image's frames come one after another, each a Frame, its rows, and FrameEnd. frame_x(), frame_y(), frame_width() and frame_height() place the frame on the canvas, and its rows are as wide as the frame; delay_numerator() and delay_denominator() say how long it is shown, dispose() what to do with its rectangle afterwards, blend() how its pixels go on the canvas. These are the values to apply: a delay's denominator of 0 comes as 100, and the first frame's dispose op of PREVIOUS as Background, as the specification says. Putting frames together is the caller's: the decoder holds no canvas.

    The default image (IDAT) is the first Frame; in_animation() says whether it is part of the animation (an fcTL comes before it) or only what a reader without APNG shows. animated(), frame_count() and plays() give what acTL says. With Decoder(animation=false) the decoder reads APNG's chunks as unknown ones and gives the default image only.

    #Limits

    Decoder(max_row_bytes=...) bounds a row's bytes: 8,000,000 by default (a million pixels of 16-bit RGBA), at most 1,000,000,000. An image with longer rows raises LimitExceeded with resource: Memory and the row's bytes, at the step after Header, before anything is allocated for it, so you can look at the size first. The height is not bounded: rows are handed out as they come.

    memory_usage() is 47,360 bytes (the zlib decoder and the palette) until the step after Header, then 47,360 + 2 × (1 + row bytes).

    decode holds the whole image, so max_output bounds its bytes: 536,870,912 (512 MiB) by default, as the image crate's Limits::max_alloc is, since a header of a few dozen bytes can claim gigabytes. An image of more raises LimitExceeded with resource: Output from IHDR, before anything is allocated. Raise it for larger images you trust, up to 2^31 − 1 bytes, what an array holds, or read them with Decoder.

    A reported chunk is held until its CRC has passed, so Decoder(max_chunk_bytes=...) bounds it: 1 MiB by default, at most 100,000,000. A reported chunk longer than that raises LimitExceeded with resource: Memory and the chunk's length, and one that inflates to more raises it with no length. Asking for iCCP, zTXt or iTXt adds a second zlib decoder, 46,592 bytes, which the decoder holds only then: APNG allows text between the image data chunks of one frame, in the middle of their zlib stream. Chunks that are not asked for are only read through their CRC, whatever their size.

    #Errors

    A step raises gaato/codec's CodecError(kind, message). The decoder follows libpng's defaults:

    • Truncated: the input ends inside the image.
    • Corrupt: not a PNG signature, an IHDR that describes no image, chunks out of order (IHDR not first, IDAT chunks not consecutive or missing, an indexed-color image without PLTE before IDAT), a malformed chunk, a filter type past 4, too little image data, or a zlib stream that does not end in the IDAT chunks.
    • ChecksumMismatch: the CRC of IHDR, PLTE (in an indexed-color image), IDAT or IEND, or the zlib stream's Adler-32.
    • Unsupported: a critical chunk the decoder does not know.
    • TrailingData: bytes after IEND. With until=EndOfMember the decoder stops right after IEND instead, and what follows is input[last_consumed():].
    • An error inside the zlib stream keeps its kind, with IDAT in the message: Corrupt for malformed data, and DictionaryRequired for a stream that names a preset dictionary.
    • In an APNG, Corrupt too for a sequence number out of order, an fdAT without its fcTL, a frame without data, an fcTL outside the canvas or with an operation the specification does not define, a second acTL, and a count of frames other than acTL's; and ChecksumMismatch for the CRC of fcTL and fdAT, which an APNG needs as it needs IDAT. An acTL after IDAT or of no frames leaves the image still, as one without acTL is.

    An ancillary chunk out of the place the specification gives it, a second copy of one that may come once, one whose CRC does not match, or one whose values the specification does not allow (a gAMA of 0, a cICP with matrix coefficients, a tRNS longer than the palette, a zTXt whose keyword is empty or whose zlib stream is broken, and so on, as libpng judges them) is left out, and ignored_chunks() counts it; a later valid copy is still read. The bits of tRNS's samples above the bit depth are dropped, as libpng drops them when it expands rows and as WPT expects of browsers. So is a PLTE that a grayscale image cannot use, a PLTE out of place in a truecolor image, and the data of an IEND that has some. Entries of a palette beyond what the bit depth can index are dropped. Inflated data past the last row, and bytes after the zlib stream, are dropped.

    Broken input shows what these rules make of corpora of broken files, and where they part from the png crate's.

    Rows come out as soon as they are decoded, before the CRCs and the Adler-32 that cover them, so ChecksumMismatch can follow rows you already have; FrameEnd comes once they have passed. A step that raises writes nothing: the rows before the error came out in the steps before it. Errors stick until reset.

    The Paeth predictor, Adam7's pass sizes, a row's bytes, an fcTL's rectangle and the sample arithmetic of the conversions are proved against the specification, and the reversal of the filters to read and write only inside its rows; PROOFS.md lists what is proved and what is trusted.

    #When to use it

    gaato/png fits input that arrives in pieces or comes from anyone. Decoder holds two rows and a zlib decoder whatever the image's size (6.5 MiB at the peak for an 8192 × 8192 RGBA image), decode refuses more than 512 MiB of rows by default before allocating them, and neither crashes nor hangs on the 30,590 broken files of Broken input. It decodes all of PngSuite as pypng does, checks every CRC and the Adler-32, and gives APNG's frames and the ancillary chunks.

    When the whole file is in memory already, a decoder that takes it at once can be faster on Native and Wasm: moonbitlang/typst's decode_png takes about 0.7 (Native) and 0.8 (Wasm) times as long as gaato/png on the benchmarks' image, the same on Wasm GC, and 6 times as long on JS.

    #License

    Apache-2.0.

    Status

    using @gaato/codec_image { type Status }

    The answers of Decoder::step and where a decoder stops, as every gaato/codec_image decoder answers and stops.

    Until

    using @gaato/codec_image { type Until }

    The answers of Decoder::step and where a decoder stops, as every gaato/codec_image decoder answers and stops.

    Background

    pub(all) enum Background {
    Gray(Int)
    Rgb(Int, Int, Int)
    Index(Int)
    } derive(Eq,
    Debug
    )

    bKGD (11.3.4.2): a background color to show the image against.

    Background::equal

    fn Background::equal(Background, Background) -> Bool

    Background::equal and Background::not_equal, the derived comparison.

    Background::not_equal

    fn Background::not_equal(x : Background, y : Background) -> Bool

    Background::equal and Background::not_equal, the derived comparison.

    Background::to_repr

    Background::to_repr, the derived debug representation.

    Blend

    pub(all) enum Blend {
    Source
    Over
    } derive(Eq,
    Debug
    )

    How a frame's pixels are put on the canvas (blend_op).

    Blend::equal

    fn Blend::equal(Blend, Blend) -> Bool

    Blend::equal and Blend::not_equal, the derived comparison.

    Blend::not_equal

    fn Blend::not_equal(x : Blend, y : Blend) -> Bool

    Blend::equal and Blend::not_equal, the derived comparison.

    Blend::to_repr

    Blend::to_repr, the derived debug representation.

    Chromaticities

    pub struct Chromaticities {
    white_x : UInt
    white_y : UInt
    red_x : UInt
    red_y : UInt
    green_x : UInt
    green_y : UInt
    blue_x : UInt
    blue_y : UInt
    } derive(Eq,
    Debug
    )

    cHRM (11.3.2.1): the chromaticities of the white point and the primaries, CIE 1931 x and y, each times 100,000.

    Chromaticities::equal

    Chromaticities::equal and Chromaticities::not_equal, the derived comparison.

    Chromaticities::not_equal

    fn Chromaticities::not_equal(x : Chromaticities, y : Chromaticities) -> Bool

    Chromaticities::equal and Chromaticities::not_equal, the derived comparison.

    Chromaticities::to_repr

    Chromaticities::to_repr, the derived debug representation.

    CodingPoints

    pub struct CodingPoints {
    color_primaries : Int
    transfer_function : Int
    matrix_coefficients : Int
    full_range : Bool
    } derive(Eq,
    Debug
    )

    cICP (11.3.2.6): the image's color space as code points of ITU-T H.273.

    CodingPoints::equal

    CodingPoints::equal and CodingPoints::not_equal, the derived comparison.

    CodingPoints::not_equal

    fn CodingPoints::not_equal(x : CodingPoints, y : CodingPoints) -> Bool

    CodingPoints::equal and CodingPoints::not_equal, the derived comparison.

    CodingPoints::to_repr

    CodingPoints::to_repr, the derived debug representation.

    ColorType

    pub(all) enum ColorType {
    Grayscale
    Truecolor
    IndexedColor
    GrayscaleAlpha
    TruecolorAlpha
    } derive(Eq,
    Debug
    )

    The color type of an image (table 11): how its pixels' samples are laid out in a row.

    ColorType::equal

    fn ColorType::equal(ColorType, ColorType) -> Bool

    ColorType::equal and ColorType::not_equal, the derived comparison.

    ColorType::not_equal

    fn ColorType::not_equal(x : ColorType, y : ColorType) -> Bool

    ColorType::equal and ColorType::not_equal, the derived comparison.

    ColorType::to_repr

    ColorType::to_repr, the derived debug representation.

    Converter

    pub struct Converter {
    // private fields
    }

    Converts rows of one image (or frame: its rows have the image's layout) to other layouts. Made from what the image says: its color type, bit depth, palette and tRNS (Decoder::converter, or Converter::Converter).

    Converter::Converter

    fn Converter::Converter(color_type : ColorType, bit_depth : Int, palette? : ArrayView[Byte], transparency? : Transparency) -> Converter

    A converter for rows of color_type and bit_depth. palette is PLTE's red, green and blue bytes (for indexed-color); transparency is tRNS, as Decoder::transparency gives it. An index past the palette converts to opaque black (13.1); every other value converts as it is.

    Converter::expanded

    fn Converter::expanded(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unit

    Writes the first pixels pixels of row into into as the png crate's EXPAND does, which libpng's png_set_expand matches: the palette applied, to RGB, or RGBA with tRNS; gray of fewer than 8 bits widened to 8; tRNS of grayscale and truecolor images turned into an alpha channel; 16-bit samples kept, big-endian. expanded_channels and expanded_depth say the layout; into holds at least their product, in bytes, times pixels.

    Converter::expanded_channels

    fn Converter::expanded_channels(self : Converter) -> Int

    The channels of a pixel expanded writes: 1 (gray), 2 (gray and alpha), 3 (RGB) or 4 (RGBA).

    Converter::expanded_depth

    fn Converter::expanded_depth(self : Converter) -> Int

    The bits of a sample expanded writes: 16 for a 16-bit image, else 8.

    Converter::rgba16

    fn Converter::rgba16(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unit

    Writes the first pixels pixels of row into into as 16-bit RGBA, eight bytes a pixel, each sample big-endian: as rgba8, but samples of fewer than 16 bits widened to 16 by repeating their bits, and 16-bit ones as they are. into holds at least 8 × pixels bytes.

    Converter::rgba8

    fn Converter::rgba8(self : Converter, row : ArrayView[Byte], pixels : Int, into : MutArrayView[Byte]) -> Unit

    Writes the first pixels pixels of row into into as 8-bit RGBA, four bytes a pixel: the palette applied, gray repeated in red, green and blue, samples of fewer than 8 bits widened by repeating their bits, 16-bit ones rounded to 8, and alpha from the image's alpha channel, from tRNS, or 255. into holds at least 4 × pixels bytes.

    Decoder

    pub struct Decoder {
    // private fields
    }

    A streaming PNG decoder. Feed it input in chunks of any size with step and collect whole rows in slices of at least room_needed bytes; it holds two rows and a zlib decoder (a 32 KiB window and its tables), never the whole image.
    impl Decode for Decoder

    Decoder::Decoder

    fn Decoder::Decoder(max_row_bytes? : Int, report? : Array[String], max_chunk_bytes? : Int, animation? : Bool, until? :
    Until
    ) -> Decoder

    A decoder at the start of an image.

    max_row_bytes bounds the bytes of a row (ceil(width × bits a pixel / 8)): 8,000,000 by default, a million pixels of 16-bit RGBA, and at most 1,000,000,000. The decoder holds two rows; an image whose rows are longer raises LimitExceeded(Memory) with a row's bytes, at the step after Header, before anything is allocated for it. Its height is not bounded, since rows are handed out as they come.

    report names the ancillary chunk types to hand out, each as one block of Metadata once its CRC has passed (see metadata_type); by default none. A name that is not an ancillary chunk type matches nothing. Their data is held until then, so a reported chunk of more than max_chunk_bytes bytes (1 MiB by default, at most 100,000,000) raises LimitExceeded(Memory) with its length, and so does one that inflates to more. iCCP's profile, zTXt's text and iTXt's compressed text come out inflated, through a second zlib decoder, which the decoder holds only when one of them is asked for.

    animation says whether to read APNG's chunks (acTL, fcTL, fdAT): each frame of an animation is then a Frame with its rows. With animation=false they are skipped as unknown chunks, and an animated image gives its default image only.

    until says where the image ends. EndOfInput, the default, decodes the image up to the end of the input, and bytes after IEND raise TrailingData. EndOfMember stops right after IEND and consumes nothing past it, so that input[last_consumed():] is what follows.

    Decoder::animated

    fn Decoder::animated(self : Decoder) -> Bool

    Whether the image is an APNG: an acTL with at least one frame came before IDAT, and the decoder reads animations (animation=true). Valid from Frame until reset.

    Decoder::background

    fn Decoder::background(self : Decoder) -> Background?

    bKGD: the background color, or None. Valid from Frame until reset.

    Decoder::bit_depth

    fn Decoder::bit_depth(self : Decoder) -> Int

    Bits a sample, or a palette index: 1, 2, 4, 8 or 16 (table 12). Valid from Header until reset.

    Decoder::blend

    fn Decoder::blend(self : Decoder) -> Blend

    How the current frame's pixels go on the canvas. Valid from Frame until the next Frame or Done.

    Decoder::chromaticities

    fn Decoder::chromaticities(self : Decoder) -> Chromaticities?

    cHRM: the chromaticities of the white point and the primaries, or None. Valid from Frame until reset.

    Decoder::coding_points

    fn Decoder::coding_points(self : Decoder) -> CodingPoints?

    cICP: the color space's code points, or None. Valid from Frame until reset.

    Decoder::color_type

    fn Decoder::color_type(self : Decoder) -> ColorType

    How the samples of a pixel are laid out. Valid from Header until reset.

    Decoder::converter

    fn Decoder::converter(self : Decoder) -> Converter

    A converter for the decoder's image: its color type, bit depth, palette and tRNS. Valid from Frame until reset.

    Decoder::delay_denominator

    fn Decoder::delay_denominator(self : Decoder) -> Int

    The denominator of the current frame's delay, 100 when fcTL says 0. Valid from Frame until the next Frame or Done.

    Decoder::delay_numerator

    fn Decoder::delay_numerator(self : Decoder) -> Int

    The numerator of the current frame's delay, in seconds: how long it is shown. Valid from Frame until the next Frame or Done.

    Decoder::dispose

    fn Decoder::dispose(self : Decoder) -> Dispose

    What to do with the current frame's rectangle after it is shown; PREVIOUS on the first frame comes as Background. Valid from Frame until the next Frame or Done.

    Decoder::frame_count

    fn Decoder::frame_count(self : Decoder) -> UInt

    The frames of the animation acTL announces (the default image among them when it is part of it), and 0 for an image that is not animated. The decoder checks the count at IEND. Valid from Frame until reset.

    Decoder::frame_height

    fn Decoder::frame_height(self : Decoder) -> Int

    The current frame's height. Valid from Frame until the next Frame or Done.

    Decoder::frame_width

    fn Decoder::frame_width(self : Decoder) -> Int

    The current frame's width: the image's for a default image. Valid from Frame until the next Frame or Done.

    Decoder::frame_x

    fn Decoder::frame_x(self : Decoder) -> Int

    The left edge of the current frame: its fcTL's x offset, and 0 for a default image. Valid from Frame until the next Frame or Done.

    Decoder::frame_y

    fn Decoder::frame_y(self : Decoder) -> Int

    The top edge of the current frame. Valid from Frame until the next Frame or Done.

    Decoder::gamma

    fn Decoder::gamma(self : Decoder) -> UInt?

    gAMA: the image's gamma times 100,000 (45,455 for 1/2.2), or None. Valid from Frame until reset.

    Decoder::height

    fn Decoder::height(self : Decoder) -> Int

    The image's height, in pixels. Valid from Header until reset.

    Decoder::ignored_chunks

    fn Decoder::ignored_chunks(self : Decoder) -> Int

    Ancillary chunks left out so far: those out of the place table 7 gives them, second copies of chunks that may come once, those whose CRC does not match, and a PLTE that a grayscale image does not use or that is out of place in a truecolor one. Unknown ancillary chunks are skipped and not counted.

    Decoder::in_animation

    fn Decoder::in_animation(self : Decoder) -> Bool

    Whether the current frame is one of the animation's: every frame of fdAT chunks, and the default image when an fcTL comes before IDAT. A default image that is not part of the animation, and the image of a PNG that is not animated, is not. Valid from Frame until the next Frame or Done.

    Decoder::interlaced

    fn Decoder::interlaced(self : Decoder) -> Bool

    Whether the image is interlaced with Adam7: its rows then come in seven passes, each a lattice of the image (pass_x, pass_dx, ...), and a pass with no pixels is skipped. Valid from Header until reset.

    Decoder::last_consumed

    fn Decoder::last_consumed(self : Decoder) -> Int

    Input bytes the latest step consumed: all of them on NeedMoreInput. A step that raised reports the bytes up to the error, and zero when an earlier step found it; for TrailingData, exactly up to the end of the image.

    Decoder::last_produced

    fn Decoder::last_produced(self : Decoder) -> Int

    Output bytes the latest step wrote: those of its rows after Rows, and zero after any other answer, an error included.

    Decoder::light_level

    fn Decoder::light_level(self : Decoder) -> LightLevel?

    cLLI: the content's light levels, or None. Valid from Frame until reset.

    Decoder::mastering_display

    fn Decoder::mastering_display(self : Decoder) -> MasteringDisplay?

    mDCV: the display the content was mastered on, or None. Valid from Frame until reset.

    Decoder::memory_usage

    fn Decoder::memory_usage(self : Decoder) -> Int

    Bytes held for the image: the zlib decoder's 46,592 (see @zlib.Decoder::memory_usage), the 768 of the palette, and from the step after Header two rows of 1 + row_bytes bytes each, for the image's full width. With chunks to report, the block of the one being read, and the second zlib decoder's 46,592 when iCCP, zTXt or iTXt is reported. The signature, chunk heads and CRCs (8 bytes at a time), IHDR's 13 bytes and the 256 into which a fixed-length ancillary chunk is read are not counted.

    Decoder::metadata_type

    fn Decoder::metadata_type(self : Decoder) -> String

    The type of the chunk whose block the last Metadata wrote, as its four letters. The block is the chunk's data, but for the inflated ones: iCCP's is the profile's name, a NUL and the profile; zTXt's is the keyword, a NUL and the text, as tEXt's is; iTXt's is the chunk's data with the text inflated and the compression flag and method set to 0. Valid after Metadata, until the next step.

    Decoder::palette

    fn Decoder::palette(self : Decoder) -> ArrayView[Byte]

    The palette: red, green and blue, one byte each, for every entry the image can use. An indexed-color image has one (PLTE is required); entries beyond what its bit depth can index are dropped, as libpng drops them. A truecolor image may have one as a suggestion; other images have none. Valid from Frame until reset. Rows hold indices as they are, so an index past the palette is the caller's to handle (the specification shows it as opaque black).

    Decoder::pass

    fn Decoder::pass(self : Decoder) -> Int

    The pass the last Rows belong to: 0 to 6 for Adam7, 0 for an image that is not interlaced. Valid after Rows, until the next step.

    Decoder::pass_dx

    fn Decoder::pass_dx(self : Decoder) -> Int

    The distance between the columns of the last Rows' pass. Valid after Rows, until the next step.

    Decoder::pass_dy

    fn Decoder::pass_dy(self : Decoder) -> Int

    The distance between the rows of the last Rows' pass. Valid after Rows, until the next step.

    Decoder::pass_x

    fn Decoder::pass_x(self : Decoder) -> Int

    The column of the image where the last Rows' pass begins. Valid after Rows, until the next step.

    Decoder::pass_y

    fn Decoder::pass_y(self : Decoder) -> Int

    The row of the image where the last Rows' pass begins. Valid after Rows, until the next step.

    Decoder::physical_size

    fn Decoder::physical_size(self : Decoder) -> PhysicalSize?

    pHYs: the pixels' size or aspect ratio, or None. Valid from Frame until reset.

    Decoder::plays

    fn Decoder::plays(self : Decoder) -> UInt

    How many times acTL says to play the animation, 0 for without end. Valid from Frame until reset.

    Decoder::reset

    fn Decoder::reset(self : Decoder) -> Unit

    Starts a new image with the same options. The rows' buffers are dropped.

    Decoder::room_needed

    fn Decoder::room_needed(self : Decoder) -> Int

    The smallest output slice the next step can write into: a reported chunk's block when one waits, the bytes of the current pass's rows while the frame has rows to come, else zero.

    Decoder::row_bytes

    fn Decoder::row_bytes(self : Decoder) -> Int

    The bytes of each row the last Rows wrote: the pass's pixels, packed as the image's color type and bit depth say, without the filter type byte. Valid after Rows, until the next step.

    Decoder::rows_count

    fn Decoder::rows_count(self : Decoder) -> Int

    How many rows the last Rows wrote. Valid after Rows, until the next step.

    Decoder::rows_first

    fn Decoder::rows_first(self : Decoder) -> Int

    The row of its pass the last Rows begin at. Valid after Rows, until the next step.

    Decoder::significant_bits

    fn Decoder::significant_bits(self : Decoder) -> ArrayView[Byte]

    sBIT: the significant bits of each channel's samples, in the order of the channels (red, green and blue for indexed-color), or empty without a valid sBIT. Valid from Frame until reset.

    Decoder::srgb_intent

    fn Decoder::srgb_intent(self : Decoder) -> Int?

    sRGB: the rendering intent, from 0 (perceptual) to 3 (absolute colorimetric), or None without a valid sRGB. Valid from Frame until reset.

    Decoder::step

    fn Decoder::step(self : Decoder, input : BytesView, output : MutArrayView[Byte], end~ : Bool) ->
    Status
    raise
    CodecError

    Decodes from input into output, and says why it stopped; see last_consumed and last_produced for how much of each it used.

    Pass end=true once input holds the last bytes and end=false before: an image cut short then raises Truncated. A step that writes, writes whole rows of one pass and answers Rows; it stops at Header, Frame and FrameEnd without writing. NeedMoreOutput means output is shorter than the next row, room_needed bytes. Rows come out before the CRCs and the Adler-32 that cover them are checked, so ChecksumMismatch can follow rows already handed out; FrameEnd comes once they have passed. A step that raises writes nothing: the rows before the error came out in the steps before it. After an error the decoder raises it again, consuming and writing nothing, until reset.

    Decoder::step_into

    fn Decoder::step_into(self : Decoder, input : BytesView, output : FixedArray[Byte], end~ : Bool) ->
    Status
    raise
    CodecError

    step into a whole FixedArray, with bulk copies of the rows.

    Decoder::transparency

    fn Decoder::transparency(self : Decoder) -> Transparency?

    tRNS: which pixels are transparent, or None without a valid tRNS. Valid from Frame until reset.

    Decoder::width

    fn Decoder::width(self : Decoder) -> Int

    The image's width, in pixels. Valid from Header until reset.

    Dispose

    pub(all) enum Dispose {
    Keep
    Background
    Previous
    } derive(Eq,
    Debug
    )

    What to do with a frame's rectangle once it has been shown, before the next frame is drawn (dispose_op).

    Dispose::equal

    fn Dispose::equal(Dispose, Dispose) -> Bool

    Dispose::equal and Dispose::not_equal, the derived comparison.

    Dispose::not_equal

    fn Dispose::not_equal(x : Dispose, y : Dispose) -> Bool

    Dispose::equal and Dispose::not_equal, the derived comparison.

    Dispose::to_repr

    Dispose::to_repr, the derived debug representation.

    Image

    pub struct Image {
    width : Int
    height : Int
    color_type : ColorType
    bit_depth : Int
    row_bytes : Int
    pixels : FixedArray[Byte]
    converter : Converter
    }

    A whole image, as decode gives it: its rows one after another, in PNG's own layout (see Converter for others), deinterlaced, with what the image says about them.

    Image::row

    fn Image::row(self : Image, y : Int) -> ArrayView[Byte]

    Row y of the image, from 0 at the top.

    LightLevel

    pub struct LightLevel {
    max_content : UInt
    max_frame_average : UInt
    } derive(Eq,
    Debug
    )

    cLLI (11.3.2.8): the content's light levels, in units of 0.0001 cd/m².

    LightLevel::equal

    fn LightLevel::equal(LightLevel, LightLevel) -> Bool

    LightLevel::equal and LightLevel::not_equal, the derived comparison.

    LightLevel::not_equal

    fn LightLevel::not_equal(x : LightLevel, y : LightLevel) -> Bool

    LightLevel::equal and LightLevel::not_equal, the derived comparison.

    LightLevel::to_repr

    LightLevel::to_repr, the derived debug representation.

    MasteringDisplay

    pub struct MasteringDisplay {
    red_x : Int
    red_y : Int
    green_x : Int
    green_y : Int
    blue_x : Int
    blue_y : Int
    white_x : Int
    white_y : Int
    max_luminance : UInt
    min_luminance : UInt
    } derive(Eq,
    Debug
    )

    mDCV (11.3.2.7): the display the content was mastered on (SMPTE ST 2086). Chromaticities are CIE 1931 x and y in units of 0.00002, luminances in units of 0.0001 cd/m².

    MasteringDisplay::equal

    MasteringDisplay::equal and MasteringDisplay::not_equal, the derived comparison.

    MasteringDisplay::not_equal

    fn MasteringDisplay::not_equal(x : MasteringDisplay, y : MasteringDisplay) -> Bool

    MasteringDisplay::equal and MasteringDisplay::not_equal, the derived comparison.

    MasteringDisplay::to_repr

    MasteringDisplay::to_repr, the derived debug representation.

    PhysicalSize

    pub struct PhysicalSize {
    x : UInt
    y : UInt
    per_meter : Bool
    } derive(Eq,
    Debug
    )

    pHYs (11.3.4.1): the pixels' size, or their aspect ratio.

    PhysicalSize::equal

    PhysicalSize::equal and PhysicalSize::not_equal, the derived comparison.

    PhysicalSize::not_equal

    fn PhysicalSize::not_equal(x : PhysicalSize, y : PhysicalSize) -> Bool

    PhysicalSize::equal and PhysicalSize::not_equal, the derived comparison.

    PhysicalSize::to_repr

    PhysicalSize::to_repr, the derived debug representation.

    Transparency

    pub(all) enum Transparency {
    Gray(Int)
    Rgb(Int, Int, Int)
    Alpha(ArrayView[Byte])
    } derive(Eq,
    Debug
    )

    tRNS (11.3.1.1): which pixels are transparent.

    Transparency::equal

    Transparency::equal and Transparency::not_equal, the derived comparison.

    Transparency::not_equal

    fn Transparency::not_equal(x : Transparency, y : Transparency) -> Bool

    Transparency::equal and Transparency::not_equal, the derived comparison.

    Transparency::to_repr

    Transparency::to_repr, the derived debug representation.

    adam7_columns

    fn adam7_columns(pass : Int, width : Int) -> Int

    The pixels of a row of Adam7's pass pass in an image width pixels wide; 0 when the pass has none, and then no rows either.

    adam7_dx

    fn adam7_dx(pass : Int) -> Int

    The distance between the columns of Adam7's pass pass: 8, 8, 4, 4, 2, 2, 1.

    adam7_dy

    fn adam7_dy(pass : Int) -> Int

    The distance between the rows of Adam7's pass pass: 8, 8, 8, 4, 4, 2, 2.

    adam7_rows

    fn adam7_rows(pass : Int, height : Int) -> Int

    The rows of Adam7's pass pass in an image height pixels high.

    adam7_x

    fn adam7_x(pass : Int) -> Int

    The column of the image where Adam7's pass pass (0 to 6) begins: 0, 4, 0, 2, 0, 1, 0 (8.2).

    adam7_y

    fn adam7_y(pass : Int) -> Int

    The row of the image where Adam7's pass pass begins: 0, 0, 4, 0, 2, 0, 1.

    decode

    fn decode(input : BytesView, max_output? : Int) -> Image raise
    CodecError

    Decodes a whole PNG image held in input, an APNG's default image alone, and returns it once every CRC and the Adler-32 have passed.

    max_output bounds the bytes of the rows, from what IHDR says, before anything is allocated for them: an image of more raises LimitExceeded(Output) with their size. It is 512 MiB (536,870,912 bytes) by default, as the image crate's Limits::max_alloc is, because the header alone can claim gigabytes: a file of a few dozen bytes would otherwise have the whole image allocated before its data is found missing. Pass a larger max_output for larger images you trust, up to 2^31 − 1 bytes, what an array holds; the streaming Decoder hands out rows without holding the image. The decoder's own limits (max_row_bytes) apply either way. Ancillary chunks are read as the streaming decoder reads them, and none is reported.

    is_signature

    fn is_signature(data : BytesView) -> Bool

    Whether data begins with PNG's 8-byte signature (5.2): whether it is a PNG image (or APNG), from its first bytes. Fewer than 8 bytes are not.

    scatter

    fn scatter(row : ArrayView[Byte], pixels : Int, bits : Int, x : Int, dx : Int, into : MutArrayView[Byte]) -> Unit

    Puts the pixels pixels of row, a row of a pass in PNG's layout (bits bits a pixel: the channels times the bit depth), into into, a row of the image in the same layout: pixel i goes to column x + i ×dx. The pass's lattice is what Decoder::pass_x and Decoder::pass_dx report after Rows. Pixels packed below 8 bits are put bit by bit, and the other bits of into are kept.