#342 Initial BigTIFF support

This commit is contained in:
Harald Kuhr
2017-08-21 21:59:09 +02:00
parent 3f5f48d8cb
commit 792b531b0e
20 changed files with 499 additions and 44 deletions

View File

@@ -41,6 +41,7 @@ public interface TIFF {
short BYTE_ORDER_MARK_LITTLE_ENDIAN = ('I' << 8) | 'I';
int TIFF_MAGIC = 42;
int BIGTIFF_MAGIC = 43;
short TYPE_BYTE = 1;
short TYPE_ASCII = 2;
@@ -56,6 +57,10 @@ public interface TIFF {
short TYPE_FLOAT = 11;
short TYPE_DOUBLE = 12;
short TYPE_IFD = 13;
// BigTIFF
short TYPE_LONG8 = 16;
short TYPE_SLONG8 = 17;
short TYPE_IFD8 = 18;
/*
1 = BYTE 8-bit unsigned integer.
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte

View File

@@ -387,7 +387,7 @@ public final class TIFFEntry extends AbstractEntry {
throw new UnsupportedOperationException(String.format("Method guessType not implemented for type %s", value.getClass()));
}
static int getValueLength(final int pType, final int pCount) {
static long getValueLength(final int pType, final long pCount) {
if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
return TIFF.TYPE_LENGTHS[pType] * pCount;
}

View File

@@ -59,6 +59,8 @@ public final class TIFFReader extends MetadataReader {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.metadata.exif.debug"));
static final Collection<Integer> KNOWN_IFDS = Collections.unmodifiableCollection(Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD));
private boolean longOffsets;
private int offsetSize;
@Override
public Directory read(final ImageInputStream input) throws IOException {
@@ -77,18 +79,41 @@ public final class TIFFReader extends MetadataReader {
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
}
// TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
// BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
int magic = input.readUnsignedShort();
if (magic != TIFF.TIFF_MAGIC) {
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
if (magic == TIFF.TIFF_MAGIC) {
longOffsets = false;
offsetSize = 4;
}
else if (magic == TIFF.BIGTIFF_MAGIC) {
longOffsets = true;
offsetSize = 8;
// Just validate we're ok
int offsetSize = input.readUnsignedShort();
if (offsetSize != 8) {
throw new IIOException(String.format("Unexpected BigTIFF offset size: %04x, expected: %04x", offsetSize, 8));
}
int padding = input.readUnsignedShort();
if (padding != 0) {
throw new IIOException(String.format("Unexpected BigTIFF padding: %04x, expected: %04x", padding, 0));
}
}
else {
throw new IIOException(String.format("Wrong TIFF magic in input data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
}
long directoryOffset = input.readUnsignedInt();
long directoryOffset = readOffset(input);
return readDirectory(input, directoryOffset, true);
}
private long readOffset(final ImageInputStream input) throws IOException {
return longOffsets ? input.readLong() : input.readUnsignedInt();
}
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
List<IFD> ifds = new ArrayList<>();
@@ -97,14 +122,7 @@ public final class TIFFReader extends MetadataReader {
pInput.seek(pOffset);
long nextOffset = -1;
int entryCount;
try {
entryCount = pInput.readUnsignedShort();
}
catch (EOFException e) {
// Treat EOF here as empty Sub-IFD
entryCount = 0;
}
long entryCount = readEntryCount(pInput);
for (int i = 0; i < entryCount; i++) {
try {
@@ -122,7 +140,7 @@ public final class TIFFReader extends MetadataReader {
if (readLinked) {
if (nextOffset == -1) {
try {
nextOffset = pInput.readUnsignedInt();
nextOffset = readOffset(pInput);
}
catch (EOFException e) {
// catch EOF here as missing EOF marker
@@ -150,6 +168,16 @@ public final class TIFFReader extends MetadataReader {
return new TIFFDirectory(ifds);
}
private long readEntryCount(final ImageInputStream pInput) throws IOException {
try {
return longOffsets ? pInput.readLong() : pInput.readUnsignedShort();
}
catch (EOFException e) {
// Treat EOF here as empty Sub-IFD
return 0;
}
}
// TODO: Might be better to leave this for client code, as it's tempting go really overboard and support any possible embedded format..
private void readSubdirectories(ImageInputStream input, List<Entry> entries, List<Integer> subIFDIds) throws IOException {
if (subIFDIds == null || subIFDIds.isEmpty()) {
@@ -215,31 +243,27 @@ public final class TIFFReader extends MetadataReader {
offsets = (long[]) value;
}
else {
throw new IIOException(String.format("Unknown pointer type: %s", (value != null
? value.getClass()
: null)));
throw new IIOException(String.format("Unknown pointer type: %s", value != null ? value.getClass() : null));
}
return offsets;
}
private TIFFEntry readEntry(final ImageInputStream pInput) throws IOException {
// TODO: BigTiff entries are different
int tagId = pInput.readUnsignedShort();
short type = pInput.readShort();
int count = pInput.readInt(); // Number of values
int count = readValueCount(pInput); // Number of values
// It's probably a spec violation to have count 0, but we'll be lenient about it
if (count < 0) {
throw new IIOException(String.format("Illegal count %d for tag %s type %s @%08x", count, tagId, type, pInput.getStreamPosition()));
}
if (type <= 0 || type > 13) {
if (!isValidType(type)) {
pInput.skipBytes(4); // read Value
// Invalid tag, this is just for debugging
long offset = pInput.getStreamPosition() - 12l;
long offset = pInput.getStreamPosition() - 12L;
if (DEBUG) {
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
@@ -266,22 +290,37 @@ public final class TIFFReader extends MetadataReader {
return null;
}
int valueLength = getValueLength(type, count);
long valueLength = getValueLength(type, count);
Object value;
// TODO: For BigTiff allow size > 4 && <= 8 in addition
if (valueLength > 0 && valueLength <= 4) {
if (valueLength > 0 && valueLength <= offsetSize) {
value = readValueInLine(pInput, type, count);
pInput.skipBytes(4 - valueLength);
pInput.skipBytes(offsetSize - valueLength);
}
else {
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= 4 bytes
value = readValueAt(pInput, valueOffset, type, count);
}
return new TIFFEntry(tagId, type, value);
}
private boolean isValidType(final short type) {
return type > 0 && type < TIFF.TYPE_LENGTHS.length && TIFF.TYPE_LENGTHS[type] > 0;
}
private int readValueCount(final ImageInputStream pInput) throws IOException {
return assertIntCount(longOffsets ? pInput.readLong() : pInput.readUnsignedInt());
}
private int assertIntCount(final long count) throws IOException {
if (count > Integer.MAX_VALUE) {
throw new IIOException(String.format("Unsupported TIFF value count value: %s > Integer.MAX_VALUE", count));
}
return (int) count;
}
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
long pos = pInput.getStreamPosition();
try {
@@ -423,16 +462,17 @@ public final class TIFFReader extends MetadataReader {
return srationals;
// BigTiff:
case 16: // LONG8
case 17: // SLONG8
case 18: // IFD8
case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8:
case TIFF.TYPE_IFD8:
// TODO: Assert BigTiff (version == 43)
if (pCount == 1) {
long val = pInput.readLong();
if (pType != 17 && val < 0) {
if (pType != TIFF.TYPE_SLONG8 && val < 0) {
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
}
return val;
}
@@ -459,6 +499,17 @@ public final class TIFFReader extends MetadataReader {
}
public static void main(String[] args) throws IOException {
// if (true) {
// ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));
//
// byte[] b = new byte[Math.min((int) stream.length(), 1024)];
// stream.readFully(b);
//
// System.err.println(HexDump.dump(b));
//
// return;
// }
//
TIFFReader reader = new TIFFReader();
ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));

View File

@@ -163,15 +163,11 @@ public final class TIFFWriter extends MetadataWriter {
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
}
public long computeIFDOffsetSize(final Collection<Entry> directory) {
return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
}
private long computeDataSize(final Directory directory) {
long dataSize = 0;
for (Entry entry : directory) {
int length = getValueLength(getType(entry), getCount(entry));
long length = getValueLength(getType(entry), getCount(entry));
if (length < 0) {
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
@@ -229,13 +225,13 @@ public final class TIFFWriter extends MetadataWriter {
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
short type = getType(entry);
int valueLength = getValueLength(type, getCount(entry));
long valueLength = getValueLength(type, getCount(entry));
if (valueLength <= LONGWORD_LENGTH) {
writeValueInline(entry.getValue(), type, stream);
// Pad
for (int i = valueLength; i < LONGWORD_LENGTH; i++) {
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
stream.write(0);
}