PNG (Portable Network Graphics) file: Java parsing library

This page hosts a formal specification of PNG (Portable Network Graphics) file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All Java code generated by Kaitai Struct depends on the Kaitai Struct runtime library for Java. You must add this dependency to your project before you can parse or serialize any data.

The Java runtime library is published in the Maven Central Repository. The artifact page provides snippets for various build tools that you can copy into your project.

Code

Parse a local file and get structure in memory:

Png data = Png.fromFile("path/to/local/file.png");

Or parse structure from a byte array:

byte[] someArray = new byte[] { ... };
Png data = new Png(new ByteBufferKaitaiStream(someArray));

After that, one can get various attributes from the structure by invoking getter methods like:

data.magic() // => get magic

Java source code to parse PNG (Portable Network Graphics) file

Png.java

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

import io.kaitai.struct.ByteBufferKaitaiStream;
import io.kaitai.struct.KaitaiStruct;
import io.kaitai.struct.KaitaiStream;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
import java.util.ArrayList;
import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * Test files for APNG can be found at the following locations:
 * 
 *   * <https://philip.html5.org/tests/apng/tests.html>
 *   * <http://littlesvr.ca/apng/>
 */
public class Png extends KaitaiStruct {
    public static Png fromFile(String fileName) throws IOException {
        return new Png(new ByteBufferKaitaiStream(fileName));
    }

    public enum BlendOpValues {
        SOURCE(0),
        OVER(1);

        private final long id;
        BlendOpValues(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, BlendOpValues> byId = new HashMap<Long, BlendOpValues>(2);
        static {
            for (BlendOpValues e : BlendOpValues.values())
                byId.put(e.id(), e);
        }
        public static BlendOpValues byId(long id) { return byId.get(id); }
    }

    public enum ColorType {
        GREYSCALE(0),
        TRUECOLOR(2),
        INDEXED(3),
        GREYSCALE_ALPHA(4),
        TRUECOLOR_ALPHA(6);

        private final long id;
        ColorType(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, ColorType> byId = new HashMap<Long, ColorType>(5);
        static {
            for (ColorType e : ColorType.values())
                byId.put(e.id(), e);
        }
        public static ColorType byId(long id) { return byId.get(id); }
    }

    public enum CompressionMethods {
        ZLIB(0);

        private final long id;
        CompressionMethods(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, CompressionMethods> byId = new HashMap<Long, CompressionMethods>(1);
        static {
            for (CompressionMethods e : CompressionMethods.values())
                byId.put(e.id(), e);
        }
        public static CompressionMethods byId(long id) { return byId.get(id); }
    }

    public enum DisposeOpValues {
        NONE(0),
        BACKGROUND(1),
        PREVIOUS(2);

        private final long id;
        DisposeOpValues(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, DisposeOpValues> byId = new HashMap<Long, DisposeOpValues>(3);
        static {
            for (DisposeOpValues e : DisposeOpValues.values())
                byId.put(e.id(), e);
        }
        public static DisposeOpValues byId(long id) { return byId.get(id); }
    }

    public enum FilterMethod {
        BASE(0);

        private final long id;
        FilterMethod(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, FilterMethod> byId = new HashMap<Long, FilterMethod>(1);
        static {
            for (FilterMethod e : FilterMethod.values())
                byId.put(e.id(), e);
        }
        public static FilterMethod byId(long id) { return byId.get(id); }
    }

    public enum InterlaceMethod {
        NONE(0),
        ADAM7(1);

        private final long id;
        InterlaceMethod(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, InterlaceMethod> byId = new HashMap<Long, InterlaceMethod>(2);
        static {
            for (InterlaceMethod e : InterlaceMethod.values())
                byId.put(e.id(), e);
        }
        public static InterlaceMethod byId(long id) { return byId.get(id); }
    }

    public enum PhysUnit {
        UNKNOWN(0),
        METER(1);

        private final long id;
        PhysUnit(long id) { this.id = id; }
        public long id() { return id; }
        private static final Map<Long, PhysUnit> byId = new HashMap<Long, PhysUnit>(2);
        static {
            for (PhysUnit e : PhysUnit.values())
                byId.put(e.id(), e);
        }
        public static PhysUnit byId(long id) { return byId.get(id); }
    }

    public Png(KaitaiStream _io) {
        this(_io, null, null);
    }

    public Png(KaitaiStream _io, KaitaiStruct _parent) {
        this(_io, _parent, null);
    }

    public Png(KaitaiStream _io, KaitaiStruct _parent, Png _root) {
        super(_io);
        this._parent = _parent;
        this._root = _root == null ? this : _root;
        _read();
    }
    private void _read() {
        this.magic = this._io.readBytes(8);
        if (!(Arrays.equals(this.magic, new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 }))) {
            throw new KaitaiStream.ValidationNotEqualError(new byte[] { -119, 80, 78, 71, 13, 10, 26, 10 }, this.magic, this._io, "/seq/0");
        }
        this.ihdrLen = this._io.readU4be();
        if (!(this.ihdrLen == 13)) {
            throw new KaitaiStream.ValidationNotEqualError(13, this.ihdrLen, this._io, "/seq/1");
        }
        this.ihdrType = this._io.readBytes(4);
        if (!(Arrays.equals(this.ihdrType, new byte[] { 73, 72, 68, 82 }))) {
            throw new KaitaiStream.ValidationNotEqualError(new byte[] { 73, 72, 68, 82 }, this.ihdrType, this._io, "/seq/2");
        }
        this.ihdr = new IhdrChunk(this._io, this, _root);
        this.ihdrCrc = this._io.readU4be();
        this.chunks = new ArrayList<Chunk>();
        {
            Chunk _it;
            int i = 0;
            do {
                _it = new Chunk(this._io, this, _root);
                this.chunks.add(_it);
                i++;
            } while (!( ((_it.type().equals("IEND")) || (_io().isEof())) ));
        }
    }

    public void _fetchInstances() {
        this.ihdr._fetchInstances();
        for (int i = 0; i < this.chunks.size(); i++) {
            this.chunks.get(((Number) (i)).intValue())._fetchInstances();
        }
    }

    /**
     * @see <a href="https://stackoverflow.com/questions/4242402/the-fireworks-png-format-any-insight-any-libs/51683285#51683285">Source</a>
     */
    public static class AdobeFireworksChunk extends KaitaiStruct {
        public static AdobeFireworksChunk fromFile(String fileName) throws IOException {
            return new AdobeFireworksChunk(new ByteBufferKaitaiStream(fileName));
        }

        public AdobeFireworksChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public AdobeFireworksChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public AdobeFireworksChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this._raw_previewData = this._io.readBytesFull();
            this.previewData = KaitaiStream.processZlib(this._raw_previewData);
        }

