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