lib/tiff.rb in exifr-0.10.6 vs lib/tiff.rb in exifr-0.10.7
- old
+ new
@@ -1,6 +1,6 @@
-# Copyright (c) 2007 - R.W. van 't Veer
+# Copyright (c) 2007, 2008 - R.W. van 't Veer
require 'rational'
module EXIFR
# = TIFF decoder
@@ -34,14 +34,14 @@
# EXIFR::TIFF.new('DSC_0218.TIF').date_time # => Tue May 23 19:15:32 +0200 2006
# EXIFR::TIFF.new('DSC_0218.TIF').exposure_time # => Rational(1, 100)
# EXIFR::TIFF.new('DSC_0218.TIF').orientation # => EXIFR::TIFF::Orientation
class TIFF
include Enumerable
-
+
# JPEG thumbnails
attr_reader :jpeg_thumbnails
-
+
TAG_MAPPING = {} # :nodoc:
TAG_MAPPING.merge!({
:image => {
0x00FE => :new_subfile_type,
0x00FF => :subfile_type,
@@ -138,11 +138,11 @@
0x83bb => :iptc,
0x8769 => :exif,
0x8825 => :gps,
},
-
+
:exif => {
0x829a => :exposure_time,
0x829d => :f_number,
0x8822 => :exposure_program,
0x8824 => :spectral_sensitivity,
@@ -197,11 +197,11 @@
0xa40a => :sharpness,
0xa40b => :device_setting_description,
0xa40c => :subject_distance_range,
0xa420 => :image_unique_id
},
-
+
:gps => {
0x0000 => :gps_version_id,
0x0001 => :gps_latitude_ref,
0x0002 => :gps_latitude,
0x0003 => :gps_longitude_ref,
@@ -233,30 +233,30 @@
0x001d => :gps_date_stamp,
0x001e => :gps_differential,
},
})
IFD_TAGS = [:image, :exif, :gps] # :nodoc:
-
+
time_proc = proc do |value|
if value =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
Time.mktime($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i) rescue nil
else
value
end
end
-
+
# The orientation of the image with respect to the rows and columns.
class Orientation
def initialize(value, type) # :nodoc:
@value, @type = value, type
end
-
+
# Field value.
def to_i
@value
end
-
+
# Rotate and/or flip for proper viewing.
def transform_rmagick(img)
case @type
when :TopRight ; img.flop
when :BottomRight ; img.rotate(180)
@@ -267,16 +267,16 @@
when :LeftBottom ; img.rotate(270)
else
img
end
end
-
+
def ==(other) # :nodoc:
Orientation === other && to_i == other.to_i
end
end
-
+
ORIENTATIONS = [] # :nodoc:
[
nil,
:TopLeft,
:TopRight,
@@ -288,96 +288,116 @@
:LeftBottom,
].each_with_index do |type,index|
next unless type
const_set("#{type}Orientation", ORIENTATIONS[index] = Orientation.new(index, type))
end
-
+
ADAPTERS = Hash.new { proc { |v| v } } # :nodoc:
ADAPTERS.merge!({
:date_time_original => time_proc,
:date_time_digitized => time_proc,
:date_time => time_proc,
:orientation => proc { |v| ORIENTATIONS[v] }
})
-
+
# Names for all recognized TIFF fields.
- TAGS = [TAG_MAPPING.keys, TAG_MAPPING.values.map{|a|a.values}].flatten.uniq - IFD_TAGS
-
+ TAGS = ([TAG_MAPPING.keys, TAG_MAPPING.values.map{|v|v.values}].flatten.uniq - IFD_TAGS).map{|v|v.to_s}
+
# +file+ is a filename or an IO object.
def initialize(file)
data = file.respond_to?(:read) ? file.read : File.open(file, 'rb') { |io| io.read }
-
+
class << data
attr_accessor :short, :long
def readshort(pos); self[pos..(pos + 1)].unpack(@short)[0]; end
def readlong(pos); self[pos..(pos + 3)].unpack(@long)[0]; end
end
-
+
case data[0..1]
when 'II'; data.short, data.long = 'v', 'V'
when 'MM'; data.short, data.long = 'n', 'N'
else; raise 'no II or MM marker found'
end
-
+
@ifds = [IFD.new(data)]
- while ifd = @ifds.last.next; @ifds << ifd; end
-
+ while ifd = @ifds.last.next
+ break if @ifds.find{|i| i.offset == ifd.offset}
+ @ifds << ifd
+ end
+
@jpeg_thumbnails = @ifds.map do |ifd|
if ifd.jpeg_interchange_format && ifd.jpeg_interchange_format_length
start, length = ifd.jpeg_interchange_format, ifd.jpeg_interchange_format_length
data[start..(start + length)]
end
end.compact
end
-
+
# Number of images.
def size
@ifds.size
end
-
+
# Yield for each image.
def each
@ifds.each { |ifd| yield ifd }
end
-
+
# Get +index+ image.
def [](index)
index.is_a?(Symbol) ? to_hash[index] : @ifds[index]
end
-
+
# Dispatch to first image.
def method_missing(method, *args)
super unless args.empty?
-
+
if @ifds.first.respond_to?(method)
@ifds.first.send(method)
- elsif TAGS.include?(method)
+ elsif TAGS.include?(method.to_s)
@ifds.first.to_hash[method]
else
super
end
end
-
+
+ def respond_to?(method) # :nodoc:
+ super ||
+ (@ifds && @ifds.first && @ifds.first.respond_to?(method)) ||
+ TAGS.include?(method.to_s)
+ end
+
+ def methods # :nodoc:
+ (super + TAGS + IFD.instance_methods(false)).uniq
+ end
+
+ class << self
+ alias instance_methods_without_tiff_extras instance_methods
+ def instance_methods(include_super = true) # :nodoc:
+ (instance_methods_without_tiff_extras(include_super) + TAGS + IFD.instance_methods(false)).uniq
+ end
+ end
+
# Convenience method to access image width.
def width; @ifds.first.width; end
# Convenience method to access image height.
def height; @ifds.first.height; end
-
+
# Get a hash presentation of the (first) image.
def to_hash; @ifds.first.to_hash; end
-
+
def inspect # :nodoc:
@ifds.inspect
end
-
+
class IFD # :nodoc:
- attr_reader :type, :fields
+ attr_reader :type, :fields, :offset
def initialize(data, offset = nil, type = :image)
@data, @offset, @type, @fields = data, offset, type, {}
-
+
pos = offset || @data.readlong(4)
num = @data.readshort(pos)
pos += 2
num.times do
@@ -385,19 +405,19 @@
pos += 12
end
@offset_next = @data.readlong(pos)
end
-
+
def method_missing(method, *args)
- super unless args.empty? && TAGS.include?(method)
+ super unless args.empty? && TAGS.include?(method.to_s)
to_hash[method]
end
-
+
def width; image_width; end
def height; image_length; end
-
+
def to_hash
@hash ||= begin
result = @fields.dup
result.delete_if { |key,value| value.nil? }
result.each do |key,value|
@@ -412,26 +432,26 @@
def inspect
to_hash.inspect
end
def next?
- @offset_next != 0 && @offset_next < @data.size && (@offset || 0) < @offset_next
+ @offset_next != 0 && @offset_next < @data.size
end
-
+
def next
IFD.new(@data, @offset_next) if next?
end
-
+
def to_yaml_properties
['@fields']
end
-
+
private
def add_field(field)
return unless tag = TAG_MAPPING[@type][field.tag]
return if @fields[tag]
-
+
if IFD_TAGS.include? tag
@fields[tag] = IFD.new(@data, field.offset, tag)
else
value = field.value.map { |v| ADAPTERS[tag][v] } if field.value
@fields[tag] = value.kind_of?(Array) && value.size == 1 ? value.first : value
@@ -442,11 +462,11 @@
class Field # :nodoc:
attr_reader :tag, :offset, :value
def initialize(data, pos)
@tag, count, @offset = data.readshort(pos), data.readlong(pos + 4), data.readlong(pos + 8)
-
+
case data.readshort(pos + 2)
when 1, 6 # byte, signed byte
# TODO handle signed bytes
len, pack = count, proc { |d| d }
when 2 # ascii
@@ -465,11 +485,11 @@
end
r.map do |f|
if f[1] == 0 # allow NaN and Infinity
f[0].to_f.quo(f[1])
else
- Rational.reduce(*f)
+ Rational.respond_to?(:reduce) ? Rational.reduce(*f) : f[0].quo(f[1])
end
end
end
end
@@ -478,6 +498,6 @@
@value = [pack[data[start..(start + len - 1)]]].flatten
end
end
end
end
-end
\ No newline at end of file
+end