        public void _fetchInstances() {
        }
        private byte[] previewData;
        private Png _root;
        private Png.Chunk _parent;
        private byte[] _raw_previewData;
        public byte[] previewData() { return previewData; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
        public byte[] _raw_previewData() { return _raw_previewData; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#acTL-chunk">Source</a>
     */
    public static class AnimationControlChunk extends KaitaiStruct {
        public static AnimationControlChunk fromFile(String fileName) throws IOException {
            return new AnimationControlChunk(new ByteBufferKaitaiStream(fileName));
        }

        public AnimationControlChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public AnimationControlChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public AnimationControlChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.numFrames = this._io.readU4be();
            this.numPlays = this._io.readU4be();
        }

        public void _fetchInstances() {
        }
        private long numFrames;
        private long numPlays;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Number of frames, must be equal to the number of `fcTL` chunks (i.e.
         * `frame_control_chunk` objects)
         */
        public long numFrames() { return numFrames; }

        /**
         * Number of times to loop, 0 indicates infinite looping.
         */
        public long numPlays() { return numPlays; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://github.com/skeeto/scratch/tree/58470254f4a95cdf7a53888e405c851c21eb2cae/pngattach">Source</a>
     * @see <a href="https://nullprogram.com/blog/2021/12/31/">A new protocol and tool for PNG file attachments</a>
     */
    public static class AtchChunk extends KaitaiStruct {
        public static AtchChunk fromFile(String fileName) throws IOException {
            return new AtchChunk(new ByteBufferKaitaiStream(fileName));
        }

        public enum CompressionAttachMethods {
            NONE(0),
            ZLIB(1);

            private final long id;
            CompressionAttachMethods(long id) { this.id = id; }
            public long id() { return id; }
            private static final Map<Long, CompressionAttachMethods> byId = new HashMap<Long, CompressionAttachMethods>(2);
            static {
                for (CompressionAttachMethods e : CompressionAttachMethods.values())
                    byId.put(e.id(), e);
            }
            public static CompressionAttachMethods byId(long id) { return byId.get(id); }
        }

        public AtchChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public AtchChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public AtchChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.fileName = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.UTF_8);
            {
                String _it = this.fileName;
                if (!( ((_it.length() != 0) && (!_it.substring(0, 1).equals("."))) )) {
                    throw new KaitaiStream.ValidationExprError(this.fileName, this._io, "/types/atch_chunk/seq/0");
                }
            }
            this.compression = CompressionAttachMethods.byId(this._io.readU1());
            if (this.compression == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.compression, this._io, "/types/atch_chunk/seq/1");
            }
            if (compression() == CompressionAttachMethods.NONE) {
                this.dataPlain = this._io.readBytesFull();
            }
            if (compression() == CompressionAttachMethods.ZLIB) {
                this._raw_dataZlib = this._io.readBytesFull();
                this.dataZlib = KaitaiStream.processZlib(this._raw_dataZlib);
            }
        }

        public void _fetchInstances() {
            if (compression() == CompressionAttachMethods.NONE) {
            }
            if (compression() == CompressionAttachMethods.ZLIB) {
            }
        }
        private byte[] data;
        public byte[] data() {
            if (this.data != null)
                return this.data;
            this.data = (compression() == CompressionAttachMethods.NONE ? dataPlain() : dataZlib());
            return this.data;
        }
        private String fileName;
        private CompressionAttachMethods compression;
        private byte[] dataPlain;
        private byte[] dataZlib;
        private Png _root;
        private Png.Chunk _parent;
        private byte[] _raw_dataZlib;

        /**
         * From the [official
         * specification](https://github.com/skeeto/scratch/tree/58470254f4a95cdf7a53888e405c851c21eb2cae/pngattach#atch-chunk-specification):
         * 
         * > The name can be any length that fits in the chunk, and should be
         * > encoded with UTF-8. It's up to each implementation to determine how
         * > to appropriately interpret the bytestring for the local system.
         * 
         * > The name must be at least one byte long, not counting the null
         * > terminator. It cannot begin with a period (`0x2e`), nor contain
         * > control bytes (anything less than `0x20`), nor slash (`0x2f`), nor
         * > backslash (`0x5c`), i.e. no directory hierarchies.
         * 
         * As of Kaitai Struct 0.11, we cannot easily check whether a string
         * contains certain characters, so we only enforce that the file name is
         * not empty and that it doesn't start with a period.
         */
        public String fileName() { return fileName; }
        public CompressionAttachMethods compression() { return compression; }
        public byte[] dataPlain() { return dataPlain; }
        public byte[] dataZlib() { return dataZlib; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
        public byte[] _raw_dataZlib() { return _raw_dataZlib; }
    }

    /**
     * Background chunk stores default background color to display this
     * image against. Contents depend on `color_type` of the image.
     * @see <a href="https://www.w3.org/TR/png/#11bKGD">Source</a>
     */
    public static class BkgdChunk extends KaitaiStruct {
        public static BkgdChunk fromFile(String fileName) throws IOException {
            return new BkgdChunk(new ByteBufferKaitaiStream(fileName));
        }

        public BkgdChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public BkgdChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public BkgdChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            {
                ColorType on = _root().ihdr().colorType();
                if (on != null) {
                    switch (_root().ihdr().colorType()) {
                    case GREYSCALE: {
                        this.bkgd = new BkgdGreyscale(this._io, this, _root);
                        break;
                    }
                    case GREYSCALE_ALPHA: {
                        this.bkgd = new BkgdGreyscale(this._io, this, _root);
                        break;
                    }
                    case INDEXED: {
                        this.bkgd = new BkgdIndexed(this._io, this, _root);
                        break;
                    }
                    case TRUECOLOR: {
                        this.bkgd = new BkgdTruecolor(this._io, this, _root);
                        break;
                    }
                    case TRUECOLOR_ALPHA: {
                        this.bkgd = new BkgdTruecolor(this._io, this, _root);
                        break;
                    }
                    }
                }
            }
        }

        public void _fetchInstances() {
            {
                ColorType on = _root().ihdr().colorType();
                if (on != null) {
                    switch (_root().ihdr().colorType()) {
                    case GREYSCALE: {
                        ((BkgdGreyscale) (this.bkgd))._fetchInstances();
                        break;
                    }
                    case GREYSCALE_ALPHA: {
                        ((BkgdGreyscale) (this.bkgd))._fetchInstances();
                        break;
                    }
                    case INDEXED: {
                        ((BkgdIndexed) (this.bkgd))._fetchInstances();
                        break;
                    }
                    case TRUECOLOR: {
                        ((BkgdTruecolor) (this.bkgd))._fetchInstances();
                        break;
                    }
                    case TRUECOLOR_ALPHA: {
                        ((BkgdTruecolor) (this.bkgd))._fetchInstances();
                        break;
                    }
                    }
                }
            }
        }
        private KaitaiStruct bkgd;
        private Png _root;
        private Png.Chunk _parent;
        public KaitaiStruct bkgd() { return bkgd; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * Background chunk for greyscale images.
     */
    public static class BkgdGreyscale extends KaitaiStruct {
        public static BkgdGreyscale fromFile(String fileName) throws IOException {
            return new BkgdGreyscale(new ByteBufferKaitaiStream(fileName));
        }

        public BkgdGreyscale(KaitaiStream _io) {
            this(_io, null, null);
        }

        public BkgdGreyscale(KaitaiStream _io, Png.BkgdChunk _parent) {
            this(_io, _parent, null);
        }

        public BkgdGreyscale(KaitaiStream _io, Png.BkgdChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.value = this._io.readU2be();
        }

        public void _fetchInstances() {
        }
        private int value;
        private Png _root;
        private Png.BkgdChunk _parent;
        public int value() { return value; }
        public Png _root() { return _root; }
        public Png.BkgdChunk _parent() { return _parent; }
    }

    /**
     * Background chunk for images with indexed palette.
     */
    public static class BkgdIndexed extends KaitaiStruct {
        public static BkgdIndexed fromFile(String fileName) throws IOException {
            return new BkgdIndexed(new ByteBufferKaitaiStream(fileName));
        }

        public BkgdIndexed(KaitaiStream _io) {
            this(_io, null, null);
        }

        public BkgdIndexed(KaitaiStream _io, Png.BkgdChunk _parent) {
            this(_io, _parent, null);
        }

        public BkgdIndexed(KaitaiStream _io, Png.BkgdChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.paletteIndex = this._io.readU1();
        }

        public void _fetchInstances() {
        }
        private int paletteIndex;
        private Png _root;
        private Png.BkgdChunk _parent;
        public int paletteIndex() { return paletteIndex; }
        public Png _root() { return _root; }
        public Png.BkgdChunk _parent() { return _parent; }
    }

    /**
     * Background chunk for truecolor images.
     */
    public static class BkgdTruecolor extends KaitaiStruct {
        public static BkgdTruecolor fromFile(String fileName) throws IOException {
            return new BkgdTruecolor(new ByteBufferKaitaiStream(fileName));
        }

        public BkgdTruecolor(KaitaiStream _io) {
            this(_io, null, null);
        }

        public BkgdTruecolor(KaitaiStream _io, Png.BkgdChunk _parent) {
            this(_io, _parent, null);
        }

        public BkgdTruecolor(KaitaiStream _io, Png.BkgdChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.red = this._io.readU2be();
            this.green = this._io.readU2be();
            this.blue = this._io.readU2be();
        }

        public void _fetchInstances() {
        }
        private int red;
        private int green;
        private int blue;
        private Png _root;
        private Png.BkgdChunk _parent;
        public int red() { return red; }
        public int green() { return green; }
        public int blue() { return blue; }
        public Png _root() { return _root; }
        public Png.BkgdChunk _parent() { return _parent; }
    }
    public static class ChrmChromaticity extends KaitaiStruct {
        public static ChrmChromaticity fromFile(String fileName) throws IOException {
            return new ChrmChromaticity(new ByteBufferKaitaiStream(fileName));
        }

        public ChrmChromaticity(KaitaiStream _io) {
            this(_io, null, null);
        }

        public ChrmChromaticity(KaitaiStream _io, Png.ChrmChunk _parent) {
            this(_io, _parent, null);
        }

        public ChrmChromaticity(KaitaiStream _io, Png.ChrmChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.xInt = this._io.readU4be();
            this.yInt = this._io.readU4be();
        }

        public void _fetchInstances() {
        }
        private Double x;
        public Double x() {
            if (this.x != null)
                return this.x;
            this.x = ((Number) (xInt() / 100000.0)).doubleValue();
            return this.x;
        }
        private Double y;
        public Double y() {
            if (this.y != null)
                return this.y;
            this.y = ((Number) (yInt() / 100000.0)).doubleValue();
            return this.y;
        }
        private long xInt;
        private long yInt;
        private Png _root;
        private Png.ChrmChunk _parent;
        public long xInt() { return xInt; }
        public long yInt() { return yInt; }
        public Png _root() { return _root; }
        public Png.ChrmChunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#11cHRM">Source</a>
     */
    public static class ChrmChunk extends KaitaiStruct {
        public static ChrmChunk fromFile(String fileName) throws IOException {
            return new ChrmChunk(new ByteBufferKaitaiStream(fileName));
        }

        public ChrmChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public ChrmChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public ChrmChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.whitePoint = new ChrmChromaticity(this._io, this, _root);
            this.red = new ChrmChromaticity(this._io, this, _root);
            this.green = new ChrmChromaticity(this._io, this, _root);
            this.blue = new ChrmChromaticity(this._io, this, _root);
        }

        public void _fetchInstances() {
            this.whitePoint._fetchInstances();
            this.red._fetchInstances();
            this.green._fetchInstances();
            this.blue._fetchInstances();
        }
        private ChrmChromaticity whitePoint;
        private ChrmChromaticity red;
        private ChrmChromaticity green;
        private ChrmChromaticity blue;
        private Png _root;
        private Png.Chunk _parent;
        public ChrmChromaticity whitePoint() { return whitePoint; }
        public ChrmChromaticity red() { return red; }
        public ChrmChromaticity green() { return green; }
        public ChrmChromaticity blue() { return blue; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }
    public static class Chunk extends KaitaiStruct {
        public static Chunk fromFile(String fileName) throws IOException {
            return new Chunk(new ByteBufferKaitaiStream(fileName));
        }

        public Chunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public Chunk(KaitaiStream _io, Png _parent) {
            this(_io, _parent, null);
        }

        public Chunk(KaitaiStream _io, Png _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.len = this._io.readU4be();
            this.typeRaw = this._io.readBytes(4);
            {
                byte[] _it = this.typeRaw;
                if (!( (( (( (((_it[((int) 0)] & 0xff) >= 65) && ((_it[((int) 0)] & 0xff) <= 90)) ) || ( (((_it[((int) 0)] & 0xff) >= 97) && ((_it[((int) 0)] & 0xff) <= 122)) )) ) && ( (( (((_it[((int) 1)] & 0xff) >= 65) && ((_it[((int) 1)] & 0xff) <= 90)) ) || ( (((_it[((int) 1)] & 0xff) >= 97) && ((_it[((int) 1)] & 0xff) <= 122)) )) ) && ( (( (((_it[((int) 2)] & 0xff) >= 65) && ((_it[((int) 2)] & 0xff) <= 90)) ) || ( (((_it[((int) 2)] & 0xff) >= 97) && ((_it[((int) 2)] & 0xff) <= 122)) )) ) && ( (( (((_it[((int) 3)] & 0xff) >= 65) && ((_it[((int) 3)] & 0xff) <= 90)) ) || ( (((_it[((int) 3)] & 0xff) >= 97) && ((_it[((int) 3)] & 0xff) <= 122)) )) )) )) {
                    throw new KaitaiStream.ValidationExprError(this.typeRaw, this._io, "/types/chunk/seq/1");
                }
            }
            switch (type()) {
            case "PLTE": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new PlteChunk(_io_body, this, _root);
                break;
            }
            case "acTL": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new AnimationControlChunk(_io_body, this, _root);
                break;
            }
            case "atCh": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new AtchChunk(_io_body, this, _root);
                break;
            }
            case "bKGD": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new BkgdChunk(_io_body, this, _root);
                break;
            }
            case "cHRM": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new ChrmChunk(_io_body, this, _root);
                break;
            }
            case "cICP": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new CicpChunk(_io_body, this, _root);
                break;
            }
            case "cLLI": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new ClliChunk(_io_body, this, _root);
                break;
            }
            case "fcTL": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new FrameControlChunk(_io_body, this, _root);
                break;
            }
            case "fdAT": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new FrameDataChunk(_io_body, this, _root);
                break;
            }
            case "gAMA": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new GamaChunk(_io_body, this, _root);
                break;
            }
            case "iTXt": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new InternationalTextChunk(_io_body, this, _root);
                break;
            }
            case "mDCV": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new MdcvChunk(_io_body, this, _root);
                break;
            }
            case "mkBS": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new AdobeFireworksChunk(_io_body, this, _root);
                break;
            }
            case "mkTS": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new AdobeFireworksChunk(_io_body, this, _root);
                break;
            }
            case "pHYs": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new PhysChunk(_io_body, this, _root);
                break;
            }
            case "prVW": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new AdobeFireworksChunk(_io_body, this, _root);
                break;
            }
            case "sRGB": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new SrgbChunk(_io_body, this, _root);
                break;
            }
            case "skMf": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new EvernoteSkmfChunk(_io_body, this, _root);
                break;
            }
            case "skRf": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new EvernoteSkrfChunk(_io_body, this, _root);
                break;
            }
            case "tEXt": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new TextChunk(_io_body, this, _root);
                break;
            }
            case "tIME": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new TimeChunk(_io_body, this, _root);
                break;
            }
            case "zTXt": {
                KaitaiStream _io_body = this._io.substream(len());
                this.body = new CompressedTextChunk(_io_body, this, _root);
                break;
            }
            default: {
                this.body = this._io.readBytes(len());
                break;
            }
            }
            this.crc = this._io.readU4be();
        }

        public void _fetchInstances() {
            switch (type()) {
            case "PLTE": {
                ((PlteChunk) (this.body))._fetchInstances();
                break;
            }
            case "acTL": {
                ((AnimationControlChunk) (this.body))._fetchInstances();
                break;
            }
            case "atCh": {
                ((AtchChunk) (this.body))._fetchInstances();
                break;
            }
            case "bKGD": {
                ((BkgdChunk) (this.body))._fetchInstances();
                break;
            }
            case "cHRM": {
                ((ChrmChunk) (this.body))._fetchInstances();
                break;
            }
            case "cICP": {
                ((CicpChunk) (this.body))._fetchInstances();
                break;
            }
            case "cLLI": {
                ((ClliChunk) (this.body))._fetchInstances();
                break;
            }
            case "fcTL": {
                ((FrameControlChunk) (this.body))._fetchInstances();
                break;
            }
            case "fdAT": {
                ((FrameDataChunk) (this.body))._fetchInstances();
                break;
            }
            case "gAMA": {
                ((GamaChunk) (this.body))._fetchInstances();
                break;
            }
            case "iTXt": {
                ((InternationalTextChunk) (this.body))._fetchInstances();
                break;
            }
            case "mDCV": {
                ((MdcvChunk) (this.body))._fetchInstances();
                break;
            }
            case "mkBS": {
                ((AdobeFireworksChunk) (this.body))._fetchInstances();
                break;
            }
            case "mkTS": {
                ((AdobeFireworksChunk) (this.body))._fetchInstances();
                break;
            }
            case "pHYs": {
                ((PhysChunk) (this.body))._fetchInstances();
                break;
            }
            case "prVW": {
                ((AdobeFireworksChunk) (this.body))._fetchInstances();
                break;
            }
            case "sRGB": {
                ((SrgbChunk) (this.body))._fetchInstances();
                break;
            }
            case "skMf": {
                ((EvernoteSkmfChunk) (this.body))._fetchInstances();
                break;
            }
            case "skRf": {
                ((EvernoteSkrfChunk) (this.body))._fetchInstances();
                break;
            }
            case "tEXt": {
                ((TextChunk) (this.body))._fetchInstances();
                break;
            }
            case "tIME": {
                ((TimeChunk) (this.body))._fetchInstances();
                break;
            }
            case "zTXt": {
                ((CompressedTextChunk) (this.body))._fetchInstances();
                break;
            }
            default: {
                break;
            }
            }
        }
        private Boolean isAncillary;

        /**
         * false = critical chunk, true = ancillary chunk
         */
        public Boolean isAncillary() {
            if (this.isAncillary != null)
                return this.isAncillary;
            this.isAncillary = ((typeRaw()[((int) 0)] & 0xff) & 32) != 0;
            return this.isAncillary;
        }
        private Boolean isPrivate;

        /**
         * false = public chunk (defined by the W3C), true = private chunk (can
         * be defined by anyone)
         */
        public Boolean isPrivate() {
            if (this.isPrivate != null)
                return this.isPrivate;
            this.isPrivate = ((typeRaw()[((int) 1)] & 0xff) & 32) != 0;
            return this.isPrivate;
        }
        private Boolean isSafeToCopy;

        /**
         * Defines whether the chunk may be copied if the image data (i.e.
         * pixels) is modified. This tells PNG editors how to handle unknown
         * chunks - see section [14.2 Behavior of PNG
         * editors](https://www.w3.org/TR/2025/REC-png-3-20250624/#14Ordering) in
         * the official specification.
         */
        public Boolean isSafeToCopy() {
            if (this.isSafeToCopy != null)
                return this.isSafeToCopy;
            this.isSafeToCopy = ((typeRaw()[((int) 3)] & 0xff) & 32) != 0;
            return this.isSafeToCopy;
        }
        private Boolean reservedBit;

        /**
         * Should be `false`, i.e. all chunk types should have uppercase third
         * letters (the lowercase third letter is reserved for possible future
         * extensions to the PNG standard)
         */
        public Boolean reservedBit() {
            if (this.reservedBit != null)
                return this.reservedBit;
            this.reservedBit = ((typeRaw()[((int) 2)] & 0xff) & 32) != 0;
            return this.reservedBit;
        }
        private String type;
        public String type() {
            if (this.type != null)
                return this.type;
            this.type = new String(typeRaw(), StandardCharsets.US_ASCII);
            return this.type;
        }
        private long len;
        private byte[] typeRaw;
        private Object body;
        private long crc;
        private Png _root;
        private Png _parent;
        public long len() { return len; }

        /**
         * Each byte of a chunk type is restricted to the hexadecimal values
         * 0x41..0x5a and 0x61..0x7a, i.e. uppercase and lowercase ASCII letters
         * (`A-Z` and `a-z`).
         * @see <a href="https://www.w3.org/TR/2025/REC-png-3-20250624/#table51">Source</a>
         */
        public byte[] typeRaw() { return typeRaw; }
        public Object body() { return body; }
        public long crc() { return crc; }
        public Png _root() { return _root; }
        public Png _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#cICP-chunk">Source</a>
     * @see <a href="https://w3c.github.io/png/Implementation_Report_3e/#cicp">Source</a>
     */
    public static class CicpChunk extends KaitaiStruct {
        public static CicpChunk fromFile(String fileName) throws IOException {
            return new CicpChunk(new ByteBufferKaitaiStream(fileName));
        }

        public CicpChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public CicpChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public CicpChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.colorPrimaries = this._io.readU1();
            this.transferFunction = this._io.readU1();
            this.matrixCoefficients = this._io.readU1();
            if (!(this.matrixCoefficients == 0)) {
                throw new KaitaiStream.ValidationNotEqualError(0, this.matrixCoefficients, this._io, "/types/cicp_chunk/seq/2");
            }
            this.videoFullRangeFlag = this._io.readU1();
            if (!( ((this.videoFullRangeFlag == 0) || (this.videoFullRangeFlag == 1)) )) {
                throw new KaitaiStream.ValidationNotAnyOfError(this.videoFullRangeFlag, this._io, "/types/cicp_chunk/seq/3");
            }
        }

        public void _fetchInstances() {
        }
        private int colorPrimaries;
        private int transferFunction;
        private int matrixCoefficients;
        private int videoFullRangeFlag;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * values above 22 are reserved, see
         * <https://github.com/pnggroup/pngcheck/blob/bd33ad6490269df07cac81e5305f4ebf56c2b637/pngcheck.c#L3322-L3325>
         */
        public int colorPrimaries() { return colorPrimaries; }

        /**
         * values above 18 are reserved, see
         * <https://github.com/pnggroup/pngcheck/blob/bd33ad6490269df07cac81e5305f4ebf56c2b637/pngcheck.c#L3326-L3329>
         */
        public int transferFunction() { return transferFunction; }

        /**
         * From the [official
         * specification](https://www.w3.org/TR/2025/REC-png-3-20250624/#cICP-chunk):
         * 
         * > RGB is currently the only supported color model in PNG, and as such
         * > `Matrix Coefficients` shall be set to `0`.
         */
        public int matrixCoefficients() { return matrixCoefficients; }

        /**
         * From the [official
         * specification](https://www.w3.org/TR/2025/REC-png-3-20250624/#cICP-chunk):
         * 
         * > If `Video Full Range Flag` value is `1`, then the image is a
         * > full-range image. Typically, images in the RGB color representation
         * > are stored in the full-range signal quantization, therefore the vast
         * > majority of computer graphics and web images, including those used
         * > in traditional PNG workflows, are full-range images.
         * 
         * > If `Video Full Range Flag` value is `0`, then the image is a
         * > narrow-range image.
         */
        public int videoFullRangeFlag() { return videoFullRangeFlag; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#cLLI-chunk">Source</a>
     * @see <a href="https://w3c.github.io/png/Implementation_Report_3e/#light">Source</a>
     */
    public static class ClliChunk extends KaitaiStruct {
        public static ClliChunk fromFile(String fileName) throws IOException {
            return new ClliChunk(new ByteBufferKaitaiStream(fileName));
        }

        public ClliChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public ClliChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public ClliChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.maxContentLightLevelInt = this._io.readU4be();
            this.maxFrameAverageLightLevelInt = this._io.readU4be();
        }

        public void _fetchInstances() {
        }
        private Double maxContentLightLevel;

        /**
         * Maximum Content Light Level (MaxCLL), in cd/m^2
         */
        public Double maxContentLightLevel() {
            if (this.maxContentLightLevel != null)
                return this.maxContentLightLevel;
            this.maxContentLightLevel = ((Number) (maxContentLightLevelInt() * 0.0001)).doubleValue();
            return this.maxContentLightLevel;
        }
        private Double maxFrameAverageLightLevel;

        /**
         * Maximum Frame Average Light Level (MaxFALL), in cd/m^2
         */
        public Double maxFrameAverageLightLevel() {
            if (this.maxFrameAverageLightLevel != null)
                return this.maxFrameAverageLightLevel;
            this.maxFrameAverageLightLevel = ((Number) (maxFrameAverageLightLevelInt() * 0.0001)).doubleValue();
            return this.maxFrameAverageLightLevel;
        }
        private long maxContentLightLevelInt;
        private long maxFrameAverageLightLevelInt;
        private Png _root;
        private Png.Chunk _parent;
        public long maxContentLightLevelInt() { return maxContentLightLevelInt; }
        public long maxFrameAverageLightLevelInt() { return maxFrameAverageLightLevelInt; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }
    public static class CompressedText extends KaitaiStruct {
        public static CompressedText fromFile(String fileName) throws IOException {
            return new CompressedText(new ByteBufferKaitaiStream(fileName));
        }

        public CompressedText(KaitaiStream _io) {
            this(_io, null, null);
        }

        public CompressedText(KaitaiStream _io, Png.CompressedTextChunk _parent) {
            this(_io, _parent, null);
        }

        public CompressedText(KaitaiStream _io, Png.CompressedTextChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.value = new String(this._io.readBytesFull(), StandardCharsets.ISO_8859_1);
        }

        public void _fetchInstances() {
        }
        private String value;
        private Png _root;
        private Png.CompressedTextChunk _parent;

        /**
         * Text string (the "value" of this key-value pair).
         * 
         * Although it is not null-terminated (unlike the keyword), it must not
         * contain a zero byte (U+0000 NULL character). A newline should be
         * represented by a single U+000A LINE FEED (LF) character (aka `\n`).
         * The remaining control characters (U+0001..U+0009, U+000B..0+001F,
         * U+007F..U+009F) are discouraged.
         */
        public String value() { return value; }
        public Png _root() { return _root; }
        public Png.CompressedTextChunk _parent() { return _parent; }
    }

    /**
     * Compressed textual data (`zTXt`) chunk effectively allows you to store
     * key-value string pairs in the PNG container, compressing the "value" part
     * (which can be quite lengthy) with zlib compression.
     * 
     * The `zTXt` and `tEXt` chunks are semantically equivalent, but the `zTXt`
     * chunk is recommended for storing large blocks of text.
     * @see <a href="https://www.w3.org/TR/png/#11zTXt">Source</a>
     */
    public static class CompressedTextChunk extends KaitaiStruct {
        public static CompressedTextChunk fromFile(String fileName) throws IOException {
            return new CompressedTextChunk(new ByteBufferKaitaiStream(fileName));
        }

        public CompressedTextChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public CompressedTextChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public CompressedTextChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.keyword = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.ISO_8859_1);
            this.compressionMethod = Png.CompressionMethods.byId(this._io.readU1());
            if (!(this.compressionMethod == Png.CompressionMethods.ZLIB)) {
                throw new KaitaiStream.ValidationNotEqualError(Png.CompressionMethods.ZLIB, this.compressionMethod, this._io, "/types/compressed_text_chunk/seq/1");
            }
            this._raw__raw_text = this._io.readBytesFull();
            this._raw_text = KaitaiStream.processZlib(this._raw__raw_text);
            KaitaiStream _io__raw_text = new ByteBufferKaitaiStream(this._raw_text);
            this.text = new CompressedText(_io__raw_text, this, _root);
        }

        public void _fetchInstances() {
            this.text._fetchInstances();
        }
        private String keyword;
        private CompressionMethods compressionMethod;
        private CompressedText text;
        private Png _root;
        private Png.Chunk _parent;
        private byte[] _raw_text;
        private byte[] _raw__raw_text;

        /**
         * Indicates the type of information represented by the text string.
         * 
         * Keywords must consist exclusively of printable ISO-8859-1 (Latin-1)
         * characters and spaces; that is, only code points 0x20-0x7E and
         * 0xA1-0xFF are allowed. To reduce the chances for human misreading of a
         * keyword, leading spaces, trailing spaces, and consecutive spaces are
         * not permitted.
         * @see <a href="https://www.w3.org/TR/2025/REC-png-3-20250624/#11keywords">Source</a>
         */
        public String keyword() { return keyword; }
        public CompressionMethods compressionMethod() { return compressionMethod; }
        public CompressedText text() { return text; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
        public byte[] _raw_text() { return _raw_text; }
        public byte[] _raw__raw_text() { return _raw__raw_text; }
    }

    /**
     * @see <a href="https://web.archive.org/web/20210302212148/https://discussion.evernote.com/forums/topic/88532-how-to-extract-annotation-information-from-annotated-evernoteskitch-images/#comment-451501">Source</a>
     */
    public static class EvernoteSkmfChunk extends KaitaiStruct {
        public static EvernoteSkmfChunk fromFile(String fileName) throws IOException {
            return new EvernoteSkmfChunk(new ByteBufferKaitaiStream(fileName));
        }

        public EvernoteSkmfChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public EvernoteSkmfChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public EvernoteSkmfChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.json = new String(this._io.readBytesFull(), StandardCharsets.UTF_8);
        }

        public void _fetchInstances() {
        }
        private String json;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * JSON document with information about editable annotations (text,
         * lines, paths, etc.) in Evernote/Skitch.
         * 
         * It refers to the original image stored in the `skRf` chunk (which
         * usually follows immediately after `skMf`) via the
         * `.children[0].children[0].uri` JSON property. This has the format
         * `"skitch+uuid:///$UUID"`, where `$UUID` is a random UUIDv4 value that
         * matches the `uuid` field in `evernote_skrf_chunk` (i.e. in the first
         * 16 bytes of the `skRf` chunk).
         */
        public String json() { return json; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://web.archive.org/web/20210302212148/https://discussion.evernote.com/forums/topic/88532-how-to-extract-annotation-information-from-annotated-evernoteskitch-images/#comment-451501">Source</a>
     */
    public static class EvernoteSkrfChunk extends KaitaiStruct {
        public static EvernoteSkrfChunk fromFile(String fileName) throws IOException {
            return new EvernoteSkrfChunk(new ByteBufferKaitaiStream(fileName));
        }

        public EvernoteSkrfChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public EvernoteSkrfChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public EvernoteSkrfChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.uuid = this._io.readBytes(16);
            this.origImg = this._io.readBytesFull();
        }

        public void _fetchInstances() {
        }
        private byte[] uuid;
        private byte[] origImg;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Random UUIDv4 value used to identify the image. It is referenced by
         * the `skMf` chunk - see the documentation for the `json` field in
         * `evernote_skmf_chunk`.
         */
        public byte[] uuid() { return uuid; }

        /**
         * The original source image without annotations. It's usually a PNG
         * image as well, but it can also be a JPEG or possibly other formats.
         */
        public byte[] origImg() { return origImg; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#fcTL-chunk">Source</a>
     */
    public static class FrameControlChunk extends KaitaiStruct {
        public static FrameControlChunk fromFile(String fileName) throws IOException {
            return new FrameControlChunk(new ByteBufferKaitaiStream(fileName));
        }

        public FrameControlChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public FrameControlChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public FrameControlChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.sequenceNumber = this._io.readU4be();
            this.width = this._io.readU4be();
            if (!(this.width >= 1)) {
                throw new KaitaiStream.ValidationLessThanError(1, this.width, this._io, "/types/frame_control_chunk/seq/1");
            }
            if (!(this.width <= _root().ihdr().width())) {
                throw new KaitaiStream.ValidationGreaterThanError(_root().ihdr().width(), this.width, this._io, "/types/frame_control_chunk/seq/1");
            }
            this.height = this._io.readU4be();
            if (!(this.height >= 1)) {
                throw new KaitaiStream.ValidationLessThanError(1, this.height, this._io, "/types/frame_control_chunk/seq/2");
            }
            if (!(this.height <= _root().ihdr().height())) {
                throw new KaitaiStream.ValidationGreaterThanError(_root().ihdr().height(), this.height, this._io, "/types/frame_control_chunk/seq/2");
            }
            this.xOffset = this._io.readU4be();
            if (!(this.xOffset <= _root().ihdr().width() - width())) {
                throw new KaitaiStream.ValidationGreaterThanError(_root().ihdr().width() - width(), this.xOffset, this._io, "/types/frame_control_chunk/seq/3");
            }
            this.yOffset = this._io.readU4be();
            if (!(this.yOffset <= _root().ihdr().height() - height())) {
                throw new KaitaiStream.ValidationGreaterThanError(_root().ihdr().height() - height(), this.yOffset, this._io, "/types/frame_control_chunk/seq/4");
            }
            this.delayNum = this._io.readU2be();
            this.delayDen = this._io.readU2be();
            this.disposeOp = Png.DisposeOpValues.byId(this._io.readU1());
            if (this.disposeOp == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.disposeOp, this._io, "/types/frame_control_chunk/seq/7");
            }
            this.blendOp = Png.BlendOpValues.byId(this._io.readU1());
            if (this.blendOp == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.blendOp, this._io, "/types/frame_control_chunk/seq/8");
            }
        }

        public void _fetchInstances() {
        }
        private Double delay;

        /**
         * Time to display this frame, in seconds
         */
        public Double delay() {
            if (this.delay != null)
                return this.delay;
            this.delay = ((Number) (delayNum() / (delayDen() == 0 ? 100.0 : delayDen()))).doubleValue();
            return this.delay;
        }
        private long sequenceNumber;
        private long width;
        private long height;
        private long xOffset;
        private long yOffset;
        private int delayNum;
        private int delayDen;
        private DisposeOpValues disposeOp;
        private BlendOpValues blendOp;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Sequence number of the animation chunk, starting from 0.
         * 
         * The `fcTL` and `fdAT` chunks have a 4-byte sequence number. Both chunk
         * types share the sequence. The purpose of this number is to detect (and
         * optionally correct) sequence errors in an Animated PNG, since the PNG
         * specification does not impose ordering restrictions on ancillary
         * chunks (which means that a PNG editor is technically allowed to
         * reorder them arbitrarily, see [14.2 Behavior of PNG
         * editors](https://www.w3.org/TR/png/#14Ordering) in the spec).
         * 
         * The first `fcTL` chunk must contain sequence number 0, and the
         * sequence numbers in the remaining `fcTL` and `fdAT` chunks must be in
         * ascending order, with no gaps or duplicates.
         */
        public long sequenceNumber() { return sequenceNumber; }

        /**
         * Width of the following frame
         */
        public long width() { return width; }

        /**
         * Height of the following frame
         */
        public long height() { return height; }

        /**
         * X position at which to render the following frame
         */
        public long xOffset() { return xOffset; }

        /**
         * Y position at which to render the following frame
         */
        public long yOffset() { return yOffset; }

        /**
         * Frame delay fraction numerator
         */
        public int delayNum() { return delayNum; }

        /**
         * Frame delay fraction denominator
         */
        public int delayDen() { return delayDen; }

        /**
         * Type of frame area disposal to be done after rendering this frame
         */
        public DisposeOpValues disposeOp() { return disposeOp; }

        /**
         * Type of frame area rendering for this frame
         */
        public BlendOpValues blendOp() { return blendOp; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#fdAT-chunk">Source</a>
     */
    public static class FrameDataChunk extends KaitaiStruct {
        public static FrameDataChunk fromFile(String fileName) throws IOException {
            return new FrameDataChunk(new ByteBufferKaitaiStream(fileName));
        }

        public FrameDataChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public FrameDataChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public FrameDataChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.sequenceNumber = this._io.readU4be();
            this.frameData = this._io.readBytesFull();
        }

        public void _fetchInstances() {
        }
        private long sequenceNumber;
        private byte[] frameData;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Sequence number of the animation chunk, starting from 0.
         * 
         * The `fcTL` and `fdAT` chunks have a 4-byte sequence number. Both chunk
         * types share the sequence. The purpose of this number is to detect (and
         * optionally correct) sequence errors in an Animated PNG, since the PNG
         * specification does not impose ordering restrictions on ancillary
         * chunks (which means that a PNG editor is technically allowed to
         * reorder them arbitrarily, see [14.2 Behavior of PNG
         * editors](https://www.w3.org/TR/png/#14Ordering) in the spec).
         * 
         * The first `fcTL` chunk must contain sequence number 0, and the
         * sequence numbers in the remaining `fcTL` and `fdAT` chunks must be in
         * ascending order, with no gaps or duplicates.
         */
        public long sequenceNumber() { return sequenceNumber; }

        /**
         * Frame data for the frame. At least one `fdAT` chunk is required for
         * each frame, except for the first frame, if that frame is represented
         * by an `IDAT` chunk. The compressed datastream for each frame is the
         * concatenation of the contents of the data fields of all the `fdAT`
         * chunks within a frame.
         */
        public byte[] frameData() { return frameData; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#11gAMA">Source</a>
     */
    public static class GamaChunk extends KaitaiStruct {
        public static GamaChunk fromFile(String fileName) throws IOException {
            return new GamaChunk(new ByteBufferKaitaiStream(fileName));
        }

        public GamaChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public GamaChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public GamaChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.gammaInt = this._io.readU4be();
            {
                long _it = this.gammaInt;
                if (!(_it != 0)) {
                    throw new KaitaiStream.ValidationExprError(this.gammaInt, this._io, "/types/gama_chunk/seq/0");
                }
            }
        }

        public void _fetchInstances() {
        }
        private Double gamma;

        /**
         * Image gamma, typically 0.45455 = 1/2.2
         */
        public Double gamma() {
            if (this.gamma != null)
                return this.gamma;
            this.gamma = ((Number) (gammaInt() / 100000.0)).doubleValue();
            return this.gamma;
        }
        private Double invGamma;

        /**
         * Inverse of the image gamma (1 / gamma), typically 2.2 (not considering
         * rounding)
         */
        public Double invGamma() {
            if (this.invGamma != null)
                return this.invGamma;
            this.invGamma = ((Number) (100000.0 / gammaInt())).doubleValue();
            return this.invGamma;
        }
        private long gammaInt;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Image gamma multiplied by 100000 (a gamma value of 1/2.2 is stored as
         * 45455)
         */
        public long gammaInt() { return gammaInt; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#11IHDR">Source</a>
     */
    public static class IhdrChunk extends KaitaiStruct {
        public static IhdrChunk fromFile(String fileName) throws IOException {
            return new IhdrChunk(new ByteBufferKaitaiStream(fileName));
        }

        public IhdrChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public IhdrChunk(KaitaiStream _io, Png _parent) {
            this(_io, _parent, null);
        }

        public IhdrChunk(KaitaiStream _io, Png _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.width = this._io.readU4be();
            if (!(this.width >= 1)) {
                throw new KaitaiStream.ValidationLessThanError(1, this.width, this._io, "/types/ihdr_chunk/seq/0");
            }
            this.height = this._io.readU4be();
            if (!(this.height >= 1)) {
                throw new KaitaiStream.ValidationLessThanError(1, this.height, this._io, "/types/ihdr_chunk/seq/1");
            }
            this.bitDepth = this._io.readU1();
            if (!( ((this.bitDepth == 1) || (this.bitDepth == 2) || (this.bitDepth == 4) || (this.bitDepth == 8) || (this.bitDepth == 16)) )) {
                throw new KaitaiStream.ValidationNotAnyOfError(this.bitDepth, this._io, "/types/ihdr_chunk/seq/2");
            }
            this.colorType = Png.ColorType.byId(this._io.readU1());
            if (this.colorType == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.colorType, this._io, "/types/ihdr_chunk/seq/3");
            }
            this.compressionMethod = Png.CompressionMethods.byId(this._io.readU1());
            if (this.compressionMethod == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.compressionMethod, this._io, "/types/ihdr_chunk/seq/4");
            }
            this.filterMethod = Png.FilterMethod.byId(this._io.readU1());
            if (this.filterMethod == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.filterMethod, this._io, "/types/ihdr_chunk/seq/5");
            }
            this.interlaceMethod = Png.InterlaceMethod.byId(this._io.readU1());
            if (this.interlaceMethod == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.interlaceMethod, this._io, "/types/ihdr_chunk/seq/6");
            }
        }

        public void _fetchInstances() {
        }
        private long width;
        private long height;
        private int bitDepth;
        private ColorType colorType;
        private CompressionMethods compressionMethod;
        private FilterMethod filterMethod;
        private InterlaceMethod interlaceMethod;
        private Png _root;
        private Png _parent;
        public long width() { return width; }
        public long height() { return height; }
        public int bitDepth() { return bitDepth; }
        public ColorType colorType() { return colorType; }
        public CompressionMethods compressionMethod() { return compressionMethod; }
        public FilterMethod filterMethod() { return filterMethod; }
        public InterlaceMethod interlaceMethod() { return interlaceMethod; }
        public Png _root() { return _root; }
        public Png _parent() { return _parent; }
    }
    public static class InternationalText extends KaitaiStruct {
        public static InternationalText fromFile(String fileName) throws IOException {
            return new InternationalText(new ByteBufferKaitaiStream(fileName));
        }

        public InternationalText(KaitaiStream _io) {
            this(_io, null, null);
        }

        public InternationalText(KaitaiStream _io, Png.InternationalTextChunk _parent) {
            this(_io, _parent, null);
        }

        public InternationalText(KaitaiStream _io, Png.InternationalTextChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.value = new String(this._io.readBytesFull(), StandardCharsets.UTF_8);
        }

        public void _fetchInstances() {
        }
        private String value;
        private Png _root;
        private Png.InternationalTextChunk _parent;

        /**
         * Text string (the "value" of this key-value pair), written in language
         * specified in `_parent.language_tag`.
         * 
         * Although it is not null-terminated (unlike other textual data in the
         * `iTXt` chunk), it must not contain a zero byte
         * (U+0000 NULL character). A newline should be represented by a single
         * U+000A LINE FEED (LF) character (aka `\n`). The remaining control
         * characters (U+0001..U+0009, U+000B..0+001F, U+007F..U+009F) are
         * discouraged.
         */
        public String value() { return value; }
        public Png _root() { return _root; }
        public Png.InternationalTextChunk _parent() { return _parent; }
    }

    /**
     * International textual data (`iTXt`) chunk effectively allows you to store
     * key-value string pairs in the PNG container.
     * 
     * The "key" part (`keyword`) is restricted to printable ISO-8859-1 (Latin-1)
     * characters and spaces. The translated keyword and the "value" part
     * (`text`) are stored in UTF-8 and thus can store text in any language -
     * this language can be indicated via the language tag (`language_tag`).
     * @see <a href="https://www.w3.org/TR/png/#11iTXt">Source</a>
     */
    public static class InternationalTextChunk extends KaitaiStruct {
        public static InternationalTextChunk fromFile(String fileName) throws IOException {
            return new InternationalTextChunk(new ByteBufferKaitaiStream(fileName));
        }

        public InternationalTextChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public InternationalTextChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public InternationalTextChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.keyword = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.ISO_8859_1);
            this.compressionFlag = this._io.readU1();
            if (!( ((this.compressionFlag == 0) || (this.compressionFlag == 1)) )) {
                throw new KaitaiStream.ValidationNotAnyOfError(this.compressionFlag, this._io, "/types/international_text_chunk/seq/1");
            }
            this.compressionMethod = Png.CompressionMethods.byId(this._io.readU1());
            if (!(this.compressionMethod == (compressionFlag() == 1 ? Png.CompressionMethods.ZLIB : compressionMethod()))) {
                throw new KaitaiStream.ValidationNotEqualError((compressionFlag() == 1 ? Png.CompressionMethods.ZLIB : compressionMethod()), this.compressionMethod, this._io, "/types/international_text_chunk/seq/2");
            }
            this.languageTag = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.US_ASCII);
            this.translatedKeyword = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.UTF_8);
            if (compressionFlag() == 0) {
                this._raw_textPlain = this._io.readBytesFull();
                KaitaiStream _io__raw_textPlain = new ByteBufferKaitaiStream(this._raw_textPlain);
                this.textPlain = new InternationalText(_io__raw_textPlain, this, _root);
            }
            if (compressionFlag() == 1) {
                this._raw__raw_textZlib = this._io.readBytesFull();
                this._raw_textZlib = KaitaiStream.processZlib(this._raw__raw_textZlib);
                KaitaiStream _io__raw_textZlib = new ByteBufferKaitaiStream(this._raw_textZlib);
                this.textZlib = new InternationalText(_io__raw_textZlib, this, _root);
            }
        }

        public void _fetchInstances() {
            if (compressionFlag() == 0) {
                this.textPlain._fetchInstances();
            }
            if (compressionFlag() == 1) {
                this.textZlib._fetchInstances();
            }
        }
        private String text;

        /**
         * Text string (the "value" of this key-value pair), written in language
         * specified in `language_tag`.
         * 
         * Although it is not null-terminated (unlike other textual data in the
         * `iTXt` chunk), it must not contain a zero byte
         * (U+0000 NULL character). A newline should be represented by a single
         * U+000A LINE FEED (LF) character (aka `\n`). The remaining control
         * characters (U+0001..U+0009, U+000B..0+001F, U+007F..U+009F) are
         * discouraged.
         */
        public String text() {
            if (this.text != null)
                return this.text;
            this.text = (compressionFlag() == 0 ? textPlain() : textZlib()).value();
            return this.text;
        }
        private String keyword;
        private int compressionFlag;
        private CompressionMethods compressionMethod;
        private String languageTag;
        private String translatedKeyword;
        private InternationalText textPlain;
        private InternationalText textZlib;
        private Png _root;
        private Png.Chunk _parent;
        private byte[] _raw_textPlain;
        private byte[] _raw_textZlib;
        private byte[] _raw__raw_textZlib;

        /**
         * Indicates the type of information represented by the text string.
         * 
         * Keywords must consist exclusively of printable ISO-8859-1 (Latin-1)
         * characters and spaces; that is, only code points 0x20-0x7E and
         * 0xA1-0xFF are allowed. To reduce the chances for human misreading of a
         * keyword, leading spaces, trailing spaces, and consecutive spaces are
         * not permitted.
         * @see <a href="https://www.w3.org/TR/2025/REC-png-3-20250624/#11keywords">Source</a>
         */
        public String keyword() { return keyword; }

        /**
         * 0 = text is uncompressed, 1 = text is compressed with a
         * method specified in `compression_method`.
         */
        public int compressionFlag() { return compressionFlag; }
        public CompressionMethods compressionMethod() { return compressionMethod; }

        /**
         * Human language used in the `translated_keyword` and `text` fields.
         * 
         * From the [official
         * specification](https://www.w3.org/TR/2025/REC-png-3-20250624/#11iTXt):
         * 
         * > The language tag is a well-formed language tag defined by [RFC 5646:
         * > BCP 47: Tags for Identifying
         * > Languages](https://www.rfc-editor.org/info/rfc5646/). Unlike the
         * > keyword, the language tag is case-insensitive. Subtags must appear
         * > in the [IANA language subtag
         * > registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
         * > If the language tag is empty, the language is unspecified. Examples
         * > of language tags include: `en`, `en-GB`, `es-419`, `zh-Hans`,
         * > `zh-Hans-CN`, `tlh-Cyrl-AQ`, `ar-AE-u-nu-latn`, and `x-private`.
         */
        public String languageTag() { return languageTag; }

        /**
         * The keyword (`keyword`) translated into the language specified in
         * `language_tag`.
         * 
         * It must not contain a zero byte (U+0000 NULL character). Line breaks
         * should not appear. The remaining control characters (U+0001..U+0009,
         * U+000B..0+001F, U+007F..U+009F) are discouraged.
         */
        public String translatedKeyword() { return translatedKeyword; }
        public InternationalText textPlain() { return textPlain; }
        public InternationalText textZlib() { return textZlib; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
        public byte[] _raw_textPlain() { return _raw_textPlain; }
        public byte[] _raw_textZlib() { return _raw_textZlib; }
        public byte[] _raw__raw_textZlib() { return _raw__raw_textZlib; }
    }
    public static class MdcvChromaticity extends KaitaiStruct {
        public static MdcvChromaticity fromFile(String fileName) throws IOException {
            return new MdcvChromaticity(new ByteBufferKaitaiStream(fileName));
        }

        public MdcvChromaticity(KaitaiStream _io) {
            this(_io, null, null);
        }

        public MdcvChromaticity(KaitaiStream _io, Png.MdcvChunk _parent) {
            this(_io, _parent, null);
        }

        public MdcvChromaticity(KaitaiStream _io, Png.MdcvChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.xInt = this._io.readU2be();
            this.yInt = this._io.readU2be();
        }

        public void _fetchInstances() {
        }
        private Double x;
        public Double x() {
            if (this.x != null)
                return this.x;
            this.x = ((Number) (xInt() * 0.00002)).doubleValue();
            return this.x;
        }
        private Double y;
        public Double y() {
            if (this.y != null)
                return this.y;
            this.y = ((Number) (yInt() * 0.00002)).doubleValue();
            return this.y;
        }
        private int xInt;
        private int yInt;
        private Png _root;
        private Png.MdcvChunk _parent;
        public int xInt() { return xInt; }
        public int yInt() { return yInt; }
        public Png _root() { return _root; }
        public Png.MdcvChunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#mDCV-chunk">Source</a>
     * @see <a href="https://w3c.github.io/png/Implementation_Report_3e/#mastering">Source</a>
     */
    public static class MdcvChunk extends KaitaiStruct {
        public static MdcvChunk fromFile(String fileName) throws IOException {
            return new MdcvChunk(new ByteBufferKaitaiStream(fileName));
        }

        public MdcvChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public MdcvChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public MdcvChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.red = new MdcvChromaticity(this._io, this, _root);
            this.green = new MdcvChromaticity(this._io, this, _root);
            this.blue = new MdcvChromaticity(this._io, this, _root);
            this.whitePoint = new MdcvChromaticity(this._io, this, _root);
            this.maxLuminanceInt = this._io.readU4be();
            this.minLuminanceInt = this._io.readU4be();
        }

        public void _fetchInstances() {
            this.red._fetchInstances();
            this.green._fetchInstances();
            this.blue._fetchInstances();
            this.whitePoint._fetchInstances();
        }
        private Double maxLuminance;

        /**
         * Maximum luminance in cd/m^2
         */
        public Double maxLuminance() {
            if (this.maxLuminance != null)
                return this.maxLuminance;
            this.maxLuminance = ((Number) (maxLuminanceInt() * 0.0001)).doubleValue();
            return this.maxLuminance;
        }
        private Double minLuminance;

        /**
         * Minimum luminance in cd/m^2
         */
        public Double minLuminance() {
            if (this.minLuminance != null)
                return this.minLuminance;
            this.minLuminance = ((Number) (minLuminanceInt() * 0.0001)).doubleValue();
            return this.minLuminance;
        }
        private MdcvChromaticity red;
        private MdcvChromaticity green;
        private MdcvChromaticity blue;
        private MdcvChromaticity whitePoint;
        private long maxLuminanceInt;
        private long minLuminanceInt;
        private Png _root;
        private Png.Chunk _parent;
        public MdcvChromaticity red() { return red; }
        public MdcvChromaticity green() { return green; }
        public MdcvChromaticity blue() { return blue; }
        public MdcvChromaticity whitePoint() { return whitePoint; }
        public long maxLuminanceInt() { return maxLuminanceInt; }
        public long minLuminanceInt() { return minLuminanceInt; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * Physical pixel dimensions (`pHYs`) chunk specifies the intended physical
     * size of the pixels (in meters) or pixel aspect ratio for display of the
     * image.
     * @see <a href="https://www.w3.org/TR/png/#11pHYs">Source</a>
     */
    public static class PhysChunk extends KaitaiStruct {
        public static PhysChunk fromFile(String fileName) throws IOException {
            return new PhysChunk(new ByteBufferKaitaiStream(fileName));
        }

        public PhysChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public PhysChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public PhysChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.pixelsPerUnitX = this._io.readU4be();
            this.pixelsPerUnitY = this._io.readU4be();
            this.unit = Png.PhysUnit.byId(this._io.readU1());
            if (this.unit == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.unit, this._io, "/types/phys_chunk/seq/2");
            }
        }

        public void _fetchInstances() {
        }
        private Double dotsPerInchX;

        /**
         * Horizontal resolution (DPI)
         */
        public Double dotsPerInchX() {
            if (this.dotsPerInchX != null)
                return this.dotsPerInchX;
            if (unit() == Png.PhysUnit.METER) {
                this.dotsPerInchX = ((Number) (pixelsPerUnitX() * 0.0254)).doubleValue();
            }
            return this.dotsPerInchX;
        }
        private Double dotsPerInchY;

        /**
         * Vertical resolution (DPI)
         */
        public Double dotsPerInchY() {
            if (this.dotsPerInchY != null)
                return this.dotsPerInchY;
            if (unit() == Png.PhysUnit.METER) {
                this.dotsPerInchY = ((Number) (pixelsPerUnitY() * 0.0254)).doubleValue();
            }
            return this.dotsPerInchY;
        }
        private long pixelsPerUnitX;
        private long pixelsPerUnitY;
        private PhysUnit unit;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Number of pixels per physical unit (typically, 1 meter) by X
         * axis.
         */
        public long pixelsPerUnitX() { return pixelsPerUnitX; }

        /**
         * Number of pixels per physical unit (typically, 1 meter) by Y
         * axis.
         */
        public long pixelsPerUnitY() { return pixelsPerUnitY; }
        public PhysUnit unit() { return unit; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#11PLTE">Source</a>
     */
    public static class PlteChunk extends KaitaiStruct {
        public static PlteChunk fromFile(String fileName) throws IOException {
            return new PlteChunk(new ByteBufferKaitaiStream(fileName));
        }

        public PlteChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public PlteChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public PlteChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.entries = new ArrayList<Rgb>();
            {
                int i = 0;
                while (!this._io.isEof()) {
                    this.entries.add(new Rgb(this._io, this, _root));
                    i++;
                }
            }
        }

        public void _fetchInstances() {
            for (int i = 0; i < this.entries.size(); i++) {
                this.entries.get(((Number) (i)).intValue())._fetchInstances();
            }
        }
        private List<Rgb> entries;
        private Png _root;
        private Png.Chunk _parent;
        public List<Rgb> entries() { return entries; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }
    public static class Rgb extends KaitaiStruct {
        public static Rgb fromFile(String fileName) throws IOException {
            return new Rgb(new ByteBufferKaitaiStream(fileName));
        }

        public Rgb(KaitaiStream _io) {
            this(_io, null, null);
        }

        public Rgb(KaitaiStream _io, Png.PlteChunk _parent) {
            this(_io, _parent, null);
        }

        public Rgb(KaitaiStream _io, Png.PlteChunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.r = this._io.readU1();
            this.g = this._io.readU1();
            this.b = this._io.readU1();
        }

        public void _fetchInstances() {
        }
        private int r;
        private int g;
        private int b;
        private Png _root;
        private Png.PlteChunk _parent;
        public int r() { return r; }
        public int g() { return g; }
        public int b() { return b; }
        public Png _root() { return _root; }
        public Png.PlteChunk _parent() { return _parent; }
    }

    /**
     * @see <a href="https://www.w3.org/TR/png/#11sRGB">Source</a>
     */
    public static class SrgbChunk extends KaitaiStruct {
        public static SrgbChunk fromFile(String fileName) throws IOException {
            return new SrgbChunk(new ByteBufferKaitaiStream(fileName));
        }

        public enum Intent {
            PERCEPTUAL(0),
            RELATIVE_COLORIMETRIC(1),
            SATURATION(2),
            ABSOLUTE_COLORIMETRIC(3);

            private final long id;
            Intent(long id) { this.id = id; }
            public long id() { return id; }
            private static final Map<Long, Intent> byId = new HashMap<Long, Intent>(4);
            static {
                for (Intent e : Intent.values())
                    byId.put(e.id(), e);
            }
            public static Intent byId(long id) { return byId.get(id); }
        }

        public SrgbChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public SrgbChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public SrgbChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.renderIntent = Intent.byId(this._io.readU1());
            if (this.renderIntent == null) {
                throw new KaitaiStream.ValidationNotInEnumError(this.renderIntent, this._io, "/types/srgb_chunk/seq/0");
            }
        }

        public void _fetchInstances() {
        }
        private Intent renderIntent;
        private Png _root;
        private Png.Chunk _parent;
        public Intent renderIntent() { return renderIntent; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * Textual data (`tEXt`) chunk effectively allows you to store key-value
     * string pairs in the PNG container.
     * 
     * Both the "key" (`keyword`) and "value" (`text`) parts are restricted to
     * printable ISO-8859-1 (Latin-1) characters and ASCII spaces, with the
     * exception that `text` can also contain newlines (U+000A LINE FEED (LF)
     * characters) and U+00A0 NON-BREAKING SPACE characters.
     * @see <a href="https://www.w3.org/TR/png/#11tEXt">Source</a>
     */
    public static class TextChunk extends KaitaiStruct {
        public static TextChunk fromFile(String fileName) throws IOException {
            return new TextChunk(new ByteBufferKaitaiStream(fileName));
        }

        public TextChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TextChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public TextChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.keyword = new String(this._io.readBytesTerm((byte) 0, false, true, true), StandardCharsets.ISO_8859_1);
            this.text = new String(this._io.readBytesFull(), StandardCharsets.ISO_8859_1);
        }

        public void _fetchInstances() {
        }
        private String keyword;
        private String text;
        private Png _root;
        private Png.Chunk _parent;

        /**
         * Indicates the type of information represented by the text string.
         * 
         * Keywords must consist exclusively of printable ISO-8859-1 (Latin-1)
         * characters and spaces; that is, only code points 0x20-0x7E and
         * 0xA1-0xFF are allowed. To reduce the chances for human misreading of a
         * keyword, leading spaces, trailing spaces, and consecutive spaces are
         * not permitted.
         * @see <a href="https://www.w3.org/TR/2025/REC-png-3-20250624/#11keywords">Source</a>
         */
        public String keyword() { return keyword; }

        /**
         * Text string (the "value" of this key-value pair).
         * 
         * Although it is not null-terminated (unlike the keyword), it must not
         * contain a zero byte (U+0000 NULL character). A newline should be
         * represented by a single U+000A LINE FEED (LF) character (aka `\n`).
         * The remaining control characters (U+0001..U+0009, U+000B..0+001F,
         * U+007F..U+009F) are discouraged.
         */
        public String text() { return text; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }

    /**
     * Time chunk stores time stamp of last modification of this image,
     * up to 1 second precision in UTC timezone.
     * @see <a href="https://www.w3.org/TR/png/#11tIME">Source</a>
     */
    public static class TimeChunk extends KaitaiStruct {
        public static TimeChunk fromFile(String fileName) throws IOException {
            return new TimeChunk(new ByteBufferKaitaiStream(fileName));
        }

        public TimeChunk(KaitaiStream _io) {
            this(_io, null, null);
        }

        public TimeChunk(KaitaiStream _io, Png.Chunk _parent) {
            this(_io, _parent, null);
        }

        public TimeChunk(KaitaiStream _io, Png.Chunk _parent, Png _root) {
            super(_io);
            this._parent = _parent;
            this._root = _root;
            _read();
        }
        private void _read() {
            this.year = this._io.readU2be();
            this.month = this._io.readU1();
            this.day = this._io.readU1();
            this.hour = this._io.readU1();
            this.minute = this._io.readU1();
            this.second = this._io.readU1();
        }

        public void _fetchInstances() {
        }
        private int year;
        private int month;
        private int day;
        private int hour;
        private int minute;
        private int second;
        private Png _root;
        private Png.Chunk _parent;
        public int year() { return year; }
        public int month() { return month; }
        public int day() { return day; }
        public int hour() { return hour; }
        public int minute() { return minute; }
        public int second() { return second; }
        public Png _root() { return _root; }
        public Png.Chunk _parent() { return _parent; }
    }
    private byte[] magic;
    private long ihdrLen;
    private byte[] ihdrType;
    private IhdrChunk ihdr;
    private long ihdrCrc;
    private List<Chunk> chunks;
    private Png _root;
    private KaitaiStruct _parent;
    public byte[] magic() { return magic; }
    public long ihdrLen() { return ihdrLen; }
    public byte[] ihdrType() { return ihdrType; }
    public IhdrChunk ihdr() { return ihdr; }
    public long ihdrCrc() { return ihdrCrc; }
    public List<Chunk> chunks() { return chunks; }
    public Png _root() { return _root; }
    public KaitaiStruct _parent() { return _parent; }
}