Skip to content

Instantly share code, notes, and snippets.

@mirakui
Created September 17, 2012 02:53
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mirakui/3735311 to your computer and use it in GitHub Desktop.
Extract width and height from JPEG format
module JpegMarkers
MARKERS = Hash[*%W(
SOF0 \xff\xc0
SOF1 \xff\xc1
SOF2 \xff\xc2
SOF3 \xff\xc3
DHT \xff\xc4
SOF5 \xff\xc5
SOF6 \xff\xc6
SOF7 \xff\xc7
JPG \xff\xc8
SOF9 \xff\xc9
SOF10 \xff\xca
SOF11 \xff\xcb
DAC \xff\xcc
SOF13 \xff\xcd
SOF14 \xff\xce
SOF15 \xff\xcf
RST0 \xff\xd0
RST1 \xff\xd1
RST2 \xff\xd2
RST3 \xff\xd3
RST4 \xff\xd4
RST5 \xff\xd5
RST6 \xff\xd6
RST7 \xff\xd7
SOI \xff\xd8
EOI \xff\xd9
SOS \xff\xda
DQT \xff\xdb
DNL \xff\xdc
DRI \xff\xdd
DHP \xff\xde
EXP \xff\xdf
APP0 \xff\xe0
APP1 \xff\xe1
APP2 \xff\xe2
APP3 \xff\xe3
APP4 \xff\xe4
APP5 \xff\xe5
APP6 \xff\xe6
APP7 \xff\xe7
APP8 \xff\xe8
APP9 \xff\xe9
APP10 \xff\xea
APP11 \xff\xeb
APP12 \xff\xec
APP13 \xff\xed
APP14 \xff\xee
APP15 \xff\xef
JPG0 \xff\xf0
JPG1 \xff\xf1
JPG2 \xff\xf2
JPG3 \xff\xf3
JPG4 \xff\xf4
JPG5 \xff\xf5
JPG6 \xff\xf6
JPG7 \xff\xf7
JPG8 \xff\xf8
JPG9 \xff\xf9
JPG10 \xff\xfa
JPG11 \xff\xfb
JPG12 \xff\xfc
JPG13 \xff\xfd
COM \xff\xfe
TEM \xff\x01
RES1 \xff\x02
RES2 \xff\xbf
)]
MARKERS.each do |k,v|
const_set k, v
end
def marker?(bytes)
MARKERS.values.include? bytes
end
def sof?(bytes)
(0..15).any? {|i| MARKERS["SOF#{i}"] == bytes }
end
def inspect_marker(chr)
key = MARKERS.key(chr)
if key
"#{key}(#{chr.unpack('n').first.to_s(16)})"
else
chr.inspect
end
end
end
require_relative './jpeg_markers.rb'
class JpegParser
include JpegMarkers
attr_reader :data
class ParseError < Exception; end
def initialize(io)
@io = io
@context_stack = []
@data = {}
end
def parse
read 2
assert SOI
parse_frame
@data
end
def parse_frame
read 2
parse_extra
parse_frame_header
end
def parse_extra
while marker?(current) && !sof?(current)
context "#{inspect_marker(current)}" do
length = read_int(2) - 2
log "length: #{length}"
skip length
end
read 2
end
end
def parse_frame_header
assert true, sof?(current)
context "frame_header" do
length = read_int(2) - 2
log "length: #{length}"
skip 1
@data[:height] = read_int 2
@data[:width] = read_int 2
skip length - 5
end
end
def current
@current
end
def log(msg)
header = @context_stack.map {|c| "[#{c}]" }.join
puts [header, msg].join(' ')
end
def context(name)
@context_stack << name
yield
@context_stack.pop
end
def read_int(bytes)
read(bytes).unpack('n').first
end
def read(bytes)
@current = @io.read bytes
log "read(#{bytes}): #{inspect_marker(@current)}"
@current
end
def skip(bytes)
@io.read bytes
log "skip(#{bytes})"
end
def assert(expected, actual=nil)
actual ||= current
if expected != actual
msg = <<-END
Parse error at #{@io.pos} bytes:
got: #{inspect_marker(actual)}
expected: #{inspect_marker(expected)}
END
raise ParseError, msg
end
end
end
reader = JpegParser.new ARGF
data = reader.parse
p data
$ ruby jpeg_parser.rb input.jpg
read(2): SOI(ffd8)
read(2): APP0(ffe0)
[APP0(ffe0)] read(2): "\x00\x10"
[APP0(ffe0)] length: 14
[APP0(ffe0)] skip(14)
read(2): APP2(ffe2)
[APP2(ffe2)] read(2): "\x02@"
[APP2(ffe2)] length: 574
[APP2(ffe2)] skip(574)
read(2): APP1(ffe1)
[APP1(ffe1)] read(2): "\x00t"
[APP1(ffe1)] length: 114
[APP1(ffe1)] skip(114)
read(2): DQT(ffdb)
[DQT(ffdb)] read(2): "\x00C"
[DQT(ffdb)] length: 65
[DQT(ffdb)] skip(65)
read(2): DQT(ffdb)
[DQT(ffdb)] read(2): "\x00C"
[DQT(ffdb)] length: 65
[DQT(ffdb)] skip(65)
read(2): SOF0(ffc0)
[frame_header] read(2): "\x00\x11"
[frame_header] length: 15
[frame_header] skip(1)
[frame_header] read(2): "\x05\xAD"
[frame_header] read(2): "\x04>"
[frame_header] skip(10)
{:height=>1453, :width=>1086}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment