class FormatParser::TIFFParser include FormatParser::IOUtils LITTLE_ENDIAN_TIFF_HEADER_BYTES = [0x49, 0x49, 0x2A, 0x0] BIG_ENDIAN_TIFF_HEADER_BYTES = [0x4D, 0x4D, 0x0, 0x2A] WIDTH_TAG = 0x100 HEIGHT_TAG = 0x101 def call(io) io = FormatParser::IOConstraint.new(io) magic_bytes = safe_read(io, 4).unpack('C4') endianness = scan_tiff_endianness(magic_bytes) return if !endianness || cr2_check(io) w, h = read_tiff_by_endianness(io, endianness) scanner = FormatParser::EXIFParser.new(io) scanner.scan_image_tiff FormatParser::Image.new( format: :tif, width_px: w, height_px: h, # might be nil if EXIF metadata wasn't found orientation: scanner.orientation ) end # TIFFs can be either big or little endian, so we check here # and set our unpack method argument to suit. def scan_tiff_endianness(magic_bytes) if magic_bytes == LITTLE_ENDIAN_TIFF_HEADER_BYTES 'v' elsif magic_bytes == BIG_ENDIAN_TIFF_HEADER_BYTES 'n' end end # The TIFF format stores metadata in a flexible set of information fields # called tags, which are stored in a header referred to as the IFD or # Image File Directory. It is not necessarily in the same place in every image, # so we need to do some work to scan through it and find the tags we need. # For more information the TIFF wikipedia page is a reasonable place to start: # https://en.wikipedia.org/wiki/TIFF def scan_ifd(cache, offset, endianness) entry_count = safe_read(cache, 4).unpack(endianness)[0] entry_count.times do |i| cache.seek(offset + 2 + (12 * i)) tag = safe_read(cache, 4).unpack(endianness)[0] if tag == WIDTH_TAG @width = safe_read(cache, 4).unpack(endianness.upcase)[0] elsif tag == HEIGHT_TAG @height = safe_read(cache, 4).unpack(endianness.upcase)[0] end end end def read_tiff_by_endianness(io, endianness) io.seek(4) offset = safe_read(io, 4).unpack(endianness.upcase)[0] io.seek(offset) scan_ifd(io, offset, endianness) [@width, @height] end def cr2_check(io) io.seek(8) cr2_check_bytes = safe_read(io, 2) cr2_check_bytes == 'CR' end FormatParser.register_parser self, natures: :image, formats: :tif end