
org.monte.media.tiff.TIFFInputStream Maven / Gradle / Ivy
/*
* @(#)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.
*
* References:
*
* 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.
*
* 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).
*
* 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.
*
* 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.
*
* 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).
*
* 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.
*
* 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);
}
}