This page hosts a formal specification of EDID (VESA Enhanced Extended Display Identification Data) 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 = Edid.from_file("path/to/local/file.bin")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = Edid.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.product_code # => Manufacturer product code
# 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
class Edid < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@magic = @_io.read_bytes(8)
raise Kaitai::Struct::ValidationNotEqualError.new([0, 255, 255, 255, 255, 255, 255, 0].pack('C*'), magic, _io, "/seq/0") if not magic == [0, 255, 255, 255, 255, 255, 255, 0].pack('C*')
@mfg_bytes = @_io.read_u2be
@product_code = @_io.read_u2le
@serial = @_io.read_u4le
@mfg_week = @_io.read_u1
@mfg_year_mod = @_io.read_u1
@edid_version_major = @_io.read_u1
@edid_version_minor = @_io.read_u1
@input_flags = @_io.read_u1
@screen_size_h = @_io.read_u1
@screen_size_v = @_io.read_u1
@gamma_mod = @_io.read_u1
@features_flags = @_io.read_u1
@chromacity = ChromacityInfo.new(@_io, self, @_root)
@est_timings = EstTimingsInfo.new(@_io, self, @_root)
@_raw_std_timings = []
@std_timings = []
(8).times { |i|
@_raw_std_timings << @_io.read_bytes(2)
_io__raw_std_timings = Kaitai::Struct::Stream.new(@_raw_std_timings[i])
@std_timings << StdTiming.new(_io__raw_std_timings, self, @_root)
}
self
end
##
# Chromaticity information: colorimetry and white point
# coordinates. All coordinates are stored as fixed precision
# 10-bit numbers, bits are shuffled for compactness.
class ChromacityInfo < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@red_x_1_0 = @_io.read_bits_int_be(2)
@red_y_1_0 = @_io.read_bits_int_be(2)
@green_x_1_0 = @_io.read_bits_int_be(2)
@green_y_1_0 = @_io.read_bits_int_be(2)
@blue_x_1_0 = @_io.read_bits_int_be(2)
@blue_y_1_0 = @_io.read_bits_int_be(2)
@white_x_1_0 = @_io.read_bits_int_be(2)
@white_y_1_0 = @_io.read_bits_int_be(2)
@_io.align_to_byte
@red_x_9_2 = @_io.read_u1
@red_y_9_2 = @_io.read_u1
@green_x_9_2 = @_io.read_u1
@green_y_9_2 = @_io.read_u1
@blue_x_9_2 = @_io.read_u1
@blue_y_9_2 = @_io.read_u1
@white_x_9_2 = @_io.read_u1
@white_y_9_2 = @_io.read_u1
self
end
def green_x_int
return @green_x_int unless @green_x_int.nil?
@green_x_int = ((green_x_9_2 << 2) | green_x_1_0)
@green_x_int
end
##
# Red Y coordinate
def red_y
return @red_y unless @red_y.nil?
@red_y = (red_y_int / 1024.0)
@red_y
end
def green_y_int
return @green_y_int unless @green_y_int.nil?
@green_y_int = ((green_y_9_2 << 2) | green_y_1_0)
@green_y_int
end
##
# White Y coordinate
def white_y
return @white_y unless @white_y.nil?
@white_y = (white_y_int / 1024.0)
@white_y
end
##
# Red X coordinate
def red_x
return @red_x unless @red_x.nil?
@red_x = (red_x_int / 1024.0)
@red_x
end
##
# White X coordinate
def white_x
return @white_x unless @white_x.nil?
@white_x = (white_x_int / 1024.0)
@white_x
end
##
# Blue X coordinate
def blue_x
return @blue_x unless @blue_x.nil?
@blue_x = (blue_x_int / 1024.0)
@blue_x
end
def white_x_int
return @white_x_int unless @white_x_int.nil?
@white_x_int = ((white_x_9_2 << 2) | white_x_1_0)
@white_x_int
end
def white_y_int
return @white_y_int unless @white_y_int.nil?
@white_y_int = ((white_y_9_2 << 2) | white_y_1_0)
@white_y_int
end
##
# Green X coordinate
def green_x
return @green_x unless @green_x.nil?
@green_x = (green_x_int / 1024.0)
@green_x
end
def red_x_int
return @red_x_int unless @red_x_int.nil?
@red_x_int = ((red_x_9_2 << 2) | red_x_1_0)
@red_x_int
end
def red_y_int
return @red_y_int unless @red_y_int.nil?
@red_y_int = ((red_y_9_2 << 2) | red_y_1_0)
@red_y_int
end
def blue_x_int
return @blue_x_int unless @blue_x_int.nil?
@blue_x_int = ((blue_x_9_2 << 2) | blue_x_1_0)
@blue_x_int
end
##
# Blue Y coordinate
def blue_y
return @blue_y unless @blue_y.nil?
@blue_y = (blue_y_int / 1024.0)
@blue_y
end
##
# Green Y coordinate
def green_y
return @green_y unless @green_y.nil?
@green_y = (green_y_int / 1024.0)
@green_y
end
def blue_y_int
return @blue_y_int unless @blue_y_int.nil?
@blue_y_int = ((blue_y_9_2 << 2) | blue_y_1_0)
@blue_y_int
end
##
# Red X, bits 1..0
attr_reader :red_x_1_0
##
# Red Y, bits 1..0
attr_reader :red_y_1_0
##
# Green X, bits 1..0
attr_reader :green_x_1_0
##
# Green Y, bits 1..0
attr_reader :green_y_1_0
##
# Blue X, bits 1..0
attr_reader :blue_x_1_0
##
# Blue Y, bits 1..0
attr_reader :blue_y_1_0
##
# White X, bits 1..0
attr_reader :white_x_1_0
##
# White Y, bits 1..0
attr_reader :white_y_1_0
##
# Red X, bits 9..2
attr_reader :red_x_9_2
##
# Red Y, bits 9..2
attr_reader :red_y_9_2
##
# Green X, bits 9..2
attr_reader :green_x_9_2
##
# Green Y, bits 9..2
attr_reader :green_y_9_2
##
# Blue X, bits 9..2
attr_reader :blue_x_9_2
##
# Blue Y, bits 9..2
attr_reader :blue_y_9_2
##
# White X, bits 9..2
attr_reader :white_x_9_2
##
# White Y, bits 9..2
attr_reader :white_y_9_2
end
class EstTimingsInfo < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@can_720x400px_70hz = @_io.read_bits_int_be(1) != 0
@can_720x400px_88hz = @_io.read_bits_int_be(1) != 0
@can_640x480px_60hz = @_io.read_bits_int_be(1) != 0
@can_640x480px_67hz = @_io.read_bits_int_be(1) != 0
@can_640x480px_72hz = @_io.read_bits_int_be(1) != 0
@can_640x480px_75hz = @_io.read_bits_int_be(1) != 0
@can_800x600px_56hz = @_io.read_bits_int_be(1) != 0
@can_800x600px_60hz = @_io.read_bits_int_be(1) != 0
@can_800x600px_72hz = @_io.read_bits_int_be(1) != 0
@can_800x600px_75hz = @_io.read_bits_int_be(1) != 0
@can_832x624px_75hz = @_io.read_bits_int_be(1) != 0
@can_1024x768px_87hz_i = @_io.read_bits_int_be(1) != 0
@can_1024x768px_60hz = @_io.read_bits_int_be(1) != 0
@can_1024x768px_70hz = @_io.read_bits_int_be(1) != 0
@can_1024x768px_75hz = @_io.read_bits_int_be(1) != 0
@can_1280x1024px_75hz = @_io.read_bits_int_be(1) != 0
@can_1152x870px_75hz = @_io.read_bits_int_be(1) != 0
@reserved = @_io.read_bits_int_be(7)
self
end
##
# Supports 720 x 400 @ 70Hz
attr_reader :can_720x400px_70hz
##
# Supports 720 x 400 @ 88Hz
attr_reader :can_720x400px_88hz
##
# Supports 640 x 480 @ 60Hz
attr_reader :can_640x480px_60hz
##
# Supports 640 x 480 @ 67Hz
attr_reader :can_640x480px_67hz
##
# Supports 640 x 480 @ 72Hz
attr_reader :can_640x480px_72hz
##
# Supports 640 x 480 @ 75Hz
attr_reader :can_640x480px_75hz
##
# Supports 800 x 600 @ 56Hz
attr_reader :can_800x600px_56hz
##
# Supports 800 x 600 @ 60Hz
attr_reader :can_800x600px_60hz
##
# Supports 800 x 600 @ 72Hz
attr_reader :can_800x600px_72hz
##
# Supports 800 x 600 @ 75Hz
attr_reader :can_800x600px_75hz
##
# Supports 832 x 624 @ 75Hz
attr_reader :can_832x624px_75hz
##
# Supports 1024 x 768 @ 87Hz(I)
attr_reader :can_1024x768px_87hz_i
##
# Supports 1024 x 768 @ 60Hz
attr_reader :can_1024x768px_60hz
##
# Supports 1024 x 768 @ 70Hz
attr_reader :can_1024x768px_70hz
##
# Supports 1024 x 768 @ 75Hz
attr_reader :can_1024x768px_75hz
##
# Supports 1280 x 1024 @ 75Hz
attr_reader :can_1280x1024px_75hz
##
# Supports 1152 x 870 @ 75Hz
attr_reader :can_1152x870px_75hz
attr_reader :reserved
end
class StdTiming < Kaitai::Struct::Struct
ASPECT_RATIOS = {
0 => :aspect_ratios_ratio_16_10,
1 => :aspect_ratios_ratio_4_3,
2 => :aspect_ratios_ratio_5_4,
3 => :aspect_ratios_ratio_16_9,
}
I__ASPECT_RATIOS = ASPECT_RATIOS.invert
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@horiz_active_pixels_mod = @_io.read_u1
@aspect_ratio = Kaitai::Struct::Stream::resolve_enum(ASPECT_RATIOS, @_io.read_bits_int_be(2))
@refresh_rate_mod = @_io.read_bits_int_be(6)
self
end
def bytes_lookahead
return @bytes_lookahead unless @bytes_lookahead.nil?
_pos = @_io.pos
@_io.seek(0)
@bytes_lookahead = @_io.read_bytes(2)
@_io.seek(_pos)
@bytes_lookahead
end
def is_used
return @is_used unless @is_used.nil?
@is_used = bytes_lookahead != [1, 1].pack('C*')
@is_used
end
##
# Range of horizontal active pixels.
def horiz_active_pixels
return @horiz_active_pixels unless @horiz_active_pixels.nil?
if is_used
@horiz_active_pixels = ((horiz_active_pixels_mod + 31) * 8)
end
@horiz_active_pixels
end
##
# Vertical refresh rate, Hz.
def refresh_rate
return @refresh_rate unless @refresh_rate.nil?
if is_used
@refresh_rate = (refresh_rate_mod + 60)
end
@refresh_rate
end
##
# Range of horizontal active pixels, written in modified form:
# `(horiz_active_pixels / 8) - 31`. This yields an effective
# range of 256..2288, with steps of 8 pixels.
attr_reader :horiz_active_pixels_mod
##
# Aspect ratio of the image. Can be used to calculate number
# of vertical pixels.
attr_reader :aspect_ratio
##
# Refresh rate in Hz, written in modified form: `refresh_rate
# - 60`. This yields an effective range of 60..123 Hz.
attr_reader :refresh_rate_mod
end
def mfg_year
return @mfg_year unless @mfg_year.nil?
@mfg_year = (mfg_year_mod + 1990)
@mfg_year
end
def mfg_id_ch1
return @mfg_id_ch1 unless @mfg_id_ch1.nil?
@mfg_id_ch1 = ((mfg_bytes & 31744) >> 10)
@mfg_id_ch1
end
def mfg_id_ch3
return @mfg_id_ch3 unless @mfg_id_ch3.nil?
@mfg_id_ch3 = (mfg_bytes & 31)
@mfg_id_ch3
end
def gamma
return @gamma unless @gamma.nil?
if gamma_mod != 255
@gamma = ((gamma_mod + 100) / 100.0)
end
@gamma
end
def mfg_str
return @mfg_str unless @mfg_str.nil?
@mfg_str = ([(mfg_id_ch1 + 64), (mfg_id_ch2 + 64), (mfg_id_ch3 + 64)].pack('C*')).force_encoding("ASCII")
@mfg_str
end
def mfg_id_ch2
return @mfg_id_ch2 unless @mfg_id_ch2.nil?
@mfg_id_ch2 = ((mfg_bytes & 992) >> 5)
@mfg_id_ch2
end
attr_reader :magic
attr_reader :mfg_bytes
##
# Manufacturer product code
attr_reader :product_code
##
# Serial number
attr_reader :serial
##
# Week of manufacture. Week numbering is not consistent between manufacturers.
attr_reader :mfg_week
##
# Year of manufacture, less 1990. (1990-2245). If week=255, it is the model year instead.
attr_reader :mfg_year_mod
##
# EDID version, usually 1 (for 1.3)
attr_reader :edid_version_major
##
# EDID revision, usually 3 (for 1.3)
attr_reader :edid_version_minor
attr_reader :input_flags
##
# Maximum horizontal image size, in centimetres (max 292 cm/115 in at 16:9 aspect ratio)
attr_reader :screen_size_h
##
# Maximum vertical image size, in centimetres. If either byte is 0, undefined (e.g. projector)
attr_reader :screen_size_v
##
# Display gamma, datavalue = (gamma*100)-100 (range 1.00-3.54)
attr_reader :gamma_mod
attr_reader :features_flags
##
# Phosphor or filter chromaticity structure, which provides info on colorimetry and white point
# @see '' Standard, section 3.7
attr_reader :chromacity
##
# Block of bit flags that indicates support of so called
# "established timings", which is a commonly used subset of VESA
# DMT (Discrete Monitor Timings) modes.
# @see '' Standard, section 3.8
attr_reader :est_timings
##
# Array of descriptions of so called "standard timings", which are
# used to specify up to 8 additional timings not included in
# "established timings".
attr_reader :std_timings
attr_reader :_raw_std_timings
end