PNG (Portable Network Graphics) file: Lua 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.

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

png.lua

-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
--
-- This file is compatible with Lua 5.3

local class = require("class")
require("kaitaistruct")
local enum = require("enum")
local str_decode = require("string_decode")
local utils = require("utils")
local stringstream = require("string_stream")

-- 
-- Test files for APNG can be found at the following locations:
-- 
--   * <https://philip.html5.org/tests/apng/tests.html>
--   * <http://littlesvr.ca/apng/>
Png = class.class(KaitaiStruct)

Png.BlendOpValues = enum.Enum {
  source = 0,
  over = 1,
}

Png.ColorType = enum.Enum {
  greyscale = 0,
  truecolor = 2,
  indexed = 3,
  greyscale_alpha = 4,
  truecolor_alpha = 6,
}

Png.CompressionMethods = enum.Enum {
  zlib = 0,
}

Png.DisposeOpValues = enum.Enum {
  none = 0,
  background = 1,
  previous = 2,
}

Png.PhysUnit = enum.Enum {
  unknown = 0,
  meter = 1,
}

function Png:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root or self
  self:_read()
end

function Png:_read()
  self.magic = self._io:read_bytes(8)
  if not(self.magic == "\137\080\078\071\013\010\026\010") then
    error("not equal, expected " .. "\137\080\078\071\013\010\026\010" .. ", but got " .. self.magic)
  end
  self.ihdr_len = self._io:read_u4be()
  if not(self.ihdr_len == 13) then
    error("not equal, expected " .. 13 .. ", but got " .. self.ihdr_len)
  end
  self.ihdr_type = self._io:read_bytes(4)
  if not(self.ihdr_type == "\073\072\068\082") then
    error("not equal, expected " .. "\073\072\068\082" .. ", but got " .. self.ihdr_type)
  end
  self.ihdr = Png.IhdrChunk(self._io, self, self._root)
  self.ihdr_crc = self._io:read_bytes(4)
  self.chunks = {}
  local i = 0
  while true do
    local _ = Png.Chunk(self._io, self, self._root)
    self.chunks[i + 1] = _
    if  ((_.type == "IEND") or (self._io:is_eof()))  then
      break
    end
    i = i + 1
  end
end


