/* * @(#)TIFFInputStream.java 1.0 2009-12-26 * * Copyright (c) 2009 Werner Randelshofer, Goldau, Switzerland. * All rights reserved. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with Werner Randelshofer. * For details see accompanying license terms. */ package org.monte.media.tiff; import org.monte.media.math.Rational; import java.io.DataInputStream; import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; import javax.imageio.stream.ImageInputStream; /** * Reads a TIFF file. * <p> * References: * <p> * TIFF TM Revision 6.0. Final — June 3, 1992. * Adobe Systems Inc. * http://www.exif.org/specifications.html * * @author Werner Randelshofer * @version 1.0 2009-12-26 Created. */ public class TIFFInputStream extends InputStream { /** A TIFF input stream can be little endian or big endian. */ private ByteOrder byteOrder; /** The offset of the first IFD. */ private long firstIFDOffset; /** The underlying input stream. */ private ImageInputStream in; public TIFFInputStream(ImageInputStream in) throws IOException { this.in = in; readHeader(); } /** Creates a TIFFInputStream from a stream which does not have a header. */ public TIFFInputStream(ImageInputStream in, ByteOrder byteOrder, long firstIFDOffset) { this.in = in; this.byteOrder = byteOrder; this.firstIFDOffset = firstIFDOffset; } public ByteOrder getByteOrder() { return byteOrder; } public void setByteOrder(ByteOrder newValue) { byteOrder = newValue; } public long getFirstIFDOffset() { return firstIFDOffset; } /** Reads the IFD at the specified offset. * <p> * An IFD consists of a 2-byte count of the number of directory entries * (i.e., the number of fields), followed by a sequence of 12-byte field entries, * followed by a 4-byte offset of the next IFD (or 0 if none). * <p> * Each 12-byte IFD entry has the following format: * Bytes 0-1 The Tag that identifies the field. * Bytes 2-3 The field Type. * Bytes 4-7 The number of values, Count of the indicated Type. * Bytes 8-11 The Value Offset, the file offset (in bytes) of the Value for the * field. The Value is expected to begin on a word boundary; the corresponding * Value Offset will thus be an even number. This file offset may point anywhere * in the file, even after the image data. * <p> * There must be at least 1 IFD in a TIFF file and each IFD must have at least * one entry. */ public IFD readIFD(long offset) throws IOException { return readIFD(offset, true, false); } /** Reads the IFD at the specified offset. * <p> * An IFD consists of a 2-byte count of the number of directory entries * (i.e., the number of fields), followed by a sequence of 12-byte field entries, * followed by a 4-byte offset of the next IFD (or 0 if none). * <p> * Each 12-byte IFD entry has the following format: * Bytes 0-1 The Tag that identifies the field. * Bytes 2-3 The field Type. * Bytes 4-7 The number of values, Count of the indicated Type. * Bytes 8-11 The Value Offset, the file offset (in bytes) of the Value for the * field. The Value is expected to begin on a word boundary; the corresponding * Value Offset will thus be an even number. This file offset may point anywhere * in the file, even after the image data. * <p> * There must be at least 1 IFD in a TIFF file and each IFD must have at least * one entry. */ public IFD readIFD(long offset, boolean hasNextOffset, boolean isFirstIFD) throws IOException { if ((offset % 1) != 0) { throw new IOException("IFD does not start at word boundary"); } if (offset == 0 && !isFirstIFD) { return null; } in.seek(offset); int numEntries = readSHORT(); IFD ifd = new IFD(offset, hasNextOffset); for (int i = 0; i < numEntries; i++) { long entryOffset = in.getStreamPosition(); int tag = readSHORT(); int type = readSHORT(); long count = readLONG(); long valueOffset = readSLONG(); if (count == 0) { throw new IOException("IFDEntry " + i + " of " + numEntries + " has count 0 in TIFF stream at offset 0x" + Long.toHexString(offset)); //continue; } ifd.add(new IFDEntry(tag, type, count, valueOffset, entryOffset)); } if (hasNextOffset) { ifd.setNextOffset(readSLONG()); if ((ifd.getNextOffset() % 1) != 0) { throw new IOException("next IFD does not start at word boundary"); } } return ifd; } /** Reads an ASCII (8-bit byte that contains a 7-bit ASCII code; the last byte * must be NUL (binary zero). * value at the specified offset. */ public String readASCII(long offset, long length) throws IOException { in.seek(offset); return readASCII(length); } private String readASCII(long length) throws IOException { byte[] buf = new byte[(int) length]; readFully(buf); if (buf[(int) length - 1] != 0) { throw new IOException("String does not end with NUL byte."); } return new String(buf, 0, (int) length - 1, "ASCII"); } private void readFully(byte b[]) throws IOException { readFully(b, 0, b.length); } private void readFully(byte b[], int off, int len) throws IOException { if (len < 0) { throw new IndexOutOfBoundsException(); } int n = 0; while (n < len) { int count = in.read(b, off + n, len - n); if (count < 0) { throw new EOFException("EOF after " + n + " bytes (needed " + len + " bytes)"); } n += count; } } /** Reads a LONG (32-bit (4-byte) unsigned integer). * value at the specified offset. */ public long readLONG(long offset) throws IOException { in.seek(offset); return readLONG(); } /** Reads the specified number of LONGs (32-bit (4-byte) unsigned integer). * value at the specified offset. */ public long[] readLONG(long offset, long count) throws IOException { in.seek(offset); long[] longs = new long[(int) count]; for (int i = 0; i < count; i++) { longs[i] = readLONG(); } return longs; } /** Reads the specified number of SHORTs (16-bit (2-byte) unsigned integer). * value at the specified offset. */ public int[] readSHORT(long offset, long count) throws IOException { in.seek(offset); int[] shorts = new int[(int) count]; for (int i = 0; i < count; i++) { shorts[i] = readSHORT(); } return shorts; } /** Reads the specified number of SSHORTs (16-bit (2-byte) signed integer). * value at the specified offset. */ public short[] readSSHORT(long offset, long count) throws IOException { in.seek(offset); short[] shorts = new short[(int) count]; for (int i = 0; i < count; i++) { shorts[i] = readSSHORT(); } return shorts; } /** Reads a RATIONAL number at the specified offset. */ public Rational readRATIONAL(long offset) throws IOException { in.seek(offset); long num = readLONG(); long denom = readLONG(); return new Rational(num, denom); } /** Reads a RATIONAL number at the specified offset. */ public Rational readSRATIONAL(long offset) throws IOException { in.seek(offset); int num = readSLONG(); int denom = readSLONG(); return new Rational(num, denom); } /** Reads the specified number of RATIONALs at the specified offset. */ public Rational[] readRATIONAL(long offset, long count) throws IOException { in.seek(offset); Rational[] r = new Rational[(int) count]; for (int i = 0; i < count; i++) { r[i] = new Rational(readLONG(), readLONG()); } return r; } /** Reads the specified number of RATIONALs at the specified offset. */ public Rational[] readSRATIONAL(long offset, long count) throws IOException { in.seek(offset); Rational[] r = new Rational[(int) count]; for (int i = 0; i < count; i++) { r[i] = new Rational(readSLONG(), readSLONG()); } return r; } /** Reads a 16-bit signed integer. */ private short readSSHORT() throws IOException { int b0 = in.read(); int b1 = in.read(); if (b0 == -1 || b1 == -1) { throw new EOFException(); } if (byteOrder == ByteOrder.LITTLE_ENDIAN) { return (short) ((b1 << 8) | b0); } else { return (short) ((b0 << 8) | b1); } } /** Reads a 16-bit unsigned integer. */ private int readSHORT() throws IOException { return readSSHORT() & 0xffff; } /** Reads a 32-bit signed integer. */ private int readSLONG() throws IOException { int b0 = in.read(); int b1 = in.read(); int b2 = in.read(); int b3 = in.read(); if (b0 == -1 || b1 == -1 || b1 == -1 || b2 == -1) { throw new EOFException(); } if (byteOrder == ByteOrder.LITTLE_ENDIAN) { return ((b3 << 24) | (b2 << 16) | (b1 << 8) | b0); } else { return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3); } } /** Reads a 32-bit unsigned integer. */ private long readLONG() throws IOException { return readSLONG() & 0xffffffffL; } /** Reads the Image File header. * * struct { * short byteOrder // 0x4949=little endian, 0x4d4d=big endian * short magic // 42 in little or big endian * long offset // offset in little or big endian to the first IFD * } */ private void readHeader() throws IOException { in.seek(0); byteOrder = ByteOrder.BIG_ENDIAN; int byteOrder = readSHORT(); switch (byteOrder) { case 0x4949: this.byteOrder = ByteOrder.LITTLE_ENDIAN; break; case 0x4d4d: this.byteOrder = ByteOrder.BIG_ENDIAN; break; default: throw new IOException("Image File Header illegal byte order value 0x" + Integer.toHexString(byteOrder)); } int magic = readSHORT(); if (magic != 42) { throw new IOException("Image File Header illegal magic value 0x" + Integer.toHexString(magic)); } firstIFDOffset = readSLONG(); if ((firstIFDOffset & 1) == 1) { throw new IOException("Image File Header IFD must be on a word boundary 0x" + Long.toHexString(firstIFDOffset)); } } @Override public int read() throws IOException { return in.read(); } @Override public int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } public int read(long offset, byte b[], int off, int len) throws IOException { in.seek(offset); return in.read(b, off, len); } }