mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-03 23:53:15 -04:00
Made EXIFReader more lenient while parsing.
- Now supports empty strings encoded with value count 0. - Added Rational.NaN constant to handle bad EXIF data. Fixed a bug in the JPEGImageReader's raw EXIF thumbnail decoding. Added test cases.
This commit is contained in:
@@ -80,6 +80,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "PhotometricInterpretation";
|
||||
case TIFF.TAG_IMAGE_DESCRIPTION:
|
||||
return "ImageDescription";
|
||||
case TIFF.TAG_STRIP_OFFSETS:
|
||||
return "StripOffsets";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
return "Orientation";
|
||||
case TIFF.TAG_SAMPLES_PER_PIXELS:
|
||||
|
@@ -213,12 +213,11 @@ public final class EXIFReader extends MetadataReader {
|
||||
short type = pInput.readShort();
|
||||
int count = pInput.readInt(); // Number of values
|
||||
|
||||
// TODO: Read up what the spec says about value-count == 0, it makes no sense.
|
||||
// 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()));
|
||||
}
|
||||
|
||||
Object value;
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
if (type < 0 || type > 13) {
|
||||
@@ -243,7 +242,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: For BigTiff allow size > 4 && <= 8
|
||||
Object value;
|
||||
// TODO: For BigTiff allow size > 4 && <= 8 in addition
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
value = readValueInLine(pInput, type, count);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
@@ -278,8 +278,11 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
switch (pType) {
|
||||
case 2: // ASCII
|
||||
// TODO: This might be UTF-8 or ISO-8859-x, even though spec says ASCII
|
||||
// TODO: This might be UTF-8 or ISO-8859-x, even though spec says NULL-terminated 7 bit ASCII
|
||||
// TODO: Fail if unknown chars, try parsing with ISO-8859-1 or file.encoding
|
||||
if (pCount == 0) {
|
||||
return "";
|
||||
}
|
||||
byte[] ascii = new byte[pCount];
|
||||
pInput.readFully(ascii);
|
||||
int len = ascii[ascii.length - 1] == 0 ? ascii.length - 1 : ascii.length;
|
||||
@@ -364,23 +367,23 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
case 5: // RATIONAL
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
return createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
Rational[] rationals = new Rational[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
rationals[i] = createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case 10: // SRATIONAL
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readInt(), pInput.readInt());
|
||||
return createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
Rational[] srationals = new Rational[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = new Rational(pInput.readInt(), pInput.readInt());
|
||||
srationals[i] = createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
return srationals;
|
||||
@@ -412,6 +415,15 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static Rational createSafeRational(final long numerator, final long denominator) throws IOException {
|
||||
if (denominator == 0) {
|
||||
// Bad data.
|
||||
return Rational.NaN;
|
||||
}
|
||||
|
||||
return new Rational(numerator, denominator);
|
||||
}
|
||||
|
||||
private int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
|
||||
|
@@ -53,10 +53,16 @@ public final class Rational extends Number implements Comparable<Rational> {
|
||||
// TODO: Move to com.tm.lang?
|
||||
// Inspired by http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html and java.lang.Integer
|
||||
static final Rational ZERO = new Rational(0, 1);
|
||||
static final Rational NaN = new Rational(); // TODO: This field needs thoughts/tests/spec/consistency check, see Float.NaN
|
||||
|
||||
private final long numerator;
|
||||
private final long denominator;
|
||||
|
||||
private Rational() {
|
||||
numerator = 0;
|
||||
denominator = 0;
|
||||
}
|
||||
|
||||
public Rational(final long pNumber) {
|
||||
this(pNumber, 1);
|
||||
}
|
||||
@@ -121,6 +127,10 @@ public final class Rational extends Number implements Comparable<Rational> {
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
if (this == NaN) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
return numerator / (double) denominator;
|
||||
}
|
||||
|
||||
@@ -147,6 +157,10 @@ public final class Rational extends Number implements Comparable<Rational> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NaN) {
|
||||
return "NaN";
|
||||
}
|
||||
|
||||
return denominator == 1 ? Long.toString(numerator) : String.format("%s/%s", numerator, denominator);
|
||||
}
|
||||
|
||||
|
@@ -30,10 +30,13 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -131,4 +134,31 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
|
||||
assertNull(ifd1.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertNull(ifd1.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBadDataZeroCount() throws IOException {
|
||||
// This image seems to contain bad Exif. But as other tools are able to read, so should we..
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-rgb-thumbnail-bad-exif-kodak-dc210.jpg"));
|
||||
stream.seek(12);
|
||||
Directory directory = createReader().read(new SubImageInputStream(stream, 21674));
|
||||
|
||||
assertEquals(22, directory.size());
|
||||
|
||||
// Special case: Ascii string with count == 0, not ok according to spec (?), but we'll let it pass
|
||||
assertEquals("", directory.getEntryById(TIFF.TAG_IMAGE_DESCRIPTION).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBadDataRationalZeroDenominator() throws IOException {
|
||||
// This image seems to contain bad Exif. But as other tools are able to read, so should we..
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-rgb-thumbnail-bad-exif-kodak-dc210.jpg"));
|
||||
stream.seek(12);
|
||||
Directory directory = createReader().read(new SubImageInputStream(stream, 21674));
|
||||
|
||||
// Special case: Rational with zero-denominator inside EXIF data
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
Entry entry = exif.getEntryById(EXIF.TAG_COMPRESSED_BITS_PER_PIXEL);
|
||||
assertNotNull(entry);
|
||||
assertEquals(Rational.NaN, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
Reference in New Issue
Block a user