Test files for APNG can be found at the following locations:
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.
All Ruby code generated by Kaitai Struct depends on the Kaitai Struct runtime library for Ruby. You must add this dependency to your project before you can parse or serialize any data.
The Ruby runtime library can be installed from RubyGems:
gem install kaitai-struct
Parse a local file and get structure in memory:
data = Png.from_file("path/to/local/file.png")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = Png.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.magic # => get magic
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
require 'kaitai/struct/struct'
require 'zlib'
unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.11')
raise "Incompatible Kaitai Struct Ruby API: 0.11 or later is required, but you have #{Kaitai::Struct::VERSION}"
end
##
# Test files for APNG can be found at the following locations:
#
# * <https://philip.html5.org/tests/apng/tests.html>
# * <http://littlesvr.ca/apng/>
class Png < Kaitai::Struct::Struct
BLEND_OP_VALUES = {
0 => :blend_op_values_source,
1 => :blend_op_values_over,
}
I__BLEND_OP_VALUES = BLEND_OP_VALUES.invert
COLOR_TYPE = {
0 => :color_type_greyscale,
2 => :color_type_truecolor,
3 => :color_type_indexed,
4 => :color_type_greyscale_alpha,
6 => :color_type_truecolor_alpha,
}
I__COLOR_TYPE = COLOR_TYPE.invert
COMPRESSION_METHODS = {
0 => :compression_methods_zlib,
}
I__COMPRESSION_METHODS = COMPRESSION_METHODS.invert
DISPOSE_OP_VALUES = {
0 => :dispose_op_values_none,
1 => :dispose_op_values_background,
2 => :dispose_op_values_previous,
}
I__DISPOSE_OP_VALUES = DISPOSE_OP_VALUES.invert
PHYS_UNIT = {
0 => :phys_unit_unknown,
1 => :phys_unit_meter,
}
I__PHYS_UNIT = PHYS_UNIT.invert
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root || self)
_read
end
def _read
@magic = @_io.read_bytes(8)
raise Kaitai::Struct::ValidationNotEqualError.new([137, 80, 78, 71, 13, 10, 26, 10].pack('C*'), @magic, @_io, "/seq/0") if not @magic == [137, 80, 78, 71, 13, 10, 26, 10].pack('C*')
@ihdr_len = @_io.read_u4be
raise Kaitai::Struct::ValidationNotEqualError.new(13, @ihdr_len, @_io, "/seq/1") if not @ihdr_len == 13
@ihdr_type = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([73, 72, 68, 82].pack('C*'), @ihdr_type, @_io, "/seq/2") if not @ihdr_type == [73, 72, 68, 82].pack('C*')
@ihdr = IhdrChunk.new(@_io, self, @_root)
@ihdr_crc = @_io.read_bytes(4)
@chunks = []
i = 0
begin
_ = Chunk.new(@_io, self, @_root)
@chunks << _
i += 1
end until ((_.type == "IEND") || (_io.eof?))
self
end
##
# @see https://stackoverflow.com/questions/4242402/the-fireworks-png-format-any-insight-any-libs/51683285#51683285 Source
class AdobeFireworksChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@_raw_preview_data = @_io.read_bytes_full
@preview_data = Zlib::Inflate.inflate(@_raw_preview_data)
self
end
attr_reader :preview_data
attr_reader :_raw_preview_data
end
##
# @see https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk Source
class AnimationControlChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@num_frames = @_io.read_u4be
@num_plays = @_io.read_u4be
self
end
##
# Number of frames, must be equal to the number of `frame_control_chunk`s
attr_reader :num_frames
##
# Number of times to loop, 0 indicates infinite looping.
attr_reader :num_plays
end
##
# @see https://github.com/skeeto/scratch/tree/58470254f4a95cdf7a53888e405c851c21eb2cae/pngattach Source
# @see https://nullprogram.com/blog/2021/12/31/ A new protocol and tool for PNG file attachments
class AtchChunk < Kaitai::Struct::Struct
COMPRESSION_ATTACH_METHODS = {
0 => :compression_attach_methods_none,
1 => :compression_attach_methods_zlib,
}
I__COMPRESSION_ATTACH_METHODS = COMPRESSION_ATTACH_METHODS.invert
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@file_name = (@_io.read_bytes_term(0, false, true, true)).force_encoding("UTF-8")
_ = @file_name
raise Kaitai::Struct::ValidationExprError.new(@file_name, @_io, "/types/atch_chunk/seq/0") if not ((_.size != 0) && (_[0...1] != "."))
@compression = Kaitai::Struct::Stream::resolve_enum(COMPRESSION_ATTACH_METHODS, @_io.read_u1)
raise Kaitai::Struct::ValidationNotAnyOfError.new(@compression, @_io, "/types/atch_chunk/seq/1") if not ((@compression == :compression_attach_methods_none) || (@compression == :compression_attach_methods_zlib))
if compression == :compression_attach_methods_none
@data_plain = @_io.read_bytes_full
end
if compression == :compression_attach_methods_zlib
@_raw_data_zlib = @_io.read_bytes_full
@data_zlib = Zlib::Inflate.inflate(@_raw_data_zlib)
end
self
end
def data
return @data unless @data.nil?
@data = (compression == :compression_attach_methods_none ? data_plain : data_zlib)
@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.
attr_reader :file_name
attr_reader :compression
attr_reader :data_plain
attr_reader :data_zlib
attr_reader :_raw_data_zlib
end
##
# Background chunk stores default background color to display this
# image against. Contents depend on `color_type` of the image.
# @see https://www.w3.org/TR/png/#11bKGD Source
class BkgdChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
case _root.ihdr.color_type
when :color_type_greyscale
@bkgd = BkgdGreyscale.new(@_io, self, @_root)
when :color_type_greyscale_alpha
@bkgd = BkgdGreyscale.new(@_io, self, @_root)
when :color_type_indexed
@bkgd = BkgdIndexed.new(@_io, self, @_root)
when :color_type_truecolor
@bkgd = BkgdTruecolor.new(@_io, self, @_root)
when :color_type_truecolor_alpha
@bkgd = BkgdTruecolor.new(@_io, self, @_root)
end
self
end
attr_reader :bkgd
end
##
# Background chunk for greyscale images.
class BkgdGreyscale < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@value = @_io.read_u2be
self
end
attr_reader :value
end
##
# Background chunk for images with indexed palette.
class BkgdIndexed < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@palette_index = @_io.read_u1
self
end
attr_reader :palette_index
end
##
# Background chunk for truecolor images.
class BkgdTruecolor < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@red = @_io.read_u2be
@green = @_io.read_u2be
@blue = @_io.read_u2be
self
end
attr_reader :red
attr_reader :green
attr_reader :blue
end
##
# @see https://www.w3.org/TR/png/#11cHRM Source
class ChrmChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@white_point = Point.new(@_io, self, @_root)
@red = Point.new(@_io, self, @_root)
@green = Point.new(@_io, self, @_root)
@blue = Point.new(@_io, self, @_root)
self
end
attr_reader :white_point
attr_reader :red
attr_reader :green
attr_reader :blue
end
class Chunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@len = @_io.read_u4be
@type = (@_io.read_bytes(4)).force_encoding("UTF-8")
_ = @type
raise Kaitai::Struct::ValidationExprError.new(@type, @_io, "/types/chunk/seq/1") if not type != "\000\000\000\000"
case type
when "PLTE"
_io_body = @_io.substream(len)
@body = PlteChunk.new(_io_body, self, @_root)
when "acTL"
_io_body = @_io.substream(len)
@body = AnimationControlChunk.new(_io_body, self, @_root)
when "atCh"
_io_body = @_io.substream(len)
@body = AtchChunk.new(_io_body, self, @_root)
when "bKGD"
_io_body = @_io.substream(len)
@body = BkgdChunk.new(_io_body, self, @_root)
when "cHRM"
_io_body = @_io.substream(len)
@body = ChrmChunk.new(_io_body, self, @_root)
when "fcTL"
_io_body = @_io.substream(len)
@body = FrameControlChunk.new(_io_body, self, @_root)
when "fdAT"
_io_body = @_io.substream(len)
@body = FrameDataChunk.new(_io_body, self, @_root)
when "gAMA"
_io_body = @_io.substream(len)
@body = GamaChunk.new(_io_body, self, @_root)
when "iTXt"
_io_body = @_io.substream(len)
@body = InternationalTextChunk.new(_io_body, self, @_root)
when "mkBS"
_io_body = @_io.substream(len)
@body = AdobeFireworksChunk.new(_io_body, self, @_root)
when "mkTS"
_io_body = @_io.substream(len)
@body = AdobeFireworksChunk.new(_io_body, self, @_root)
when "pHYs"
_io_body = @_io.substream(len)
@body = PhysChunk.new(_io_body, self, @_root)
when "prVW"
_io_body = @_io.substream(len)
@body = AdobeFireworksChunk.new(_io_body, self, @_root)
when "sRGB"
_io_body = @_io.substream(len)
@body = SrgbChunk.new(_io_body, self, @_root)
when "skMf"
_io_body = @_io.substream(len)
@body = EvernoteSkmfChunk.new(_io_body, self, @_root)
when "skRf"
_io_body = @_io.substream(len)
@body = EvernoteSkrfChunk.new(_io_body, self, @_root)
when "tEXt"
_io_body = @_io.substream(len)
@body = TextChunk.new(_io_body, self, @_root)
when "tIME"
_io_body = @_io.substream(len)
@body = TimeChunk.new(_io_body, self, @_root)
when "zTXt"
_io_body = @_io.substream(len)
@body = CompressedTextChunk.new(_io_body, self, @_root)
else
@body = @_io.read_bytes(len)
end
@crc = @_io.read_bytes(4)
self
end
attr_reader :len
attr_reader :type
attr_reader :body
attr_reader :crc
attr_reader :_raw_body
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 https://www.w3.org/TR/png/#11zTXt Source
class CompressedTextChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@keyword = (@_io.read_bytes_term(0, false, true, true)).force_encoding("UTF-8")
@compression_method = Kaitai::Struct::Stream::resolve_enum(Png::COMPRESSION_METHODS, @_io.read_u1)
@_raw_text_datastream = @_io.read_bytes_full
@text_datastream = Zlib::Inflate.inflate(@_raw_text_datastream)
self
end
##
# Indicates purpose of the following text data.
attr_reader :keyword
attr_reader :compression_method
attr_reader :text_datastream
attr_reader :_raw_text_datastream
end
##
# @see 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
class EvernoteSkmfChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@json = (@_io.read_bytes_full).force_encoding("UTF-8")
self
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).
attr_reader :json
end
##
# @see 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
class EvernoteSkrfChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@uuid = @_io.read_bytes(16)
@orig_img = @_io.read_bytes_full
self
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`.
attr_reader :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.
attr_reader :orig_img
end
##
# @see https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk Source
class FrameControlChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@sequence_number = @_io.read_u4be
@width = @_io.read_u4be
raise Kaitai::Struct::ValidationLessThanError.new(1, @width, @_io, "/types/frame_control_chunk/seq/1") if not @width >= 1
raise Kaitai::Struct::ValidationGreaterThanError.new(_root.ihdr.width, @width, @_io, "/types/frame_control_chunk/seq/1") if not @width <= _root.ihdr.width
@height = @_io.read_u4be
raise Kaitai::Struct::ValidationLessThanError.new(1, @height, @_io, "/types/frame_control_chunk/seq/2") if not @height >= 1
raise Kaitai::Struct::ValidationGreaterThanError.new(_root.ihdr.height, @height, @_io, "/types/frame_control_chunk/seq/2") if not @height <= _root.ihdr.height
@x_offset = @_io.read_u4be
raise Kaitai::Struct::ValidationGreaterThanError.new(_root.ihdr.width - width, @x_offset, @_io, "/types/frame_control_chunk/seq/3") if not @x_offset <= _root.ihdr.width - width
@y_offset = @_io.read_u4be
raise Kaitai::Struct::ValidationGreaterThanError.new(_root.ihdr.height - height, @y_offset, @_io, "/types/frame_control_chunk/seq/4") if not @y_offset <= _root.ihdr.height - height
@delay_num = @_io.read_u2be
@delay_den = @_io.read_u2be
@dispose_op = Kaitai::Struct::Stream::resolve_enum(Png::DISPOSE_OP_VALUES, @_io.read_u1)
@blend_op = Kaitai::Struct::Stream::resolve_enum(Png::BLEND_OP_VALUES, @_io.read_u1)
self
end
##
# Time to display this frame, in seconds
def delay
return @delay unless @delay.nil?
@delay = delay_num / (delay_den == 0 ? 100.0 : delay_den)
@delay
end
##
# Sequence number of the animation chunk
attr_reader :sequence_number
##
# Width of the following frame
attr_reader :width
##
# Height of the following frame
attr_reader :height
##
# X position at which to render the following frame
attr_reader :x_offset
##
# Y position at which to render the following frame
attr_reader :y_offset
##
# Frame delay fraction numerator
attr_reader :delay_num
##
# Frame delay fraction denominator
attr_reader :delay_den
##
# Type of frame area disposal to be done after rendering this frame
attr_reader :dispose_op
##
# Type of frame area rendering for this frame
attr_reader :blend_op
end
##
# @see https://wiki.mozilla.org/APNG_Specification#.60fdAT.60:_The_Frame_Data_Chunk Source
class FrameDataChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@sequence_number = @_io.read_u4be
@frame_data = @_io.read_bytes_full
self
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.
attr_reader :sequence_number
##
# 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.
attr_reader :frame_data
end
##
# @see https://www.w3.org/TR/png/#11gAMA Source
class GamaChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@gamma_int = @_io.read_u4be
self
end
def gamma_ratio
return @gamma_ratio unless @gamma_ratio.nil?
@gamma_ratio = 100000.0 / gamma_int
@gamma_ratio
end
attr_reader :gamma_int
end
##
# @see https://www.w3.org/TR/png/#11IHDR Source
class IhdrChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@width = @_io.read_u4be
raise Kaitai::Struct::ValidationLessThanError.new(1, @width, @_io, "/types/ihdr_chunk/seq/0") if not @width >= 1
@height = @_io.read_u4be
raise Kaitai::Struct::ValidationLessThanError.new(1, @height, @_io, "/types/ihdr_chunk/seq/1") if not @height >= 1
@bit_depth = @_io.read_u1
@color_type = Kaitai::Struct::Stream::resolve_enum(Png::COLOR_TYPE, @_io.read_u1)
@compression_method = @_io.read_u1
@filter_method = @_io.read_u1
@interlace_method = @_io.read_u1
self
end
attr_reader :width
attr_reader :height
attr_reader :bit_depth
attr_reader :color_type
attr_reader :compression_method
attr_reader :filter_method
attr_reader :interlace_method
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 https://www.w3.org/TR/png/#11iTXt Source
class InternationalTextChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@keyword = (@_io.read_bytes_term(0, false, true, true)).force_encoding("UTF-8")
@compression_flag = @_io.read_u1
@compression_method = Kaitai::Struct::Stream::resolve_enum(Png::COMPRESSION_METHODS, @_io.read_u1)
@language_tag = (@_io.read_bytes_term(0, false, true, true)).force_encoding("ASCII").encode('UTF-8')
@translated_keyword = (@_io.read_bytes_term(0, false, true, true)).force_encoding("UTF-8")
@text = (@_io.read_bytes_full).force_encoding("UTF-8")
self
end
##
# Indicates purpose of the following text data.
attr_reader :keyword
##
# 0 = text is uncompressed, 1 = text is compressed with a
# method specified in `compression_method`.
attr_reader :compression_flag
attr_reader :compression_method
##
# Human language used in `translated_keyword` and `text`
# attributes - should be a language code conforming to ISO
# 646.IRV:1991.
attr_reader :language_tag
##
# Keyword translated into language specified in
# `language_tag`. Line breaks are not allowed.
attr_reader :translated_keyword
##
# Text contents ("value" of this key-value pair), written in
# language specified in `language_tag`. Line breaks are
# allowed.
attr_reader :text
end
##
# "Physical size" chunk stores data that allows to translate
# logical pixels into physical units (meters, etc) and vice-versa.
# @see https://www.w3.org/TR/png/#11pHYs Source
class PhysChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@pixels_per_unit_x = @_io.read_u4be
@pixels_per_unit_y = @_io.read_u4be
@unit = Kaitai::Struct::Stream::resolve_enum(Png::PHYS_UNIT, @_io.read_u1)
self
end
##
# Number of pixels per physical unit (typically, 1 meter) by X
# axis.
attr_reader :pixels_per_unit_x
##
# Number of pixels per physical unit (typically, 1 meter) by Y
# axis.
attr_reader :pixels_per_unit_y
attr_reader :unit
end
##
# @see https://www.w3.org/TR/png/#11PLTE Source
class PlteChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@entries = []
i = 0
while not @_io.eof?
@entries << Rgb.new(@_io, self, @_root)
i += 1
end
self
end
attr_reader :entries
end
class Point < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@x_int = @_io.read_u4be
@y_int = @_io.read_u4be
self
end
def x
return @x unless @x.nil?
@x = x_int / 100000.0
@x
end
def y
return @y unless @y.nil?
@y = y_int / 100000.0
@y
end
attr_reader :x_int
attr_reader :y_int
end
class Rgb < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@r = @_io.read_u1
@g = @_io.read_u1
@b = @_io.read_u1
self
end
attr_reader :r
attr_reader :g
attr_reader :b
end
##
# @see https://www.w3.org/TR/png/#11sRGB Source
class SrgbChunk < Kaitai::Struct::Struct
INTENT = {
0 => :intent_perceptual,
1 => :intent_relative_colorimetric,
2 => :intent_saturation,
3 => :intent_absolute_colorimetric,
}
I__INTENT = INTENT.invert
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@render_intent = Kaitai::Struct::Stream::resolve_enum(INTENT, @_io.read_u1)
self
end
attr_reader :render_intent
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 https://www.w3.org/TR/png/#11tEXt Source
class TextChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@keyword = (@_io.read_bytes_term(0, false, true, true)).force_encoding("ISO-8859-1").encode('UTF-8')
@text = (@_io.read_bytes_full).force_encoding("ISO-8859-1").encode('UTF-8')
self
end
##
# Indicates purpose of the following text data.
attr_reader :keyword
attr_reader :text
end
##
# Time chunk stores time stamp of last modification of this image,
# up to 1 second precision in UTC timezone.
# @see https://www.w3.org/TR/png/#11tIME Source
class TimeChunk < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = nil)
super(_io, _parent, _root)
_read
end
def _read
@year = @_io.read_u2be
@month = @_io.read_u1
@day = @_io.read_u1
@hour = @_io.read_u1
@minute = @_io.read_u1
@second = @_io.read_u1
self
end
attr_reader :year
attr_reader :month
attr_reader :day
attr_reader :hour
attr_reader :minute
attr_reader :second
end
attr_reader :magic
attr_reader :ihdr_len
attr_reader :ihdr_type
attr_reader :ihdr
attr_reader :ihdr_crc
attr_reader :chunks
end