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