Permalink
Browse files

Add support for TIFF images

  • Loading branch information...
1 parent ca04bbc commit a977bead5e08e3f0f449b1340b3cdca2afb5877b @sstephenson committed Apr 25, 2012
View
@@ -2,7 +2,7 @@ Dimensions
==========
Dimensions is a pure Ruby library for reading the width, height and
-rotation angle of GIF, PNG and JPEG images.
+rotation angle of GIF, PNG, JPEG and TIFF images.
Use the `dimensions`, `width` and `height` methods on the `Dimensions`
module to measure image files:
View
@@ -3,6 +3,8 @@
require 'dimensions/jpeg_scanner'
require 'dimensions/reader'
require 'dimensions/scanner'
+require 'dimensions/tiff_scanner'
+require 'dimensions/tiff_scanning'
require 'dimensions/version'
# Extends an IO object with the `Dimensions::IO` module, which adds
@@ -1,7 +1,11 @@
require 'dimensions/scanner'
+require 'dimensions/tiff_scanning'
module Dimensions
class ExifScanner < Scanner
+ include TiffScanning
+
+ ORIENTATION_TAG = 0x0112
ORIENTATIONS = [
:top_left,
:top_right,
@@ -23,21 +27,9 @@ def initialize(data)
def scan
scan_header
- offset = read_long + 6
- skip_to(offset)
-
- # Note: This only checks the first IFD for orientation entries,
- # which seems to work fine in my (limited) testing but might not
- # play out in practice.
- entry_count = read_short
- entry_count.times do |i|
- skip_to(offset + 2 + (12 * i))
- tag = read_short
-
- if tag == 0x0112 # orientation
- advance(6)
- value = read_short
-
+ scan_ifd do |tag|
+ if tag == ORIENTATION_TAG
+ value = read_integer_value
if valid_orientation?(value)
@orientation = ORIENTATIONS[value - 1]
end
@@ -50,20 +42,5 @@ def scan
def valid_orientation?(value)
(1..ORIENTATIONS.length).include?(value)
end
-
- def scan_header
- advance(6)
- scan_endianness
- scan_tag_mark
- end
-
- def scan_endianness
- tag = [read_char, read_char]
- @big = tag == [0x4D, 0x4D]
- end
-
- def scan_tag_mark
- raise ScanError unless read_short == 0x002A
- end
end
end
@@ -51,14 +51,14 @@ def scan_start_of_frame
if length == (size * 3) + 8
@width, @height = width, height
else
- raise ScanError
+ raise_scan_error
end
end
def scan_app1_frame
frame = read_frame
if frame[0..5] == "Exif\000\000"
- scanner = ExifScanner.new(frame)
+ scanner = ExifScanner.new(frame[6..-1])
if scanner.scan
case scanner.orientation
when :bottom_right
View
@@ -2,9 +2,11 @@
module Dimensions
class Reader
- GIF_HEADER = [0x47, 0x49, 0x46, 0x38]
- PNG_HEADER = [0x89, 0x50, 0x4E, 0x47]
- JPEG_HEADER = [0xFF, 0xD8, 0xFF]
+ GIF_HEADER = [0x47, 0x49, 0x46, 0x38]
+ PNG_HEADER = [0x89, 0x50, 0x4E, 0x47]
+ JPEG_HEADER = [0xFF, 0xD8, 0xFF]
+ TIFF_HEADER_I = [0x49, 0x49, 0x2A, 0x00]
+ TIFF_HEADER_M = [0x4D, 0x4D, 0x00, 0x2A]
attr_reader :type, :width, :height, :angle
@@ -41,6 +43,8 @@ def determine_type
@type = :png
elsif match_header(JPEG_HEADER, bytes)
@type = :jpeg
+ elsif match_header(TIFF_HEADER_I, bytes) || match_header(TIFF_HEADER_M, bytes)
+ @type = :tiff
end
process @type ? :"extract_#{type}_dimensions" : nil
@@ -77,6 +81,16 @@ def extract_jpeg_dimensions
rescue JpegScanner::ScanError
end
+ def extract_tiff_dimensions
+ scanner = TiffScanner.new(@data)
+ if scanner.scan
+ @width = scanner.width
+ @height = scanner.height
+ process nil
+ end
+ rescue TiffScanner::ScanError
+ end
+
def match_header(header, bytes)
bytes[0, header.length] == header
end
View
@@ -2,12 +2,14 @@ module Dimensions
class Scanner
class ScanError < ::StandardError; end
+ attr_reader :pos
+
def initialize(data)
@data = data.dup
@data.force_encoding("BINARY") if @data.respond_to?(:force_encoding)
@size = @data.length
@pos = 0
- @big = true # endianness
+ big! # endianness
end
def read_char
@@ -35,12 +37,24 @@ def read_data(size)
def advance(length)
@pos += length
- raise ScanError if @pos > @size
+ raise_scan_error if @pos > @size
end
def skip_to(pos)
@pos = pos
- raise ScanError if @pos > @size
+ raise_scan_error if @pos > @size
+ end
+
+ def big!
+ @big = true
+ end
+
+ def little!
+ @big = false
+ end
+
+ def raise_scan_error
+ raise ScanError
end
end
end
@@ -0,0 +1,33 @@
+require 'dimensions/scanner'
+require 'dimensions/tiff_scanning'
+
+module Dimensions
+ class TiffScanner < Scanner
+ include TiffScanning
+
+ WIDTH_TAG = 0x100
+ HEIGHT_TAG = 0x101
+
+ attr_reader :width, :height
+
+ def initialize(data)
+ @width = nil
+ @height = nil
+ super
+ end
+
+ def scan
+ scan_header
+
+ scan_ifd do |tag|
+ if tag == WIDTH_TAG
+ @width = read_integer_value
+ elsif tag == HEIGHT_TAG
+ @height = read_integer_value
+ end
+ end
+
+ @width && @height
+ end
+ end
+end
@@ -0,0 +1,47 @@
+module Dimensions
+ module TiffScanning
+ def scan_header
+ scan_endianness
+ scan_tag_mark
+ scan_and_skip_to_offset
+ end
+
+ def scan_endianness
+ tag = [read_char, read_char]
+ tag == [0x4D, 0x4D] ? big! : little!
+ end
+
+ def scan_tag_mark
+ raise_scan_error unless read_short == 0x002A
+ end
+
+ def scan_and_skip_to_offset
+ offset = read_long
+ skip_to(offset)
+ end
+
+ def scan_ifd
+ offset = pos
+ entry_count = read_short
+
+ entry_count.times do |i|
+ skip_to(offset + 2 + (12 * i))
+ tag = read_short
+ yield tag
+ end
+ end
+
+ def read_integer_value
+ type = read_short
+ advance(4)
+
+ if type == 3
+ read_short
+ elsif type == 4
+ read_long
+ else
+ raise_scan_error
+ end
+ end
+ end
+end
View
Binary file not shown.
View
Binary file not shown.
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
@@ -26,6 +26,10 @@ def test_rotated_jpeg_dimensions
assert_angle "rotated.jpg", 90
end
+ def test_tiff_dimensions
+ assert_dimensions "short.tif", 50, 20
+ end
+
def assert_dimensions(filename, expected_width, expected_height)
actual_width, actual_height = Dimensions.dimensions(fixture_path(filename))
assert_equal "#{expected_width}x#{expected_height}", "#{actual_width}x#{actual_height}"
View
@@ -0,0 +1,21 @@
+require 'dimensions/test_case'
+
+class TestTiffScanner < Dimensions::TestCase
+ def test_scanning_tiff_with_short_values
+ with_fixture("short.tif") do |file|
+ scanner = Dimensions::TiffScanner.new(file.read)
+ assert scanner.scan
+ assert_equal 50, scanner.width
+ assert_equal 20, scanner.height
+ end
+ end
+
+ def test_scanning_tiff_with_long_values
+ with_fixture("long.tif") do |file|
+ scanner = Dimensions::TiffScanner.new(file.read)
+ assert scanner.scan
+ assert_equal 67_000, scanner.width
+ assert_equal 1, scanner.height
+ end
+ end
+end

0 comments on commit a977bea

Please sign in to comment.