Microsoft Windows uses specific file format to store applications icons - ICO. This is a container that contains one or more image files (effectively, DIB parts of BMP files or full PNG files are contained inside).
This page hosts a formal specification of Microsoft Windows icon file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.
The Ruby runtime library can be installed from RubyGems:
gem install kaitai-struct
Parse a local file and get structure in memory:
data = Ico.from_file("path/to/local/file.ico")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = Ico.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.num_images # => Number of images contained in this file
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
require 'kaitai/struct/struct'
unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end
##
# Microsoft Windows uses specific file format to store applications
# icons - ICO. This is a container that contains one or more image
# files (effectively, DIB parts of BMP files or full PNG files are
# contained inside).
# @see https://learn.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10) Source
class Ico < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@magic = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 1, 0].pack('C*'), magic, _io, "/seq/0") if not magic == [0, 0, 1, 0].pack('C*')
@num_images = @_io.read_u2le
@images = []
(num_images).times { |i|
@images << IconDirEntry.new(@_io, self, @_root)
}
self
end
class IconDirEntry < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@width = @_io.read_u1
@height = @_io.read_u1
@num_colors = @_io.read_u1
@reserved = @_io.read_bytes(1)
raise Kaitai::Struct::ValidationNotEqualError.new([0].pack('C*'), reserved, _io, "/types/icon_dir_entry/seq/3") if not reserved == [0].pack('C*')
@num_planes = @_io.read_u2le
@bpp = @_io.read_u2le
@len_img = @_io.read_u4le
@ofs_img = @_io.read_u4le
self
end
##
# Raw image data. Use `is_png` to determine whether this is an
# embedded PNG file (true) or a DIB bitmap (false) and call a
# relevant parser, if needed to parse image data further.
def img
return @img unless @img.nil?
_pos = @_io.pos
@_io.seek(ofs_img)
@img = @_io.read_bytes(len_img)
@_io.seek(_pos)
@img
end
##
# Pre-reads first 8 bytes of the image to determine if it's an
# embedded PNG file.
def png_header
return @png_header unless @png_header.nil?
_pos = @_io.pos
@_io.seek(ofs_img)
@png_header = @_io.read_bytes(8)
@_io.seek(_pos)
@png_header
end
##
# True if this image is in PNG format.
def is_png
return @is_png unless @is_png.nil?
@is_png = png_header == [137, 80, 78, 71, 13, 10, 26, 10].pack('C*')
@is_png
end
##
# Width of image, px
attr_reader :width
##
# Height of image, px
attr_reader :height
##
# Number of colors in palette of the image or 0 if image has
# no palette (i.e. RGB, RGBA, etc)
attr_reader :num_colors
attr_reader :reserved
##
# Number of color planes
attr_reader :num_planes
##
# Bits per pixel in the image
attr_reader :bpp
##
# Size of the image data
attr_reader :len_img
##
# Absolute offset of the image data start in the file
attr_reader :ofs_img
end
attr_reader :magic
##
# Number of images contained in this file
attr_reader :num_images
attr_reader :images
end