-- 
-- See also: Source (https://stackoverflow.com/questions/4242402/the-fireworks-png-format-any-insight-any-libs/51683285#51683285)
Png.AdobeFireworksChunk = class.class(KaitaiStruct)

function Png.AdobeFireworksChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.AdobeFireworksChunk:_read()
  self._raw_preview_data = self._io:read_bytes_full()
  self.preview_data = KaitaiStream.process_zlib(self._raw_preview_data)
end


-- 
-- See also: Source (https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk)
Png.AnimationControlChunk = class.class(KaitaiStruct)

function Png.AnimationControlChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.AnimationControlChunk:_read()
  self.num_frames = self._io:read_u4be()
  self.num_plays = self._io:read_u4be()
end

-- 
-- Number of frames, must be equal to the number of `frame_control_chunk`s.
-- 
-- Number of times to loop, 0 indicates infinite looping.

-- 
-- See also: Source (https://github.com/skeeto/scratch/tree/58470254f4a95cdf7a53888e405c851c21eb2cae/pngattach)
-- See also: A new protocol and tool for PNG file attachments (https://nullprogram.com/blog/2021/12/31/)
Png.AtchChunk = class.class(KaitaiStruct)

Png.AtchChunk.CompressionAttachMethods = enum.Enum {
  none = 0,
  zlib = 1,
}

function Png.AtchChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.AtchChunk:_read()
  self.file_name = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  local _ = self.file_name
  if not( ((string.len(_) ~= 0) and (string.sub(_, 0 + 1, 1) ~= ".")) ) then
    error("ValidationExprError")
  end
  self.compression = Png.AtchChunk.CompressionAttachMethods(self._io:read_u1())
  if not( ((self.compression == Png.AtchChunk.CompressionAttachMethods.none) or (self.compression == Png.AtchChunk.CompressionAttachMethods.zlib)) ) then
    error("ValidationNotAnyOfError")
  end
  if self.compression == Png.AtchChunk.CompressionAttachMethods.none then
    self.data_plain = self._io:read_bytes_full()
  end
  if self.compression == Png.AtchChunk.CompressionAttachMethods.zlib then
    self._raw_data_zlib = self._io:read_bytes_full()
    self.data_zlib = KaitaiStream.process_zlib(self._raw_data_zlib)
  end
end

Png.AtchChunk.property.data = {}
function Png.AtchChunk.property.data:get()
  if self._m_data ~= nil then
    return self._m_data
  end

  self._m_data = utils.box_unwrap((self.compression == Png.AtchChunk.CompressionAttachMethods.none) and utils.box_wrap(self.data_plain) or (self.data_zlib))
  return self._m_data
end

-- 
-- 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.

-- 
-- Background chunk stores default background color to display this
-- image against. Contents depend on `color_type` of the image.
-- See also: Source (https://www.w3.org/TR/png/#11bKGD)
Png.BkgdChunk = class.class(KaitaiStruct)

function Png.BkgdChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.BkgdChunk:_read()
  local _on = self._root.ihdr.color_type
  if _on == Png.ColorType.greyscale then
    self.bkgd = Png.BkgdGreyscale(self._io, self, self._root)
  elseif _on == Png.ColorType.greyscale_alpha then
    self.bkgd = Png.BkgdGreyscale(self._io, self, self._root)
  elseif _on == Png.ColorType.indexed then
    self.bkgd = Png.BkgdIndexed(self._io, self, self._root)
  elseif _on == Png.ColorType.truecolor then
    self.bkgd = Png.BkgdTruecolor(self._io, self, self._root)
  elseif _on == Png.ColorType.truecolor_alpha then
    self.bkgd = Png.BkgdTruecolor(self._io, self, self._root)
  end
end


-- 
-- Background chunk for greyscale images.
Png.BkgdGreyscale = class.class(KaitaiStruct)

function Png.BkgdGreyscale:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.BkgdGreyscale:_read()
  self.value = self._io:read_u2be()
end


-- 
-- Background chunk for images with indexed palette.
Png.BkgdIndexed = class.class(KaitaiStruct)

function Png.BkgdIndexed:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.BkgdIndexed:_read()
  self.palette_index = self._io:read_u1()
end


-- 
-- Background chunk for truecolor images.
Png.BkgdTruecolor = class.class(KaitaiStruct)

function Png.BkgdTruecolor:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.BkgdTruecolor:_read()
  self.red = self._io:read_u2be()
  self.green = self._io:read_u2be()
  self.blue = self._io:read_u2be()
end


-- 
-- See also: Source (https://www.w3.org/TR/png/#11cHRM)
Png.ChrmChunk = class.class(KaitaiStruct)

function Png.ChrmChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.ChrmChunk:_read()
  self.white_point = Png.Point(self._io, self, self._root)
  self.red = Png.Point(self._io, self, self._root)
  self.green = Png.Point(self._io, self, self._root)
  self.blue = Png.Point(self._io, self, self._root)
end


Png.Chunk = class.class(KaitaiStruct)

function Png.Chunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.Chunk:_read()
  self.len = self._io:read_u4be()
  self.type = str_decode.decode(self._io:read_bytes(4), "UTF-8")
  local _ = self.type
  if not(self.type ~= "\000\000\000\000") then
    error("ValidationExprError")
  end
  local _on = self.type
  if _on == "PLTE" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.PlteChunk(_io, self, self._root)
  elseif _on == "acTL" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.AnimationControlChunk(_io, self, self._root)
  elseif _on == "atCh" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.AtchChunk(_io, self, self._root)
  elseif _on == "bKGD" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.BkgdChunk(_io, self, self._root)
  elseif _on == "cHRM" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.ChrmChunk(_io, self, self._root)
  elseif _on == "fcTL" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.FrameControlChunk(_io, self, self._root)
  elseif _on == "fdAT" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.FrameDataChunk(_io, self, self._root)
  elseif _on == "gAMA" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.GamaChunk(_io, self, self._root)
  elseif _on == "iTXt" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.InternationalTextChunk(_io, self, self._root)
  elseif _on == "mkBS" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.AdobeFireworksChunk(_io, self, self._root)
  elseif _on == "mkTS" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.AdobeFireworksChunk(_io, self, self._root)
  elseif _on == "pHYs" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.PhysChunk(_io, self, self._root)
  elseif _on == "prVW" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.AdobeFireworksChunk(_io, self, self._root)
  elseif _on == "sRGB" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.SrgbChunk(_io, self, self._root)
  elseif _on == "skMf" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.EvernoteSkmfChunk(_io, self, self._root)
  elseif _on == "skRf" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.EvernoteSkrfChunk(_io, self, self._root)
  elseif _on == "tEXt" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.TextChunk(_io, self, self._root)
  elseif _on == "tIME" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.TimeChunk(_io, self, self._root)
  elseif _on == "zTXt" then
    self._raw_body = self._io:read_bytes(self.len)
    local _io = KaitaiStream(stringstream(self._raw_body))
    self.body = Png.CompressedTextChunk(_io, self, self._root)
  else
    self.body = self._io:read_bytes(self.len)
  end
  self.crc = self._io:read_bytes(4)
end


-- 
-- Compressed text chunk effectively allows to store key-value
-- string pairs in PNG container, compressing "value" part (which
-- can be quite lengthy) with zlib compression.
-- See also: Source (https://www.w3.org/TR/png/#11zTXt)
Png.CompressedTextChunk = class.class(KaitaiStruct)

function Png.CompressedTextChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.CompressedTextChunk:_read()
  self.keyword = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  self.compression_method = Png.CompressionMethods(self._io:read_u1())
  self._raw_text_datastream = self._io:read_bytes_full()
  self.text_datastream = KaitaiStream.process_zlib(self._raw_text_datastream)
end

-- 
-- Indicates purpose of the following text data.

-- 
-- See also: Source (https://web.archive.org/web/20210302212148/https://discussion.evernote.com/forums/topic/88532-how-to-extract-annotation-information-from-annotated-evernoteskitch-images/#comment-451501)
Png.EvernoteSkmfChunk = class.class(KaitaiStruct)

function Png.EvernoteSkmfChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.EvernoteSkmfChunk:_read()
  self.json = str_decode.decode(self._io:read_bytes_full(), "UTF-8")
end

-- 
-- 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).

-- 
-- See also: Source (https://web.archive.org/web/20210302212148/https://discussion.evernote.com/forums/topic/88532-how-to-extract-annotation-information-from-annotated-evernoteskitch-images/#comment-451501)
Png.EvernoteSkrfChunk = class.class(KaitaiStruct)

function Png.EvernoteSkrfChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.EvernoteSkrfChunk:_read()
  self.uuid = self._io:read_bytes(16)
  self.orig_img = self._io:read_bytes_full()
end

-- 
-- 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`.
-- 
-- 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.

-- 
-- See also: Source (https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk)
Png.FrameControlChunk = class.class(KaitaiStruct)

function Png.FrameControlChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.FrameControlChunk:_read()
  self.sequence_number = self._io:read_u4be()
  self.width = self._io:read_u4be()
  if not(self.width >= 1) then
    error("ValidationLessThanError")
  end
  if not(self.width <= self._root.ihdr.width) then
    error("ValidationGreaterThanError")
  end
  self.height = self._io:read_u4be()
  if not(self.height >= 1) then
    error("ValidationLessThanError")
  end
  if not(self.height <= self._root.ihdr.height) then
    error("ValidationGreaterThanError")
  end
  self.x_offset = self._io:read_u4be()
  if not(self.x_offset <= self._root.ihdr.width - self.width) then
    error("ValidationGreaterThanError")
  end
  self.y_offset = self._io:read_u4be()
  if not(self.y_offset <= self._root.ihdr.height - self.height) then
    error("ValidationGreaterThanError")
  end
  self.delay_num = self._io:read_u2be()
  self.delay_den = self._io:read_u2be()
  self.dispose_op = Png.DisposeOpValues(self._io:read_u1())
  self.blend_op = Png.BlendOpValues(self._io:read_u1())
end

-- 
-- Time to display this frame, in seconds.
Png.FrameControlChunk.property.delay = {}
function Png.FrameControlChunk.property.delay:get()
  if self._m_delay ~= nil then
    return self._m_delay
  end

  self._m_delay = self.delay_num / utils.box_unwrap((self.delay_den == 0) and utils.box_wrap(100.0) or (self.delay_den))
  return self._m_delay
end

-- 
-- Sequence number of the animation chunk.
-- 
-- Width of the following frame.
-- 
-- Height of the following frame.
-- 
-- X position at which to render the following frame.
-- 
-- Y position at which to render the following frame.
-- 
-- Frame delay fraction numerator.
-- 
-- Frame delay fraction denominator.
-- 
-- Type of frame area disposal to be done after rendering this frame.
-- 
-- Type of frame area rendering for this frame.

-- 
-- See also: Source (https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk)
Png.FrameDataChunk = class.class(KaitaiStruct)

function Png.FrameDataChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.FrameDataChunk:_read()
  self.sequence_number = self._io:read_u4be()
  self.frame_data = self._io:read_bytes_full()
end

-- 
-- Sequence number of the animation chunk. The fcTL and fdAT chunks
-- have a 4 byte sequence number. Both chunk types share the sequence.
-- The first fcTL chunk must contain sequence number 0, and the sequence
-- numbers in the remaining fcTL and fdAT chunks must be in order, with
-- no gaps or duplicates.
-- 
-- Frame data for the frame. At least one fdAT chunk is required for
-- each frame. The compressed datastream is the concatenation of the
-- contents of the data fields of all the fdAT chunks within a frame.

-- 
-- See also: Source (https://www.w3.org/TR/png/#11gAMA)
Png.GamaChunk = class.class(KaitaiStruct)

function Png.GamaChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.GamaChunk:_read()
  self.gamma_int = self._io:read_u4be()
end

Png.GamaChunk.property.gamma_ratio = {}
function Png.GamaChunk.property.gamma_ratio:get()
  if self._m_gamma_ratio ~= nil then
    return self._m_gamma_ratio
  end

  self._m_gamma_ratio = 100000.0 / self.gamma_int
  return self._m_gamma_ratio
end


-- 
-- See also: Source (https://www.w3.org/TR/png/#11IHDR)
Png.IhdrChunk = class.class(KaitaiStruct)

function Png.IhdrChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.IhdrChunk:_read()
  self.width = self._io:read_u4be()
  if not(self.width >= 1) then
    error("ValidationLessThanError")
  end
  self.height = self._io:read_u4be()
  if not(self.height >= 1) then
    error("ValidationLessThanError")
  end
  self.bit_depth = self._io:read_u1()
  self.color_type = Png.ColorType(self._io:read_u1())
  self.compression_method = self._io:read_u1()
  self.filter_method = self._io:read_u1()
  self.interlace_method = self._io:read_u1()
end


-- 
-- International text chunk effectively allows to store key-value string pairs in
-- PNG container. Both "key" (keyword) and "value" (text) parts are
-- given in pre-defined subset of iso8859-1 without control
-- characters.
-- See also: Source (https://www.w3.org/TR/png/#11iTXt)
Png.InternationalTextChunk = class.class(KaitaiStruct)

function Png.InternationalTextChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.InternationalTextChunk:_read()
  self.keyword = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  self.compression_flag = self._io:read_u1()
  self.compression_method = Png.CompressionMethods(self._io:read_u1())
  self.language_tag = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "ASCII")
  self.translated_keyword = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "UTF-8")
  self.text = str_decode.decode(self._io:read_bytes_full(), "UTF-8")
end

-- 
-- Indicates purpose of the following text data.
-- 
-- 0 = text is uncompressed, 1 = text is compressed with a
-- method specified in `compression_method`.
-- 
-- Human language used in `translated_keyword` and `text`
-- attributes - should be a language code conforming to ISO
-- 646.IRV:1991.
-- 
-- Keyword translated into language specified in
-- `language_tag`. Line breaks are not allowed.
-- 
-- Text contents ("value" of this key-value pair), written in
-- language specified in `language_tag`. Line breaks are
-- allowed.

-- 
-- "Physical size" chunk stores data that allows to translate
-- logical pixels into physical units (meters, etc) and vice-versa.
-- See also: Source (https://www.w3.org/TR/png/#11pHYs)
Png.PhysChunk = class.class(KaitaiStruct)

function Png.PhysChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.PhysChunk:_read()
  self.pixels_per_unit_x = self._io:read_u4be()
  self.pixels_per_unit_y = self._io:read_u4be()
  self.unit = Png.PhysUnit(self._io:read_u1())
end

-- 
-- Number of pixels per physical unit (typically, 1 meter) by X
-- axis.
-- 
-- Number of pixels per physical unit (typically, 1 meter) by Y
-- axis.

-- 
-- See also: Source (https://www.w3.org/TR/png/#11PLTE)
Png.PlteChunk = class.class(KaitaiStruct)

function Png.PlteChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.PlteChunk:_read()
  self.entries = {}
  local i = 0
  while not self._io:is_eof() do
    self.entries[i + 1] = Png.Rgb(self._io, self, self._root)
    i = i + 1
  end
end


Png.Point = class.class(KaitaiStruct)

function Png.Point:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.Point:_read()
  self.x_int = self._io:read_u4be()
  self.y_int = self._io:read_u4be()
end

Png.Point.property.x = {}
function Png.Point.property.x:get()
  if self._m_x ~= nil then
    return self._m_x
  end

  self._m_x = self.x_int / 100000.0
  return self._m_x
end

Png.Point.property.y = {}
function Png.Point.property.y:get()
  if self._m_y ~= nil then
    return self._m_y
  end

  self._m_y = self.y_int / 100000.0
  return self._m_y
end


Png.Rgb = class.class(KaitaiStruct)

function Png.Rgb:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.Rgb:_read()
  self.r = self._io:read_u1()
  self.g = self._io:read_u1()
  self.b = self._io:read_u1()
end


-- 
-- See also: Source (https://www.w3.org/TR/png/#11sRGB)
Png.SrgbChunk = class.class(KaitaiStruct)

Png.SrgbChunk.Intent = enum.Enum {
  perceptual = 0,
  relative_colorimetric = 1,
  saturation = 2,
  absolute_colorimetric = 3,
}

function Png.SrgbChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.SrgbChunk:_read()
  self.render_intent = Png.SrgbChunk.Intent(self._io:read_u1())
end


-- 
-- Text chunk effectively allows to store key-value string pairs in
-- PNG container. Both "key" (keyword) and "value" (text) parts are
-- given in pre-defined subset of iso8859-1 without control
-- characters.
-- See also: Source (https://www.w3.org/TR/png/#11tEXt)
Png.TextChunk = class.class(KaitaiStruct)

function Png.TextChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.TextChunk:_read()
  self.keyword = str_decode.decode(self._io:read_bytes_term(0, false, true, true), "ISO-8859-1")
  self.text = str_decode.decode(self._io:read_bytes_full(), "ISO-8859-1")
end

-- 
-- Indicates purpose of the following text data.

-- 
-- Time chunk stores time stamp of last modification of this image,
-- up to 1 second precision in UTC timezone.
-- See also: Source (https://www.w3.org/TR/png/#11tIME)
Png.TimeChunk = class.class(KaitaiStruct)

function Png.TimeChunk:_init(io, parent, root)
  KaitaiStruct._init(self, io)
  self._parent = parent
  self._root = root
  self:_read()
end

function Png.TimeChunk:_read()
  self.year = self._io:read_u2be()
  self.month = self._io:read_u1()
  self.day = self._io:read_u1()
  self.hour = self._io:read_u1()
  self.minute = self._io:read_u1()
  self.second = self._io:read_u1()
end