mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-02 02:55:28 -04:00
#204 TIFF metadata refactor
This commit is contained in:
parent
7a0660c4d7
commit
a86b76256b
@ -29,11 +29,14 @@
|
||||
package com.twelvemonkeys.contrib.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.AffineTransformOp;
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@ -44,7 +47,6 @@ import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
@ -57,7 +59,7 @@ import java.util.List;
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class TIFFUtilities {
|
||||
public final class TIFFUtilities {
|
||||
private TIFFUtilities() {
|
||||
}
|
||||
|
||||
@ -201,7 +203,7 @@ public class TIFFUtilities {
|
||||
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
|
||||
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
|
||||
|
||||
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
|
||||
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(imageInput);
|
||||
|
||||
int pageCount = IFDs.directoryCount();
|
||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||
@ -212,7 +214,7 @@ public class TIFFUtilities {
|
||||
}
|
||||
|
||||
public static void writePages(ImageOutputStream imageOutput, List<TIFFPage> pages) throws IOException {
|
||||
EXIFWriter exif = new EXIFWriter();
|
||||
TIFFWriter exif = new TIFFWriter();
|
||||
long nextPagePos = imageOutput.getStreamPosition();
|
||||
if (nextPagePos == 0) {
|
||||
exif.writeTIFFHeader(imageOutput);
|
||||
@ -316,9 +318,9 @@ public class TIFFUtilities {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
private long write(ImageOutputStream outputStream, EXIFWriter exifWriter) throws IOException {
|
||||
private long write(ImageOutputStream outputStream, TIFFWriter tiffWriter) throws IOException {
|
||||
List<Entry> newIFD = writeDirectoryData(IFD, outputStream);
|
||||
return exifWriter.writeIFD(newIFD, outputStream);
|
||||
return tiffWriter.writeIFD(newIFD, outputStream);
|
||||
}
|
||||
|
||||
private List<Entry> writeDirectoryData(Directory IFD, ImageOutputStream outputStream) throws IOException {
|
||||
@ -506,78 +508,9 @@ public class TIFFUtilities {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
|
||||
IFD = new AbstractDirectory(newIDFData) {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||
*/
|
||||
public static final class TIFFEntry extends AbstractEntry {
|
||||
// TODO: Expose a merge of this and the EXIFEntry class...
|
||||
private final short type;
|
||||
|
||||
private static short guessType(final Object val) {
|
||||
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
|
||||
Object value = Validate.notNull(val);
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
if (array) {
|
||||
value = Array.get(value, 0);
|
||||
}
|
||||
|
||||
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||
// TODO: Check for negative values and use signed types?
|
||||
if (value instanceof Byte) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
if (value instanceof Short) {
|
||||
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Rational) {
|
||||
return TIFF.TYPE_RATIONAL;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return TIFF.TYPE_ASCII;
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
|
||||
}
|
||||
|
||||
public TIFFEntry(final int identifier, final Object value) {
|
||||
this(identifier, guessType(value), value);
|
||||
}
|
||||
|
||||
TIFFEntry(int identifier, short type, Object value) {
|
||||
super(identifier, value);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
IFD = new AbstractDirectory(newIDFData) {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,13 +31,13 @@ package com.twelvemonkeys.imageio.path;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
|
||||
@ -129,7 +129,7 @@ public final class Paths {
|
||||
else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC
|
||||
|| magic >>> 16 == TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC << 8) {
|
||||
// TIFF version
|
||||
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(stream);
|
||||
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(stream);
|
||||
|
||||
Directory directory = IFDs.getDirectory(0);
|
||||
Entry photoshop = directory.getEntryById(TIFF.TAG_PHOTOSHOP);
|
||||
|
@ -31,7 +31,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
|
@ -34,11 +34,11 @@ import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
@ -790,9 +790,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
||||
return (CompoundDirectory) new EXIFReader().read(stream);
|
||||
return (CompoundDirectory) new TIFFReader().read(stream);
|
||||
|
||||
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the EXIFReader...
|
||||
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the TIFFReader...
|
||||
}
|
||||
}
|
||||
|
||||
@ -981,7 +981,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
ImageInputStream stream = new MemoryCacheImageInputStream(data);
|
||||
CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
|
||||
CompoundDirectory exifMetadata = (CompoundDirectory) new TIFFReader().read(stream);
|
||||
|
||||
if (exifMetadata.directoryCount() == 2) {
|
||||
Directory ifd1 = exifMetadata.getDirectory(1);
|
||||
|
@ -29,10 +29,10 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
@ -63,7 +63,7 @@ public class EXIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||
assertNotNull(segments);
|
||||
assertFalse(segments.isEmpty());
|
||||
|
||||
EXIFReader reader = new EXIFReader();
|
||||
TIFFReader reader = new TIFFReader();
|
||||
InputStream data = segments.get(0).data();
|
||||
if (data.read() < 0) {
|
||||
throw new AssertionError("EOF!");
|
||||
|
@ -28,22 +28,12 @@
|
||||
|
||||
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.MetadataReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* EXIFReader
|
||||
@ -51,528 +41,15 @@ import java.util.*;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
||||
*
|
||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFReader instead.
|
||||
*/
|
||||
public final class EXIFReader 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 final TIFFReader delegate = new TIFFReader();
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
byte[] bom = new byte[2];
|
||||
input.readFully(bom);
|
||||
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||
input.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
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
|
||||
// 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));
|
||||
}
|
||||
|
||||
long directoryOffset = input.readUnsignedInt();
|
||||
|
||||
return readDirectory(input, directoryOffset, true);
|
||||
}
|
||||
|
||||
// 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<>();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
long nextOffset = -1;
|
||||
|
||||
int entryCount;
|
||||
try {
|
||||
entryCount = pInput.readUnsignedShort();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// Treat EOF here as empty Sub-IFD
|
||||
entryCount = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
try {
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (readLinked) {
|
||||
if (nextOffset == -1) {
|
||||
try {
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// catch EOF here as missing EOF marker
|
||||
nextOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read linked IFDs
|
||||
if (nextOffset != 0) {
|
||||
CompoundDirectory next = (CompoundDirectory) readDirectory(pInput, nextOffset, true);
|
||||
|
||||
for (int i = 0; i < next.directoryCount(); i++) {
|
||||
ifds.add((IFD) next.getDirectory(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider leaving to client code what sub-IFDs to parse (but always parse TAG_SUB_IFD).
|
||||
readSubdirectories(pInput, entries,
|
||||
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD)
|
||||
);
|
||||
|
||||
ifds.add(0, new IFD(entries));
|
||||
|
||||
return new EXIFDirectory(ifds);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
|
||||
EXIFEntry entry = (EXIFEntry) entries.get(i);
|
||||
int tagId = (Integer) entry.getIdentifier();
|
||||
|
||||
if (subIFDIds.contains(tagId)) {
|
||||
try {
|
||||
if (KNOWN_IFDS.contains(tagId)) {
|
||||
long[] pointerOffsets = getPointerOffsets(entry);
|
||||
List<IFD> subIFDs = new ArrayList<>(pointerOffsets.length);
|
||||
|
||||
for (long pointerOffset : pointerOffsets) {
|
||||
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
||||
|
||||
for (int j = 0; j < subDirectory.directoryCount(); j++) {
|
||||
subIFDs.add((IFD) subDirectory.getDirectory(j));
|
||||
}
|
||||
}
|
||||
|
||||
if (subIFDs.size() == 1) {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.get(0), entry.getType()));
|
||||
}
|
||||
else {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.toArray(new IFD[subIFDs.size()]), entry.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
if (DEBUG) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
System.err.println("Error parsing sub-IFD: " + tagId);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long[] getPointerOffsets(final Entry entry) throws IIOException {
|
||||
long[] offsets;
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Byte) {
|
||||
offsets = new long[] {(Byte) value & 0xff};
|
||||
}
|
||||
else if (value instanceof Short) {
|
||||
offsets = new long[] {(Short) value & 0xffff};
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
offsets = new long[] {(Integer) value & 0xffffffffL};
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
offsets = new long[] {(Long) value};
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
offsets = (long[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null
|
||||
? value.getClass()
|
||||
: null)));
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
private EXIFEntry 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
|
||||
|
||||
// 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) {
|
||||
pInput.skipBytes(4); // read Value
|
||||
|
||||
// Invalid tag, this is just for debugging
|
||||
long offset = pInput.getStreamPosition() - 12l;
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
|
||||
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
value = readValueAt(pInput, valueOffset, type, count);
|
||||
}
|
||||
|
||||
return new EXIFEntry(tagId, value, type);
|
||||
}
|
||||
|
||||
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(pOffset);
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// TODO: Add warning listener API and report problem to client code
|
||||
return e;
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
|
||||
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
|
||||
// TODO: New strategy: Leave data as is, instead perform the widening in EXIFEntry.getValue.
|
||||
// TODO: Add getValueByte/getValueUnsignedByte/getValueShort/getValueUnsignedShort/getValueInt/etc... in API.
|
||||
|
||||
long pos = pInput.getStreamPosition();
|
||||
|
||||
switch (pType) {
|
||||
case TIFF.TYPE_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;
|
||||
return StringUtil.decode(ascii, 0, len, "UTF-8"); // UTF-8 is ASCII compatible
|
||||
case TIFF.TYPE_BYTE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
// else fall through
|
||||
case TIFF.TYPE_SBYTE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
// else fall through
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
// NOTE: We don't change (unsigned) BYTE array wider Java type, as most often BYTE array means
|
||||
// binary data and we want to keep that as a byte array for clients to parse further
|
||||
|
||||
return bytes;
|
||||
case TIFF.TYPE_SHORT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case TIFF.TYPE_SSHORT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
|
||||
if (pType == TIFF.TYPE_SHORT) {
|
||||
int[] ints = new int[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
ints[i] = shorts[i] & 0xffff;
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
return shorts;
|
||||
case TIFF.TYPE_IFD:
|
||||
case TIFF.TYPE_LONG:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case TIFF.TYPE_SLONG:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
|
||||
if (pType == TIFF.TYPE_LONG || pType == TIFF.TYPE_IFD) {
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = ints[i] & 0xffffffffL;
|
||||
}
|
||||
|
||||
return longs;
|
||||
}
|
||||
|
||||
return ints;
|
||||
case TIFF.TYPE_FLOAT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
|
||||
double[] doubles = new double[pCount];
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
if (pCount == 1) {
|
||||
return createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
Rational[] rationals = new Rational[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
if (pCount == 1) {
|
||||
return createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
Rational[] srationals = new Rational[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
return srationals;
|
||||
|
||||
// BigTiff:
|
||||
case 16: // LONG8
|
||||
case 17: // SLONG8
|
||||
case 18: // IFD8
|
||||
// TODO: Assert BigTiff (version == 43)
|
||||
|
||||
if (pCount == 1) {
|
||||
long val = pInput.readLong();
|
||||
if (pType != 17 && val < 0) {
|
||||
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = pInput.readLong();
|
||||
}
|
||||
|
||||
return longs;
|
||||
|
||||
default:
|
||||
// Spec says skip unknown values
|
||||
return new Unknown(pType, pCount, pos);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType] * pCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
EXIFReader reader = new EXIFReader();
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));
|
||||
|
||||
long pos = 0;
|
||||
if (args.length > 1) {
|
||||
if (args[1].startsWith("0x")) {
|
||||
pos = Integer.parseInt(args[1].substring(2), 16);
|
||||
}
|
||||
else {
|
||||
pos = Long.parseLong(args[1]);
|
||||
}
|
||||
|
||||
stream.setByteOrder(pos < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
|
||||
pos = Math.abs(pos);
|
||||
|
||||
stream.seek(pos);
|
||||
}
|
||||
|
||||
try {
|
||||
Directory directory;
|
||||
|
||||
if (args.length > 1) {
|
||||
directory = reader.readDirectory(stream, pos, false);
|
||||
}
|
||||
else {
|
||||
directory = reader.read(stream);
|
||||
}
|
||||
|
||||
for (Entry entry : directory) {
|
||||
System.err.println(entry);
|
||||
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
byte[] bytes = (byte[]) value;
|
||||
System.err.println(HexDump.dump(0, bytes, 0, Math.min(bytes.length, 128)));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// TODO: Stream based hex dump util?
|
||||
public static class HexDump {
|
||||
private HexDump() {
|
||||
}
|
||||
|
||||
private static final int WIDTH = 32;
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(0, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String dump(long offset, byte[] bytes, int off, int len) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % WIDTH == 0) {
|
||||
if (i > 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(String.format("%08x: ", i + off + offset));
|
||||
}
|
||||
else if (i > 0 && i % 2 == 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
builder.append(String.format("%02x", bytes[i + off]));
|
||||
|
||||
int next = i + 1;
|
||||
if (next % WIDTH == 0 || next == len) {
|
||||
int leftOver = (WIDTH - (next % WIDTH)) % WIDTH;
|
||||
|
||||
if (leftOver != 0) {
|
||||
// Pad: 5 spaces for every 2 bytes... Special care if padding is non-even.
|
||||
int pad = leftOver / 2;
|
||||
|
||||
if (len % 2 != 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
for (int j = 0; j < pad; j++) {
|
||||
builder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(" ");
|
||||
builder.append(toAsciiString(bytes, next - (WIDTH - leftOver) + off, next + off));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String toAsciiString(final byte[] bytes, final int from, final int to) {
|
||||
byte[] range = Arrays.copyOfRange(bytes, from, to);
|
||||
|
||||
for (int i = 0; i < range.length; i++) {
|
||||
if (range[i] < 32 || range[i] > 126) {
|
||||
range[i] = '.'; // Unreadable char
|
||||
}
|
||||
}
|
||||
|
||||
return new String(range, Charset.forName("ascii"));
|
||||
}
|
||||
return delegate.read(input);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -28,20 +28,14 @@
|
||||
|
||||
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.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* EXIFWriter
|
||||
@ -49,430 +43,19 @@ import java.util.*;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
|
||||
*
|
||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter instead.
|
||||
*/
|
||||
public final class EXIFWriter extends MetadataWriter {
|
||||
|
||||
static final int WORD_LENGTH = 2;
|
||||
static final int LONGWORD_LENGTH = 4;
|
||||
static final int ENTRY_LENGTH = 12;
|
||||
|
||||
public boolean write(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return write(new IFD(entries), stream);
|
||||
}
|
||||
private final TIFFWriter delegate = new TIFFWriter();
|
||||
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(directory);
|
||||
Validate.notNull(stream);
|
||||
|
||||
// TODO: Should probably validate that the directory contains only valid TIFF entries...
|
||||
// the writer will crash on non-Integer ids and unsupported types
|
||||
// TODO: Implement the above validation in IFD constructor?
|
||||
|
||||
writeTIFFHeader(stream);
|
||||
|
||||
if (directory instanceof CompoundDirectory) {
|
||||
CompoundDirectory compoundDirectory = (CompoundDirectory) directory;
|
||||
|
||||
for (int i = 0; i < compoundDirectory.directoryCount(); i++) {
|
||||
writeIFD(compoundDirectory.getDirectory(i), stream, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
writeIFD(directory, stream, false);
|
||||
}
|
||||
|
||||
// Offset to next IFD (EOF)
|
||||
stream.writeInt(0);
|
||||
|
||||
return true;
|
||||
return delegate.write(directory, stream);
|
||||
}
|
||||
|
||||
public void writeTIFFHeader(final ImageOutputStream stream) throws IOException {
|
||||
// Header
|
||||
ByteOrder byteOrder = stream.getByteOrder();
|
||||
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
||||
stream.writeShort(42);
|
||||
public boolean write(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return delegate.write(entries, stream);
|
||||
}
|
||||
|
||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return writeIFD(new IFD(entries), stream, false);
|
||||
}
|
||||
|
||||
private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException {
|
||||
// TIFF spec says tags should be in increasing order, enforce that when writing
|
||||
Directory ordered = ensureOrderedDirectory(original);
|
||||
|
||||
// Compute space needed for extra storage first, then write the offset to the IFD, so that the layout is:
|
||||
// IFD offset
|
||||
// <data including sub-IFDs>
|
||||
// IFD entries (values/offsets)
|
||||
long dataOffset = stream.getStreamPosition();
|
||||
long dataSize = computeDataSize(ordered);
|
||||
|
||||
// Offset to this IFD
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
|
||||
|
||||
if (!isSubIFD) {
|
||||
stream.writeInt(assertIntegerOffset(ifdOffset));
|
||||
dataOffset += LONGWORD_LENGTH;
|
||||
|
||||
// Seek to offset
|
||||
stream.seek(ifdOffset);
|
||||
}
|
||||
else {
|
||||
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
|
||||
}
|
||||
|
||||
// Write directory
|
||||
stream.writeShort(ordered.size());
|
||||
|
||||
for (Entry entry : ordered) {
|
||||
// Write tag id
|
||||
stream.writeShort((Integer) entry.getIdentifier());
|
||||
// Write tag type
|
||||
stream.writeShort(getType(entry));
|
||||
// Write value count
|
||||
stream.writeInt(getCount(entry));
|
||||
|
||||
// Write value
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
// TODO: This could possibly be a compound directory, in which case the count should be > 1
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long streamPosition = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
writeIFD(subIFD, stream, true);
|
||||
dataOffset += computeDataSize(subIFD);
|
||||
stream.seek(streamPosition);
|
||||
}
|
||||
else {
|
||||
dataOffset += writeValue(entry, dataOffset, stream);
|
||||
}
|
||||
}
|
||||
|
||||
return ifdOffset;
|
||||
}
|
||||
|
||||
public long computeIFDSize(final Collection<Entry> directory) {
|
||||
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 = EXIFReader.getValueLength(getType(entry), getCount(entry));
|
||||
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
||||
}
|
||||
|
||||
if (length > LONGWORD_LENGTH) {
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
|
||||
dataSize += subIFDSize;
|
||||
}
|
||||
}
|
||||
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
private Directory ensureOrderedDirectory(final Directory directory) {
|
||||
if (!isSorted(directory)) {
|
||||
List<Entry> entries = new ArrayList<>(directory.size());
|
||||
|
||||
for (Entry entry : directory) {
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
Collections.sort(entries, new Comparator<Entry>() {
|
||||
public int compare(Entry left, Entry right) {
|
||||
return (Integer) left.getIdentifier() - (Integer) right.getIdentifier();
|
||||
}
|
||||
});
|
||||
|
||||
return new IFD(entries);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private boolean isSorted(final Directory directory) {
|
||||
int lastTag = 0;
|
||||
|
||||
for (Entry entry : directory) {
|
||||
int tag = ((Integer) entry.getIdentifier()) & 0xffff;
|
||||
|
||||
if (tag < lastTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastTag = tag;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
|
||||
short type = getType(entry);
|
||||
int valueLength = EXIFReader.getValueLength(type, getCount(entry));
|
||||
|
||||
if (valueLength <= LONGWORD_LENGTH) {
|
||||
writeValueInline(entry.getValue(), type, stream);
|
||||
|
||||
// Pad
|
||||
for (int i = valueLength; i < LONGWORD_LENGTH; i++) {
|
||||
stream.write(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
writeValueAt(dataOffset, entry.getValue(), type, stream);
|
||||
|
||||
return valueLength;
|
||||
}
|
||||
}
|
||||
|
||||
private int getCount(final Entry entry) {
|
||||
Object value = entry.getValue();
|
||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
||||
}
|
||||
|
||||
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
if (value.getClass().isArray()) {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
stream.write((byte[]) value);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
short[] shorts;
|
||||
|
||||
if (value instanceof short[]) {
|
||||
shorts = (short[]) value;
|
||||
}
|
||||
else if (value instanceof int[]) {
|
||||
int[] ints = (int[]) value;
|
||||
shorts = new short[ints.length];
|
||||
|
||||
for (int i = 0; i < ints.length; i++) {
|
||||
shorts[i] = (short) ints[i];
|
||||
}
|
||||
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
long[] longs = (long[]) value;
|
||||
shorts = new short[longs.length];
|
||||
|
||||
for (int i = 0; i < longs.length; i++) {
|
||||
shorts[i] = (short) longs[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeShorts(shorts, 0, shorts.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
int[] ints;
|
||||
|
||||
if (value instanceof int[]) {
|
||||
ints = (int[]) value;
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
long[] longs = (long[]) value;
|
||||
ints = new int[longs.length];
|
||||
|
||||
for (int i = 0; i < longs.length; i++) {
|
||||
ints[i] = (int) longs[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeInts(ints, 0, ints.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational[] rationals = (Rational[]) value;
|
||||
for (Rational rational : rationals) {
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_FLOAT:
|
||||
float[] floats;
|
||||
|
||||
if (value instanceof float[]) {
|
||||
floats = (float[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeFloats(floats, 0, floats.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
double[] doubles;
|
||||
|
||||
if (value instanceof double[]) {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeDoubles(doubles, 0, doubles.length);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
}
|
||||
}
|
||||
// else if (value instanceof Directory) {
|
||||
// writeIFD((Directory) value, stream, false);
|
||||
// }
|
||||
else {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
stream.writeByte(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_ASCII:
|
||||
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.write(bytes);
|
||||
stream.write(0);
|
||||
break;
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
stream.writeShort(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational rational = (Rational) value;
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
break;
|
||||
case TIFF.TYPE_FLOAT:
|
||||
stream.writeFloat(((Number) value).floatValue());
|
||||
break;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
writeValueInline(value, type, stream);
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private short getType(final Entry entry) {
|
||||
// TODO: What a MESS! Rewrite and expose EXIFEntry as TIFFEntry or so...
|
||||
|
||||
// For internal entries use type directly
|
||||
if (entry instanceof EXIFEntry) {
|
||||
EXIFEntry exifEntry = (EXIFEntry) entry;
|
||||
return exifEntry.getType();
|
||||
}
|
||||
|
||||
// For other entries, use name if it matches
|
||||
String typeName = entry.getTypeName();
|
||||
|
||||
if (typeName != null) {
|
||||
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||
return (short) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the native Java type
|
||||
Object value = Validate.notNull(entry.getValue());
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
if (array) {
|
||||
value = Array.get(value, 0);
|
||||
}
|
||||
|
||||
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||
// TODO: Check for negative values and use signed types?
|
||||
if (value instanceof Byte) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
if (value instanceof Short) {
|
||||
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Rational) {
|
||||
return TIFF.TYPE_RATIONAL;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return TIFF.TYPE_ASCII;
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Method getType not implemented for entry of type %s/value of type %s", entry.getClass(), value.getClass()));
|
||||
}
|
||||
|
||||
private int assertIntegerOffset(long offset) throws IIOException {
|
||||
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||
throw new IIOException("Integer overflow for TIFF stream");
|
||||
}
|
||||
|
||||
return (int) offset;
|
||||
}
|
||||
}
|
||||
}
|
@ -38,191 +38,84 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
* Represents a rational number with a {@code long} numerator and {@code long} denominator.
|
||||
* Rational numbers are stored in reduced form with the sign stored with the numerator.
|
||||
* Rationals are immutable.
|
||||
* <p/>
|
||||
* Adapted from sample code featured in
|
||||
* <a href="http://www.cs.princeton.edu/introcs/home/">"Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)</a>
|
||||
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author <a href="http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html">Robert Sedgewick and Kevin Wayne (original version)</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$
|
||||
*
|
||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.Rational instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public final class Rational extends Number implements Comparable<Rational> {
|
||||
// TODO: Document public API
|
||||
// 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;
|
||||
}
|
||||
private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate;
|
||||
|
||||
public Rational(final long pNumber) {
|
||||
this(pNumber, 1);
|
||||
this(new com.twelvemonkeys.imageio.metadata.tiff.Rational(pNumber, 1));
|
||||
}
|
||||
|
||||
public Rational(final long pNumerator, final long pDenominator) {
|
||||
if (pDenominator == 0) {
|
||||
throw new IllegalArgumentException("denominator == 0");
|
||||
}
|
||||
if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) {
|
||||
throw new IllegalArgumentException("value == Long.MIN_VALUE");
|
||||
}
|
||||
|
||||
// Reduce fractions
|
||||
long gcd = gcd(pNumerator, pDenominator);
|
||||
long num = pNumerator / gcd;
|
||||
long den = pDenominator / gcd;
|
||||
|
||||
numerator = pDenominator >= 0 ? num : -num;
|
||||
denominator = pDenominator >= 0 ? den : -den;
|
||||
this(new com.twelvemonkeys.imageio.metadata.tiff.Rational(pNumerator, pDenominator));
|
||||
}
|
||||
|
||||
private static long gcd(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return gcd(n, -m);
|
||||
}
|
||||
|
||||
return n == 0 ? m : gcd(n, m % n);
|
||||
}
|
||||
|
||||
private static long lcm(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return lcm(n, -m);
|
||||
}
|
||||
|
||||
return m * (n / gcd(m, n)); // parentheses important to avoid overflow
|
||||
private Rational(final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public long numerator() {
|
||||
return numerator;
|
||||
return delegate.numerator();
|
||||
}
|
||||
|
||||
public long denominator() {
|
||||
return denominator;
|
||||
return delegate.denominator();
|
||||
}
|
||||
|
||||
/// Number implementation
|
||||
@Override
|
||||
public byte byteValue() {
|
||||
return delegate.byteValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short shortValue() {
|
||||
return delegate.shortValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) doubleValue();
|
||||
return delegate.intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long) doubleValue();
|
||||
return delegate.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) doubleValue();
|
||||
return delegate.floatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
if (this == NaN) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
return numerator / (double) denominator;
|
||||
return delegate.doubleValue();
|
||||
}
|
||||
|
||||
/// Comparable implementation
|
||||
|
||||
public int compareTo(final Rational pOther) {
|
||||
double thisVal = doubleValue();
|
||||
double otherVal = pOther.doubleValue();
|
||||
|
||||
return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1;
|
||||
public int compareTo(Rational pOther) {
|
||||
return delegate.compareTo(pOther.delegate);
|
||||
}
|
||||
|
||||
/// Object overrides
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Float.floatToIntBits(floatValue());
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
return pOther == this || pOther instanceof Rational && compareTo((Rational) pOther) == 0;
|
||||
return pOther == this || pOther instanceof Rational && delegate.equals(((Rational) pOther).delegate);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NaN) {
|
||||
return "NaN";
|
||||
}
|
||||
|
||||
return denominator == 1 ? Long.toString(numerator) : String.format("%s/%s", numerator, denominator);
|
||||
}
|
||||
|
||||
/// Operations (adapted from http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html)
|
||||
// TODO: Naming! multiply/divide/add/subtract or times/divides/plus/minus
|
||||
|
||||
// return a * b, staving off overflow as much as possible by cross-cancellation
|
||||
public Rational times(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO) || pOther.equals(ZERO)) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
// reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2
|
||||
Rational c = new Rational(numerator, pOther.denominator);
|
||||
Rational d = new Rational(pOther.numerator, denominator);
|
||||
|
||||
return new Rational(c.numerator * d.numerator, c.denominator * d.denominator);
|
||||
}
|
||||
|
||||
// return a + b, staving off overflow
|
||||
public Rational plus(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO)) {
|
||||
return pOther;
|
||||
}
|
||||
if (pOther.equals(ZERO)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Find gcd of numerators and denominators
|
||||
long f = gcd(numerator, pOther.numerator);
|
||||
long g = gcd(denominator, pOther.denominator);
|
||||
|
||||
// add cross-product terms for numerator
|
||||
// multiply back in
|
||||
return new Rational(
|
||||
((numerator / f) * (pOther.denominator / g) + (pOther.numerator / f) * (denominator / g)) * f,
|
||||
lcm(denominator, pOther.denominator)
|
||||
);
|
||||
}
|
||||
|
||||
// return -a
|
||||
public Rational negate() {
|
||||
return new Rational(-numerator, denominator);
|
||||
}
|
||||
|
||||
// return a - b
|
||||
public Rational minus(final Rational pOther) {
|
||||
return plus(pOther.negate());
|
||||
}
|
||||
|
||||
public Rational reciprocal() {
|
||||
return new Rational(denominator, numerator);
|
||||
}
|
||||
|
||||
// return a / b
|
||||
public Rational divides(final Rational pOther) {
|
||||
if (pOther.equals(ZERO)) {
|
||||
throw new ArithmeticException("/ by zero");
|
||||
}
|
||||
|
||||
return times(pOther.reciprocal());
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
|
@ -34,207 +34,8 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||
*
|
||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFF instead.
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public interface TIFF {
|
||||
short BYTE_ORDER_MARK_BIG_ENDIAN = ('M' << 8) | 'M';
|
||||
short BYTE_ORDER_MARK_LITTLE_ENDIAN = ('I' << 8) | 'I';
|
||||
|
||||
int TIFF_MAGIC = 42;
|
||||
|
||||
short TYPE_BYTE = 1;
|
||||
short TYPE_ASCII = 2;
|
||||
short TYPE_SHORT = 3;
|
||||
short TYPE_LONG = 4;
|
||||
short TYPE_RATIONAL = 5;
|
||||
|
||||
short TYPE_SBYTE = 6;
|
||||
short TYPE_UNDEFINED = 7;
|
||||
short TYPE_SSHORT = 8;
|
||||
short TYPE_SLONG = 9;
|
||||
short TYPE_SRATIONAL = 10;
|
||||
short TYPE_FLOAT = 11;
|
||||
short TYPE_DOUBLE = 12;
|
||||
short TYPE_IFD = 13;
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
must be NUL (binary zero).
|
||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
|
||||
See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html
|
||||
13 = IFD, same as LONG
|
||||
|
||||
TODO: BigTiff specifies more types
|
||||
See http://www.awaresystems.be/imaging/tiff/bigtiff.html, http://www.remotesensing.org/libtiff/bigtiffdesign.html
|
||||
(what about 14-15??)
|
||||
16 = TIFF_LONG8, being unsigned 8byte integer
|
||||
17 = TIFF_SLONG8, being signed 8byte integer
|
||||
18 = TIFF_IFD8, being a new unsigned 8byte IFD offset.
|
||||
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
null,
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
"IFD",
|
||||
null, null,
|
||||
"LONG8", "SLONG8", "IFD8"
|
||||
};
|
||||
/** Length of the corresponding type, in bytes. */
|
||||
int[] TYPE_LENGTHS = {
|
||||
-1,
|
||||
1, 1, 2, 4, 8,
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
4,
|
||||
-1, -1,
|
||||
8, 8, 8
|
||||
};
|
||||
|
||||
/// EXIF defined TIFF tags
|
||||
|
||||
int TAG_EXIF_IFD = 34665;
|
||||
int TAG_GPS_IFD = 34853;
|
||||
int TAG_INTEROP_IFD = 40965;
|
||||
|
||||
/// A. Tags relating to image data structure:
|
||||
|
||||
int TAG_IMAGE_WIDTH = 256;
|
||||
int TAG_IMAGE_HEIGHT = 257;
|
||||
int TAG_BITS_PER_SAMPLE = 258;
|
||||
int TAG_COMPRESSION = 259;
|
||||
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||
int TAG_FILL_ORDER = 266;
|
||||
int TAG_ORIENTATION = 274;
|
||||
int TAG_SAMPLES_PER_PIXEL = 277;
|
||||
int TAG_PLANAR_CONFIGURATION = 284;
|
||||
int TAG_SAMPLE_FORMAT = 339;
|
||||
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||
int TAG_YCBCR_POSITIONING = 531;
|
||||
int TAG_X_RESOLUTION = 282;
|
||||
int TAG_Y_RESOLUTION = 283;
|
||||
int TAG_X_POSITION = 286;
|
||||
int TAG_Y_POSITION = 287;
|
||||
int TAG_RESOLUTION_UNIT = 296;
|
||||
|
||||
/// B. Tags relating to recording offset
|
||||
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
|
||||
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
int TAG_GROUP3OPTIONS = 292;
|
||||
int TAG_GROUP4OPTIONS = 293;
|
||||
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
int TAG_TRANSFER_FUNCTION = 301;
|
||||
int TAG_PREDICTOR = 317;
|
||||
int TAG_WHITE_POINT = 318;
|
||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||
int TAG_COLOR_MAP = 320;
|
||||
int TAG_INK_SET = 332;
|
||||
int TAG_INK_NAMES = 333;
|
||||
int TAG_NUMBER_OF_INKS = 334;
|
||||
int TAG_EXTRA_SAMPLES = 338;
|
||||
int TAG_TRANSFER_RANGE = 342;
|
||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||
|
||||
/// D. Other tags
|
||||
|
||||
int TAG_DATE_TIME = 306;
|
||||
int TAG_DOCUMENT_NAME = 269;
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_PAGE_NAME = 285;
|
||||
int TAG_PAGE_NUMBER = 297;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
int TAG_HOST_COMPUTER = 316;
|
||||
int TAG_COPYRIGHT = 33432;
|
||||
|
||||
int TAG_SUBFILE_TYPE = 254;
|
||||
int TAG_OLD_SUBFILE_TYPE = 255; // Deprecated NO NOT WRITE!
|
||||
int TAG_SUB_IFD = 330;
|
||||
|
||||
/**
|
||||
* XMP record.
|
||||
* @see com.twelvemonkeys.imageio.metadata.xmp.XMP
|
||||
*/
|
||||
int TAG_XMP = 700;
|
||||
|
||||
/**
|
||||
* IPTC record.
|
||||
* @see com.twelvemonkeys.imageio.metadata.iptc.IPTC
|
||||
*/
|
||||
int TAG_IPTC = 33723;
|
||||
|
||||
/**
|
||||
* Photoshop image resources.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP = 34377;
|
||||
|
||||
/**
|
||||
* Photoshop layer and mask information (byte order follows TIFF container).
|
||||
* Layer and mask information found in a typical layered Photoshop file.
|
||||
* Starts with a character string of "Adobe Photoshop Document Data Block"
|
||||
* (or "Adobe Photoshop Document Data V0002" for PSB)
|
||||
* including the null termination character.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
|
||||
|
||||
int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
|
||||
|
||||
/**
|
||||
* ICC Color Profile.
|
||||
* @see java.awt.color.ICC_Profile
|
||||
*/
|
||||
int TAG_ICC_PROFILE = 34675;
|
||||
|
||||
// Microsoft Office Document Imaging (MODI)
|
||||
// http://msdn.microsoft.com/en-us/library/aa167596%28office.11%29.aspx
|
||||
int TAG_MODI_BLC = 34718;
|
||||
int TAG_MODI_VECTOR = 34719;
|
||||
int TAG_MODI_PTC = 34720;
|
||||
|
||||
// http://blogs.msdn.com/b/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx
|
||||
int TAG_MODI_PLAIN_TEXT = 37679;
|
||||
int TAG_MODI_OLE_PROPERTY_SET = 37680;
|
||||
int TAG_MODI_TEXT_POS_INFO = 37681;
|
||||
|
||||
int TAG_TILE_WIDTH = 322;
|
||||
int TAG_TILE_HEIGTH = 323;
|
||||
int TAG_TILE_OFFSETS = 324;
|
||||
int TAG_TILE_BYTE_COUNTS = 325;
|
||||
|
||||
// JPEG
|
||||
int TAG_JPEG_TABLES = 347;
|
||||
|
||||
// "Old-style" JPEG (Obsolete) DO NOT WRITE!
|
||||
int TAG_OLD_JPEG_PROC = 512;
|
||||
int TAG_OLD_JPEG_Q_TABLES = 519;
|
||||
int TAG_OLD_JPEG_DC_TABLES = 520;
|
||||
int TAG_OLD_JPEG_AC_TABLES = 521;
|
||||
public interface TIFF extends com.twelvemonkeys.imageio.metadata.tiff.TIFF {
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMP;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
@ -42,7 +42,10 @@ import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
@ -279,13 +282,13 @@ public final class JPEGSegmentUtil {
|
||||
ImageInputStream stream = new ByteArrayImageInputStream(segment.data, segment.offset() + 1, segment.length() - 1);
|
||||
|
||||
// Root entry is TIFF, that contains the EXIF sub-IFD
|
||||
Directory tiff = new EXIFReader().read(stream);
|
||||
Directory tiff = new TIFFReader().read(stream);
|
||||
System.err.println("EXIF: " + tiff);
|
||||
}
|
||||
else if (XMP.NS_XAP.equals(segment.identifier())) {
|
||||
Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(segment.data, segment.offset(), segment.length()));
|
||||
System.err.println("XMP: " + xmp);
|
||||
System.err.println(EXIFReader.HexDump.dump(segment.data));
|
||||
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
||||
}
|
||||
else if ("Photoshop 3.0".equals(segment.identifier())) {
|
||||
// TODO: The "Photoshop 3.0" segment contains several image resources, of which one might contain
|
||||
@ -298,13 +301,13 @@ public final class JPEGSegmentUtil {
|
||||
System.err.println("colorSpace: " + colorSpace);
|
||||
}
|
||||
System.err.println("PSD: " + psd);
|
||||
System.err.println(EXIFReader.HexDump.dump(segment.data));
|
||||
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
||||
}
|
||||
else if ("ICC_PROFILE".equals(segment.identifier())) {
|
||||
// Skip
|
||||
}
|
||||
else {
|
||||
System.err.println(EXIFReader.HexDump.dump(segment.data));
|
||||
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
@ -34,14 +34,14 @@ import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* IFD
|
||||
* Represents a TIFF Image File Directory.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IFD.java,v 1.0 23.12.11 16:24 haraldk Exp$
|
||||
*/
|
||||
final class IFD extends AbstractDirectory {
|
||||
protected IFD(final Collection<? extends Entry> pEntries) {
|
||||
public final class IFD extends AbstractDirectory {
|
||||
public IFD(final Collection<? extends Entry> pEntries) {
|
||||
super(pEntries);
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted from sample code featured in
|
||||
* "Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)
|
||||
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
/**
|
||||
* Represents a rational number with a {@code long} numerator and {@code long} denominator.
|
||||
* Rational numbers are stored in reduced form with the sign stored with the numerator.
|
||||
* Rationals are immutable.
|
||||
* <p/>
|
||||
* Adapted from sample code featured in
|
||||
* <a href="http://www.cs.princeton.edu/introcs/home/">"Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)</a>
|
||||
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author <a href="http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html">Robert Sedgewick and Kevin Wayne (original version)</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$
|
||||
*/
|
||||
public final class Rational extends Number implements Comparable<Rational> {
|
||||
// TODO: Document public API
|
||||
// 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);
|
||||
}
|
||||
|
||||
public Rational(final long pNumerator, final long pDenominator) {
|
||||
if (pDenominator == 0) {
|
||||
throw new IllegalArgumentException("denominator == 0");
|
||||
}
|
||||
if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) {
|
||||
throw new IllegalArgumentException("value == Long.MIN_VALUE");
|
||||
}
|
||||
|
||||
// Reduce fractions
|
||||
long gcd = gcd(pNumerator, pDenominator);
|
||||
long num = pNumerator / gcd;
|
||||
long den = pDenominator / gcd;
|
||||
|
||||
numerator = pDenominator >= 0 ? num : -num;
|
||||
denominator = pDenominator >= 0 ? den : -den;
|
||||
}
|
||||
|
||||
private static long gcd(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return gcd(n, -m);
|
||||
}
|
||||
|
||||
return n == 0 ? m : gcd(n, m % n);
|
||||
}
|
||||
|
||||
private static long lcm(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return lcm(n, -m);
|
||||
}
|
||||
|
||||
return m * (n / gcd(m, n)); // parentheses important to avoid overflow
|
||||
}
|
||||
|
||||
public long numerator() {
|
||||
return numerator;
|
||||
}
|
||||
|
||||
public long denominator() {
|
||||
return denominator;
|
||||
}
|
||||
|
||||
/// Number implementation
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
if (this == NaN) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
return numerator / (double) denominator;
|
||||
}
|
||||
|
||||
/// Comparable implementation
|
||||
|
||||
public int compareTo(final Rational pOther) {
|
||||
double thisVal = doubleValue();
|
||||
double otherVal = pOther.doubleValue();
|
||||
|
||||
return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1;
|
||||
}
|
||||
|
||||
/// Object overrides
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Float.floatToIntBits(floatValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
return pOther == this || pOther instanceof Rational && compareTo((Rational) pOther) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == NaN) {
|
||||
return "NaN";
|
||||
}
|
||||
|
||||
return denominator == 1 ? Long.toString(numerator) : String.format("%s/%s", numerator, denominator);
|
||||
}
|
||||
|
||||
/// Operations (adapted from http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html)
|
||||
// TODO: Naming! multiply/divide/add/subtract or times/divides/plus/minus
|
||||
|
||||
// return a * b, staving off overflow as much as possible by cross-cancellation
|
||||
public Rational times(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO) || pOther.equals(ZERO)) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
// reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2
|
||||
Rational c = new Rational(numerator, pOther.denominator);
|
||||
Rational d = new Rational(pOther.numerator, denominator);
|
||||
|
||||
return new Rational(c.numerator * d.numerator, c.denominator * d.denominator);
|
||||
}
|
||||
|
||||
// return a + b, staving off overflow
|
||||
public Rational plus(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO)) {
|
||||
return pOther;
|
||||
}
|
||||
if (pOther.equals(ZERO)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Find gcd of numerators and denominators
|
||||
long f = gcd(numerator, pOther.numerator);
|
||||
long g = gcd(denominator, pOther.denominator);
|
||||
|
||||
// add cross-product terms for numerator
|
||||
// multiply back in
|
||||
return new Rational(
|
||||
((numerator / f) * (pOther.denominator / g) + (pOther.numerator / f) * (denominator / g)) * f,
|
||||
lcm(denominator, pOther.denominator)
|
||||
);
|
||||
}
|
||||
|
||||
// return -a
|
||||
public Rational negate() {
|
||||
return new Rational(-numerator, denominator);
|
||||
}
|
||||
|
||||
// return a - b
|
||||
public Rational minus(final Rational pOther) {
|
||||
return plus(pOther.negate());
|
||||
}
|
||||
|
||||
public Rational reciprocal() {
|
||||
return new Rational(denominator, numerator);
|
||||
}
|
||||
|
||||
// return a / b
|
||||
public Rational divides(final Rational pOther) {
|
||||
if (pOther.equals(ZERO)) {
|
||||
throw new ArithmeticException("/ by zero");
|
||||
}
|
||||
|
||||
return times(pOther.reciprocal());
|
||||
}
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
/**
|
||||
* TIFF
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public interface TIFF {
|
||||
short BYTE_ORDER_MARK_BIG_ENDIAN = ('M' << 8) | 'M';
|
||||
short BYTE_ORDER_MARK_LITTLE_ENDIAN = ('I' << 8) | 'I';
|
||||
|
||||
int TIFF_MAGIC = 42;
|
||||
|
||||
short TYPE_BYTE = 1;
|
||||
short TYPE_ASCII = 2;
|
||||
short TYPE_SHORT = 3;
|
||||
short TYPE_LONG = 4;
|
||||
short TYPE_RATIONAL = 5;
|
||||
|
||||
short TYPE_SBYTE = 6;
|
||||
short TYPE_UNDEFINED = 7;
|
||||
short TYPE_SSHORT = 8;
|
||||
short TYPE_SLONG = 9;
|
||||
short TYPE_SRATIONAL = 10;
|
||||
short TYPE_FLOAT = 11;
|
||||
short TYPE_DOUBLE = 12;
|
||||
short TYPE_IFD = 13;
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
must be NUL (binary zero).
|
||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
|
||||
See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html
|
||||
13 = IFD, same as LONG
|
||||
|
||||
TODO: BigTiff specifies more types
|
||||
See http://www.awaresystems.be/imaging/tiff/bigtiff.html, http://www.remotesensing.org/libtiff/bigtiffdesign.html
|
||||
(what about 14-15??)
|
||||
16 = TIFF_LONG8, being unsigned 8byte integer
|
||||
17 = TIFF_SLONG8, being signed 8byte integer
|
||||
18 = TIFF_IFD8, being a new unsigned 8byte IFD offset.
|
||||
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
null,
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
"IFD",
|
||||
null, null,
|
||||
"LONG8", "SLONG8", "IFD8"
|
||||
};
|
||||
/** Length of the corresponding type, in bytes. */
|
||||
int[] TYPE_LENGTHS = {
|
||||
-1,
|
||||
1, 1, 2, 4, 8,
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
4,
|
||||
-1, -1,
|
||||
8, 8, 8
|
||||
};
|
||||
|
||||
/// EXIF defined TIFF tags
|
||||
|
||||
int TAG_EXIF_IFD = 34665;
|
||||
int TAG_GPS_IFD = 34853;
|
||||
int TAG_INTEROP_IFD = 40965;
|
||||
|
||||
/// A. Tags relating to image data structure:
|
||||
|
||||
int TAG_IMAGE_WIDTH = 256;
|
||||
int TAG_IMAGE_HEIGHT = 257;
|
||||
int TAG_BITS_PER_SAMPLE = 258;
|
||||
int TAG_COMPRESSION = 259;
|
||||
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||
int TAG_FILL_ORDER = 266;
|
||||
int TAG_ORIENTATION = 274;
|
||||
int TAG_SAMPLES_PER_PIXEL = 277;
|
||||
int TAG_PLANAR_CONFIGURATION = 284;
|
||||
int TAG_SAMPLE_FORMAT = 339;
|
||||
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||
int TAG_YCBCR_POSITIONING = 531;
|
||||
int TAG_X_RESOLUTION = 282;
|
||||
int TAG_Y_RESOLUTION = 283;
|
||||
int TAG_X_POSITION = 286;
|
||||
int TAG_Y_POSITION = 287;
|
||||
int TAG_RESOLUTION_UNIT = 296;
|
||||
|
||||
/// B. Tags relating to recording offset
|
||||
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
|
||||
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
int TAG_GROUP3OPTIONS = 292;
|
||||
int TAG_GROUP4OPTIONS = 293;
|
||||
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
int TAG_TRANSFER_FUNCTION = 301;
|
||||
int TAG_PREDICTOR = 317;
|
||||
int TAG_WHITE_POINT = 318;
|
||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||
int TAG_COLOR_MAP = 320;
|
||||
int TAG_INK_SET = 332;
|
||||
int TAG_INK_NAMES = 333;
|
||||
int TAG_NUMBER_OF_INKS = 334;
|
||||
int TAG_EXTRA_SAMPLES = 338;
|
||||
int TAG_TRANSFER_RANGE = 342;
|
||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||
|
||||
/// D. Other tags
|
||||
|
||||
int TAG_DATE_TIME = 306;
|
||||
int TAG_DOCUMENT_NAME = 269;
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_PAGE_NAME = 285;
|
||||
int TAG_PAGE_NUMBER = 297;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
int TAG_HOST_COMPUTER = 316;
|
||||
int TAG_COPYRIGHT = 33432;
|
||||
|
||||
int TAG_SUBFILE_TYPE = 254;
|
||||
int TAG_OLD_SUBFILE_TYPE = 255; // Deprecated NO NOT WRITE!
|
||||
int TAG_SUB_IFD = 330;
|
||||
|
||||
/**
|
||||
* XMP record.
|
||||
* @see com.twelvemonkeys.imageio.metadata.xmp.XMP
|
||||
*/
|
||||
int TAG_XMP = 700;
|
||||
|
||||
/**
|
||||
* IPTC record.
|
||||
* @see com.twelvemonkeys.imageio.metadata.iptc.IPTC
|
||||
*/
|
||||
int TAG_IPTC = 33723;
|
||||
|
||||
/**
|
||||
* Photoshop image resources.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP = 34377;
|
||||
|
||||
/**
|
||||
* Photoshop layer and mask information (byte order follows TIFF container).
|
||||
* Layer and mask information found in a typical layered Photoshop file.
|
||||
* Starts with a character string of "Adobe Photoshop Document Data Block"
|
||||
* (or "Adobe Photoshop Document Data V0002" for PSB)
|
||||
* including the null termination character.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
|
||||
|
||||
int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
|
||||
|
||||
/**
|
||||
* ICC Color Profile.
|
||||
* @see java.awt.color.ICC_Profile
|
||||
*/
|
||||
int TAG_ICC_PROFILE = 34675;
|
||||
|
||||
// Microsoft Office Document Imaging (MODI)
|
||||
// http://msdn.microsoft.com/en-us/library/aa167596%28office.11%29.aspx
|
||||
int TAG_MODI_BLC = 34718;
|
||||
int TAG_MODI_VECTOR = 34719;
|
||||
int TAG_MODI_PTC = 34720;
|
||||
|
||||
// http://blogs.msdn.com/b/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx
|
||||
int TAG_MODI_PLAIN_TEXT = 37679;
|
||||
int TAG_MODI_OLE_PROPERTY_SET = 37680;
|
||||
int TAG_MODI_TEXT_POS_INFO = 37681;
|
||||
|
||||
int TAG_TILE_WIDTH = 322;
|
||||
int TAG_TILE_HEIGTH = 323;
|
||||
int TAG_TILE_OFFSETS = 324;
|
||||
int TAG_TILE_BYTE_COUNTS = 325;
|
||||
|
||||
// JPEG
|
||||
int TAG_JPEG_TABLES = 347;
|
||||
|
||||
// "Old-style" JPEG (Obsolete) DO NOT WRITE!
|
||||
int TAG_OLD_JPEG_PROC = 512;
|
||||
int TAG_OLD_JPEG_Q_TABLES = 519;
|
||||
int TAG_OLD_JPEG_DC_TABLES = 520;
|
||||
int TAG_OLD_JPEG_AC_TABLES = 521;
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractCompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
@ -34,14 +34,14 @@ import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* EXIFDirectory
|
||||
* TIFFDirectory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||
* @version $Id: TIFFDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||
*/
|
||||
final class EXIFDirectory extends AbstractCompoundDirectory {
|
||||
EXIFDirectory(final Collection<? extends Directory> directories) {
|
||||
final class TIFFDirectory extends AbstractCompoundDirectory {
|
||||
TIFFDirectory(final Collection<? extends Directory> directories) {
|
||||
super(directories);
|
||||
}
|
||||
}
|
@ -26,22 +26,54 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIF;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
/**
|
||||
* EXIFEntry
|
||||
* Represents a TIFF IFD entry.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||
* @version $Id: TIFFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||
*
|
||||
* @see TIFF
|
||||
* @see IFD
|
||||
*/
|
||||
final class EXIFEntry extends AbstractEntry {
|
||||
// TODO: Expose as TIFFEntry
|
||||
public final class TIFFEntry extends AbstractEntry {
|
||||
final private short type;
|
||||
|
||||
EXIFEntry(final int identifier, final Object value, final short type) {
|
||||
/**
|
||||
* Creates a new {@code TIFFEntry}.
|
||||
*
|
||||
* @param identifier the TIFF tag identifier.
|
||||
* @param value the value of the entry.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code value} is {@code null}.
|
||||
*
|
||||
* @see #TIFFEntry(int, short, Object)
|
||||
*/
|
||||
public TIFFEntry(final int identifier, final Object value) {
|
||||
this(identifier, guessType(value), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code TIFFEntry}.
|
||||
*
|
||||
* @param identifier the TIFF tag identifier.
|
||||
* @param type the type of the entry.
|
||||
* @param value the value of the entry.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code type} is not a legal TIFF type.
|
||||
*
|
||||
* @see TIFF
|
||||
*/
|
||||
public TIFFEntry(final int identifier, final short type, final Object value) {
|
||||
super(identifier, value);
|
||||
|
||||
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
||||
@ -284,4 +316,82 @@ final class EXIFEntry extends AbstractEntry {
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
}
|
||||
|
||||
static short getType(final Entry entry) {
|
||||
// For internal entries use type directly
|
||||
if (entry instanceof TIFFEntry) {
|
||||
TIFFEntry tiffEntry = (TIFFEntry) entry;
|
||||
return tiffEntry.getType();
|
||||
}
|
||||
|
||||
// For other entries, use name if it matches
|
||||
Validate.notNull(entry, "entry");
|
||||
String typeName = entry.getTypeName();
|
||||
|
||||
if (typeName != null) {
|
||||
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||
return (short) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to guessing based on value's type
|
||||
return guessType(entry.getValue());
|
||||
}
|
||||
|
||||
private static short guessType(final Object entryValue) {
|
||||
// Guess type based on native Java type
|
||||
Object value = Validate.notNull(entryValue);
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
if (array) {
|
||||
value = Array.get(value, 0);
|
||||
}
|
||||
|
||||
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||
// TODO: Check for negative values and use signed types?
|
||||
if (value instanceof Byte) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
if (value instanceof Short) {
|
||||
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Rational) {
|
||||
return TIFF.TYPE_RATIONAL;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return TIFF.TYPE_ASCII;
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Method guessType not implemented for type %s", value.getClass()));
|
||||
}
|
||||
|
||||
static int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType] * pCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
@ -0,0 +1,572 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||
|
||||
/**
|
||||
* TIFFReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
||||
*/
|
||||
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));
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
byte[] bom = new byte[2];
|
||||
input.readFully(bom);
|
||||
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||
input.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
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
|
||||
// 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));
|
||||
}
|
||||
|
||||
long directoryOffset = input.readUnsignedInt();
|
||||
|
||||
return readDirectory(input, directoryOffset, true);
|
||||
}
|
||||
|
||||
// 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<>();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
long nextOffset = -1;
|
||||
|
||||
int entryCount;
|
||||
try {
|
||||
entryCount = pInput.readUnsignedShort();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// Treat EOF here as empty Sub-IFD
|
||||
entryCount = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
try {
|
||||
TIFFEntry entry = readEntry(pInput);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (readLinked) {
|
||||
if (nextOffset == -1) {
|
||||
try {
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// catch EOF here as missing EOF marker
|
||||
nextOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read linked IFDs
|
||||
if (nextOffset != 0) {
|
||||
CompoundDirectory next = (CompoundDirectory) readDirectory(pInput, nextOffset, true);
|
||||
|
||||
for (int i = 0; i < next.directoryCount(); i++) {
|
||||
ifds.add((IFD) next.getDirectory(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider leaving to client code what sub-IFDs to parse (but always parse TAG_SUB_IFD).
|
||||
readSubdirectories(pInput, entries,
|
||||
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD)
|
||||
);
|
||||
|
||||
ifds.add(0, new IFD(entries));
|
||||
|
||||
return new TIFFDirectory(ifds);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
|
||||
TIFFEntry entry = (TIFFEntry) entries.get(i);
|
||||
int tagId = (Integer) entry.getIdentifier();
|
||||
|
||||
if (subIFDIds.contains(tagId)) {
|
||||
try {
|
||||
if (KNOWN_IFDS.contains(tagId)) {
|
||||
long[] pointerOffsets = getPointerOffsets(entry);
|
||||
List<IFD> subIFDs = new ArrayList<>(pointerOffsets.length);
|
||||
|
||||
for (long pointerOffset : pointerOffsets) {
|
||||
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
||||
|
||||
for (int j = 0; j < subDirectory.directoryCount(); j++) {
|
||||
subIFDs.add((IFD) subDirectory.getDirectory(j));
|
||||
}
|
||||
}
|
||||
|
||||
if (subIFDs.size() == 1) {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new TIFFEntry(tagId, entry.getType(), subIFDs.get(0)));
|
||||
}
|
||||
else {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new TIFFEntry(tagId, entry.getType(), subIFDs.toArray(new IFD[subIFDs.size()])));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
if (DEBUG) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
System.err.println("Error parsing sub-IFD: " + tagId);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long[] getPointerOffsets(final Entry entry) throws IIOException {
|
||||
long[] offsets;
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (value instanceof Byte) {
|
||||
offsets = new long[] {(Byte) value & 0xff};
|
||||
}
|
||||
else if (value instanceof Short) {
|
||||
offsets = new long[] {(Short) value & 0xffff};
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
offsets = new long[] {(Integer) value & 0xffffffffL};
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
offsets = new long[] {(Long) value};
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
offsets = (long[]) value;
|
||||
}
|
||||
else {
|
||||
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
|
||||
|
||||
// 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) {
|
||||
pInput.skipBytes(4); // read Value
|
||||
|
||||
// Invalid tag, this is just for debugging
|
||||
long offset = pInput.getStreamPosition() - 12l;
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
|
||||
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
value = readValueAt(pInput, valueOffset, type, count);
|
||||
}
|
||||
|
||||
return new TIFFEntry(tagId, type, value);
|
||||
}
|
||||
|
||||
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(pOffset);
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// TODO: Add warning listener API and report problem to client code
|
||||
return e;
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
return readValue(pInput, pType, pCount);
|
||||
}
|
||||
|
||||
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
|
||||
// TODO: New strategy: Leave data as is, instead perform the widening in TIFFEntry.getValue.
|
||||
// TODO: Add getValueByte/getValueUnsignedByte/getValueShort/getValueUnsignedShort/getValueInt/etc... in API.
|
||||
|
||||
long pos = pInput.getStreamPosition();
|
||||
|
||||
switch (pType) {
|
||||
case TIFF.TYPE_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;
|
||||
return StringUtil.decode(ascii, 0, len, "UTF-8"); // UTF-8 is ASCII compatible
|
||||
case TIFF.TYPE_BYTE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
// else fall through
|
||||
case TIFF.TYPE_SBYTE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
// else fall through
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
// NOTE: We don't change (unsigned) BYTE array wider Java type, as most often BYTE array means
|
||||
// binary data and we want to keep that as a byte array for clients to parse further
|
||||
|
||||
return bytes;
|
||||
case TIFF.TYPE_SHORT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case TIFF.TYPE_SSHORT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
|
||||
if (pType == TIFF.TYPE_SHORT) {
|
||||
int[] ints = new int[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
ints[i] = shorts[i] & 0xffff;
|
||||
}
|
||||
|
||||
return ints;
|
||||
}
|
||||
|
||||
return shorts;
|
||||
case TIFF.TYPE_IFD:
|
||||
case TIFF.TYPE_LONG:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case TIFF.TYPE_SLONG:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
|
||||
if (pType == TIFF.TYPE_LONG || pType == TIFF.TYPE_IFD) {
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = ints[i] & 0xffffffffL;
|
||||
}
|
||||
|
||||
return longs;
|
||||
}
|
||||
|
||||
return ints;
|
||||
case TIFF.TYPE_FLOAT:
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
|
||||
double[] doubles = new double[pCount];
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
if (pCount == 1) {
|
||||
return createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
Rational[] rationals = new Rational[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = createSafeRational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
if (pCount == 1) {
|
||||
return createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
Rational[] srationals = new Rational[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = createSafeRational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
return srationals;
|
||||
|
||||
// BigTiff:
|
||||
case 16: // LONG8
|
||||
case 17: // SLONG8
|
||||
case 18: // IFD8
|
||||
// TODO: Assert BigTiff (version == 43)
|
||||
|
||||
if (pCount == 1) {
|
||||
long val = pInput.readLong();
|
||||
if (pType != 17 && val < 0) {
|
||||
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
long[] longs = new long[pCount];
|
||||
for (int i = 0; i < pCount; i++) {
|
||||
longs[i] = pInput.readLong();
|
||||
}
|
||||
|
||||
return longs;
|
||||
|
||||
default:
|
||||
// Spec says skip unknown values
|
||||
return new Unknown(pType, pCount, pos);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
TIFFReader reader = new TIFFReader();
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0]));
|
||||
|
||||
long pos = 0;
|
||||
if (args.length > 1) {
|
||||
if (args[1].startsWith("0x")) {
|
||||
pos = Integer.parseInt(args[1].substring(2), 16);
|
||||
}
|
||||
else {
|
||||
pos = Long.parseLong(args[1]);
|
||||
}
|
||||
|
||||
stream.setByteOrder(pos < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
|
||||
pos = Math.abs(pos);
|
||||
|
||||
stream.seek(pos);
|
||||
}
|
||||
|
||||
try {
|
||||
Directory directory;
|
||||
|
||||
if (args.length > 1) {
|
||||
directory = reader.readDirectory(stream, pos, false);
|
||||
}
|
||||
else {
|
||||
directory = reader.read(stream);
|
||||
}
|
||||
|
||||
for (Entry entry : directory) {
|
||||
System.err.println(entry);
|
||||
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof byte[]) {
|
||||
byte[] bytes = (byte[]) value;
|
||||
System.err.println(HexDump.dump(0, bytes, 0, Math.min(bytes.length, 128)));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// TODO: Stream based hex dump util?
|
||||
public static class HexDump {
|
||||
private HexDump() {
|
||||
}
|
||||
|
||||
private static final int WIDTH = 32;
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(0, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public static String dump(long offset, byte[] bytes, int off, int len) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % WIDTH == 0) {
|
||||
if (i > 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(String.format("%08x: ", i + off + offset));
|
||||
}
|
||||
else if (i > 0 && i % 2 == 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
builder.append(String.format("%02x", bytes[i + off]));
|
||||
|
||||
int next = i + 1;
|
||||
if (next % WIDTH == 0 || next == len) {
|
||||
int leftOver = (WIDTH - (next % WIDTH)) % WIDTH;
|
||||
|
||||
if (leftOver != 0) {
|
||||
// Pad: 5 spaces for every 2 bytes... Special care if padding is non-even.
|
||||
int pad = leftOver / 2;
|
||||
|
||||
if (len % 2 != 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
for (int j = 0; j < pad; j++) {
|
||||
builder.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(" ");
|
||||
builder.append(toAsciiString(bytes, next - (WIDTH - leftOver) + off, next + off));
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String toAsciiString(final byte[] bytes, final int from, final int to) {
|
||||
byte[] range = Arrays.copyOfRange(bytes, from, to);
|
||||
|
||||
for (int i = 0; i < range.length; i++) {
|
||||
if (range[i] < 32 || range[i] > 126) {
|
||||
range[i] = '.'; // Unreadable char
|
||||
}
|
||||
}
|
||||
|
||||
return new String(range, Charset.forName("ascii"));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,414 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||
|
||||
/**
|
||||
* TIFFWriter
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
|
||||
*/
|
||||
public final class TIFFWriter extends MetadataWriter {
|
||||
|
||||
private static final int WORD_LENGTH = 2;
|
||||
private static final int LONGWORD_LENGTH = 4;
|
||||
private static final int ENTRY_LENGTH = 12;
|
||||
|
||||
public boolean write(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return write(new IFD(entries), stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(directory);
|
||||
Validate.notNull(stream);
|
||||
|
||||
// TODO: Should probably validate that the directory contains only valid TIFF entries...
|
||||
// the writer will crash on non-Integer ids and unsupported types
|
||||
// TODO: Implement the above validation in IFD constructor?
|
||||
|
||||
writeTIFFHeader(stream);
|
||||
|
||||
if (directory instanceof CompoundDirectory) {
|
||||
CompoundDirectory compoundDirectory = (CompoundDirectory) directory;
|
||||
|
||||
for (int i = 0; i < compoundDirectory.directoryCount(); i++) {
|
||||
writeIFD(compoundDirectory.getDirectory(i), stream, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
writeIFD(directory, stream, false);
|
||||
}
|
||||
|
||||
// Offset to next IFD (EOF)
|
||||
stream.writeInt(0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void writeTIFFHeader(final ImageOutputStream stream) throws IOException {
|
||||
// Header
|
||||
ByteOrder byteOrder = stream.getByteOrder();
|
||||
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
||||
stream.writeShort(42);
|
||||
}
|
||||
|
||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(entries);
|
||||
Validate.notNull(stream);
|
||||
|
||||
return writeIFD(new IFD(entries), stream, false);
|
||||
}
|
||||
|
||||
private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException {
|
||||
// TIFF spec says tags should be in increasing order, enforce that when writing
|
||||
Directory ordered = ensureOrderedDirectory(original);
|
||||
|
||||
// Compute space needed for extra storage first, then write the offset to the IFD, so that the layout is:
|
||||
// IFD offset
|
||||
// <data including sub-IFDs>
|
||||
// IFD entries (values/offsets)
|
||||
long dataOffset = stream.getStreamPosition();
|
||||
long dataSize = computeDataSize(ordered);
|
||||
|
||||
// Offset to this IFD
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
|
||||
|
||||
if (!isSubIFD) {
|
||||
stream.writeInt(assertIntegerOffset(ifdOffset));
|
||||
dataOffset += LONGWORD_LENGTH;
|
||||
|
||||
// Seek to offset
|
||||
stream.seek(ifdOffset);
|
||||
}
|
||||
else {
|
||||
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
|
||||
}
|
||||
|
||||
// Write directory
|
||||
stream.writeShort(ordered.size());
|
||||
|
||||
for (Entry entry : ordered) {
|
||||
// Write tag id
|
||||
stream.writeShort((Integer) entry.getIdentifier());
|
||||
// Write tag type
|
||||
stream.writeShort(getType(entry));
|
||||
// Write value count
|
||||
stream.writeInt(getCount(entry));
|
||||
|
||||
// Write value
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
// TODO: This could possibly be a compound directory, in which case the count should be > 1
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long streamPosition = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
writeIFD(subIFD, stream, true);
|
||||
dataOffset += computeDataSize(subIFD);
|
||||
stream.seek(streamPosition);
|
||||
}
|
||||
else {
|
||||
dataOffset += writeValue(entry, dataOffset, stream);
|
||||
}
|
||||
}
|
||||
|
||||
return ifdOffset;
|
||||
}
|
||||
|
||||
public long computeIFDSize(final Collection<Entry> directory) {
|
||||
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));
|
||||
|
||||
if (length < 0) {
|
||||
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
||||
}
|
||||
|
||||
if (length > LONGWORD_LENGTH) {
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
|
||||
dataSize += subIFDSize;
|
||||
}
|
||||
}
|
||||
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
private Directory ensureOrderedDirectory(final Directory directory) {
|
||||
if (!isSorted(directory)) {
|
||||
List<Entry> entries = new ArrayList<>(directory.size());
|
||||
|
||||
for (Entry entry : directory) {
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
Collections.sort(entries, new Comparator<Entry>() {
|
||||
public int compare(Entry left, Entry right) {
|
||||
return (Integer) left.getIdentifier() - (Integer) right.getIdentifier();
|
||||
}
|
||||
});
|
||||
|
||||
return new IFD(entries);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private boolean isSorted(final Directory directory) {
|
||||
int lastTag = 0;
|
||||
|
||||
for (Entry entry : directory) {
|
||||
int tag = ((Integer) entry.getIdentifier()) & 0xffff;
|
||||
|
||||
if (tag < lastTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lastTag = tag;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
|
||||
short type = getType(entry);
|
||||
int valueLength = getValueLength(type, getCount(entry));
|
||||
|
||||
if (valueLength <= LONGWORD_LENGTH) {
|
||||
writeValueInline(entry.getValue(), type, stream);
|
||||
|
||||
// Pad
|
||||
for (int i = valueLength; i < LONGWORD_LENGTH; i++) {
|
||||
stream.write(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
writeValueAt(dataOffset, entry.getValue(), type, stream);
|
||||
|
||||
return valueLength;
|
||||
}
|
||||
}
|
||||
|
||||
private int getCount(final Entry entry) {
|
||||
Object value = entry.getValue();
|
||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
||||
}
|
||||
|
||||
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
if (value.getClass().isArray()) {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
stream.write((byte[]) value);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
short[] shorts;
|
||||
|
||||
if (value instanceof short[]) {
|
||||
shorts = (short[]) value;
|
||||
}
|
||||
else if (value instanceof int[]) {
|
||||
int[] ints = (int[]) value;
|
||||
shorts = new short[ints.length];
|
||||
|
||||
for (int i = 0; i < ints.length; i++) {
|
||||
shorts[i] = (short) ints[i];
|
||||
}
|
||||
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
long[] longs = (long[]) value;
|
||||
shorts = new short[longs.length];
|
||||
|
||||
for (int i = 0; i < longs.length; i++) {
|
||||
shorts[i] = (short) longs[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeShorts(shorts, 0, shorts.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
int[] ints;
|
||||
|
||||
if (value instanceof int[]) {
|
||||
ints = (int[]) value;
|
||||
}
|
||||
else if (value instanceof long[]) {
|
||||
long[] longs = (long[]) value;
|
||||
ints = new int[longs.length];
|
||||
|
||||
for (int i = 0; i < longs.length; i++) {
|
||||
ints[i] = (int) longs[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeInts(ints, 0, ints.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational[] rationals = (Rational[]) value;
|
||||
for (Rational rational : rationals) {
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_FLOAT:
|
||||
float[] floats;
|
||||
|
||||
if (value instanceof float[]) {
|
||||
floats = (float[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeFloats(floats, 0, floats.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
double[] doubles;
|
||||
|
||||
if (value instanceof double[]) {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeDoubles(doubles, 0, doubles.length);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
stream.writeByte(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_ASCII:
|
||||
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.write(bytes);
|
||||
stream.write(0);
|
||||
break;
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
stream.writeShort(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational rational = (Rational) value;
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
break;
|
||||
case TIFF.TYPE_FLOAT:
|
||||
stream.writeFloat(((Number) value).floatValue());
|
||||
break;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
writeValueInline(value, type, stream);
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private int assertIntegerOffset(long offset) throws IIOException {
|
||||
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||
throw new IIOException("Integer overflow for TIFF stream");
|
||||
}
|
||||
|
||||
return (int) offset;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
/**
|
||||
* Unknown
|
@ -30,8 +30,8 @@ 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.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -149,20 +149,6 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBadDirectoryCount() throws IOException {
|
||||
// This image seems to contain bad Exif. But as other tools are able to read, so should we..
|
||||
|
@ -29,6 +29,10 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
@ -41,18 +45,16 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* EXIFWriterTest
|
||||
* TIFFWriterTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
*/
|
||||
public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
@ -61,28 +63,28 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
protected EXIFReader createReader() {
|
||||
return new EXIFReader();
|
||||
protected TIFFReader createReader() {
|
||||
return new TIFFReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EXIFWriter createWriter() {
|
||||
return new EXIFWriter();
|
||||
protected TIFFWriter createWriter() {
|
||||
return new TIFFWriter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteReadSimple() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ARTIST, "Harald K.", TIFF.TYPE_ASCII));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
new EXIFWriter().write(directory, imageStream);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@ -95,13 +97,11 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(42, data[3]);
|
||||
|
||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(5, read.size());
|
||||
|
||||
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
|
||||
@ -122,7 +122,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
public void testWriteMotorola() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
@ -130,7 +130,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||
|
||||
new EXIFWriter().write(directory, imageStream);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@ -143,7 +143,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(42, data[3]);
|
||||
|
||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
@ -157,7 +157,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
public void testWriteIntel() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
@ -165,7 +165,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||
|
||||
new EXIFWriter().write(directory, imageStream);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@ -178,7 +178,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
assertEquals(42, data[2]);
|
||||
assertEquals(0, data[3]);
|
||||
|
||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
@ -188,45 +188,15 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNesting() throws IOException {
|
||||
EXIFEntry artist = new EXIFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO", TIFF.TYPE_ASCII);
|
||||
|
||||
EXIFEntry subSubSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(artist)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubSubIFD)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubIFD)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubIFD)), TIFF.TYPE_LONG);
|
||||
|
||||
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
new EXIFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(1, read.size());
|
||||
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWriteRead() throws IOException {
|
||||
Directory original = createReader().read(getDataAsIIS());
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
||||
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
|
||||
|
||||
try {
|
||||
try (ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output)) {
|
||||
createWriter().write(original, imageOutput);
|
||||
}
|
||||
finally {
|
||||
imageOutput.close();
|
||||
}
|
||||
|
||||
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
@ -236,35 +206,16 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
@Test
|
||||
public void testComputeIFDSize() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ARTIST, "Harald K.", TIFF.TYPE_ASCII));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
|
||||
EXIFWriter writer = createWriter();
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSizeNested() throws IOException {
|
||||
EXIFEntry artist = new EXIFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO", TIFF.TYPE_ASCII);
|
||||
|
||||
EXIFEntry subSubSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(artist)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubSubIFD)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubIFD)), TIFF.TYPE_LONG);
|
||||
EXIFEntry subIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubIFD)), TIFF.TYPE_LONG);
|
||||
|
||||
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||
|
||||
EXIFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
writer.write(entries, stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* RationalTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: RationalTest.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class RationalTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testZeroDenominator() {
|
||||
new Rational(1, 0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testLongMinValueNumerator() {
|
||||
new Rational(Long.MIN_VALUE, 1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testLongMinValueDenominator() {
|
||||
new Rational(1, Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
||||
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
||||
assertEquals(new Rational(1, 2), new Rational(1000000, 2000000));
|
||||
assertEquals(new Rational(1, -2), new Rational(-1, 2));
|
||||
|
||||
Rational x = new Rational(1, -2);
|
||||
Rational y = new Rational(-1000000, 2000000);
|
||||
assertEquals(x, y);
|
||||
assertEquals(x.numerator(), y.numerator());
|
||||
assertEquals(x.denominator(), y.denominator());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsBoundaries() {
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1));
|
||||
|
||||
// NOTE: Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE... :-P
|
||||
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MIN_VALUE + 1), new Rational(1, 1));
|
||||
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MAX_VALUE), new Rational(-1, 1));
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1));
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.DirectoryAbstractTest;
|
@ -1,41 +1,34 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* RationalTestCase
|
||||
* RationalTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: RationalTestCase.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$
|
||||
* @version $Id: RationalTest.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$
|
||||
*/
|
||||
public class RationalTestCase extends TestCase {
|
||||
public class RationalTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testZeroDenominator() {
|
||||
try {
|
||||
new Rational(1, 0);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
new Rational(1, 0);
|
||||
}
|
||||
|
||||
// TODO: Find a solution to this problem, as we should be able to work with it...
|
||||
public void testLongMinValue() {
|
||||
try {
|
||||
new Rational(Long.MIN_VALUE, 1);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
try {
|
||||
new Rational(1, Long.MIN_VALUE);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testLongMinValueNumerator() {
|
||||
new Rational(Long.MIN_VALUE, 1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testLongMinValueDenominator() {
|
||||
new Rational(1, Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
||||
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
||||
@ -47,9 +40,9 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(x, y);
|
||||
assertEquals(x.numerator(), y.numerator());
|
||||
assertEquals(x.denominator(), y.denominator());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualsBoundaries() {
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1));
|
||||
|
||||
@ -59,16 +52,19 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReciprocal() {
|
||||
assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal());
|
||||
assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNegate() {
|
||||
assertEquals(new Rational(-1, 99), new Rational(1, 99).negate());
|
||||
assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlus() {
|
||||
Rational x, y;
|
||||
|
||||
@ -96,6 +92,7 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(x, x.plus(Rational.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimes() {
|
||||
Rational x, y;
|
||||
|
||||
@ -113,6 +110,7 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(Rational.ZERO, x.times(Rational.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinus() {
|
||||
// 1/6 - -4/-8 = -1/3
|
||||
Rational x = new Rational(1, 6);
|
||||
@ -123,6 +121,7 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(x, x.minus(Rational.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDivides() {
|
||||
// 3037141/3247033 / 3246599/3037547 = 841/961
|
||||
Rational x = new Rational(3037141, 3247033);
|
||||
@ -133,11 +132,8 @@ public class RationalTestCase extends TestCase {
|
||||
assertEquals(Rational.ZERO, new Rational(0, 386).divides(x));
|
||||
}
|
||||
|
||||
@Test(expected = ArithmeticException.class)
|
||||
public void testDivideZero() {
|
||||
try {
|
||||
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
||||
}
|
||||
catch (ArithmeticException expected) {
|
||||
}
|
||||
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectoryAbstractTest;
|
||||
@ -35,15 +35,15 @@ import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* EXIFDirectoryTest
|
||||
* TIFFDirectoryTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFDirectoryTest.java,v 1.0 02.01.12 16:41 haraldk Exp$
|
||||
* @version $Id: TIFFDirectoryTest.java,v 1.0 02.01.12 16:41 haraldk Exp$
|
||||
*/
|
||||
public class EXIFDirectoryTest extends CompoundDirectoryAbstractTest {
|
||||
public class TIFFDirectoryTest extends CompoundDirectoryAbstractTest {
|
||||
@Override
|
||||
protected CompoundDirectory createCompoundDirectory(Collection<Directory> directories) {
|
||||
return new EXIFDirectory(directories);
|
||||
return new TIFFDirectory(directories);
|
||||
}
|
||||
}
|
@ -26,27 +26,27 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.EntryAbstractTest;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* EXIFEntryTest
|
||||
* TIFFEntryTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFEntryTest.java,v 1.0 02.01.12 17:35 haraldk Exp$
|
||||
* @version $Id: TIFFEntryTest.java,v 1.0 02.01.12 17:35 haraldk Exp$
|
||||
*/
|
||||
public class EXIFEntryTest extends EntryAbstractTest {
|
||||
public class TIFFEntryTest extends EntryAbstractTest {
|
||||
@Override
|
||||
protected Entry createEntry(final Object value) {
|
||||
return createEXIFEntry(TIFF.TAG_COPYRIGHT, value, (short) 2);
|
||||
}
|
||||
|
||||
private EXIFEntry createEXIFEntry(final int identifier, final Object value, final int type) {
|
||||
return new EXIFEntry(identifier, value, (short) type);
|
||||
private TIFFEntry createEXIFEntry(final int identifier, final Object value, final int type) {
|
||||
return new TIFFEntry(identifier, (short) type, value);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
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.metadata.exif.EXIF;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* TIFFReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFReaderTest.java,v 1.0 23.12.11 13:50 haraldk Exp$
|
||||
*/
|
||||
public class TIFFReaderTest extends MetadataReaderAbstractTest {
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TIFFReader createReader() {
|
||||
return new TIFFReader();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCompoundDirectory() throws IOException {
|
||||
Directory exif = createReader().read(getDataAsIIS());
|
||||
assertThat(exif, instanceOf(CompoundDirectory.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectory() throws IOException {
|
||||
CompoundDirectory exif = (CompoundDirectory) createReader().read(getDataAsIIS());
|
||||
|
||||
assertEquals(2, exif.directoryCount());
|
||||
assertNotNull(exif.getDirectory(0));
|
||||
assertNotNull(exif.getDirectory(1));
|
||||
assertEquals(exif.size(), exif.getDirectory(0).size() + exif.getDirectory(1).size());
|
||||
}
|
||||
|
||||
@Test(expected = IndexOutOfBoundsException.class)
|
||||
public void testDirectoryOutOfBounds() throws IOException {
|
||||
InputStream data = getData();
|
||||
|
||||
CompoundDirectory exif = (CompoundDirectory) createReader().read(ImageIO.createImageInputStream(data));
|
||||
|
||||
assertEquals(2, exif.directoryCount());
|
||||
assertNotNull(exif.getDirectory(exif.directoryCount()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntries() throws IOException {
|
||||
CompoundDirectory exif = (CompoundDirectory) createReader().read(getDataAsIIS());
|
||||
|
||||
// From IFD0
|
||||
assertNotNull(exif.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("Adobe Photoshop CS2 Macintosh", exif.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
assertEquals(exif.getEntryById(TIFF.TAG_SOFTWARE), exif.getEntryByFieldName("Software"));
|
||||
|
||||
// From IFD1
|
||||
assertNotNull(exif.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT));
|
||||
assertEquals((long) 418, exif.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT).getValue());
|
||||
assertEquals(exif.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT), exif.getEntryByFieldName("JPEGInterchangeFormat"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIFD0() throws IOException {
|
||||
CompoundDirectory exif = (CompoundDirectory) createReader().read(getDataAsIIS());
|
||||
|
||||
Directory ifd0 = exif.getDirectory(0);
|
||||
assertNotNull(ifd0);
|
||||
|
||||
assertNotNull(ifd0.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals(3601, ifd0.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
|
||||
assertNotNull(ifd0.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
|
||||
assertEquals(4176, ifd0.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
|
||||
|
||||
// Assert 'uncompressed' (there's no TIFF image here, really)
|
||||
assertNotNull(ifd0.getEntryById(TIFF.TAG_COMPRESSION));
|
||||
assertEquals(1, ifd0.getEntryById(TIFF.TAG_COMPRESSION).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIFD1() throws IOException {
|
||||
CompoundDirectory exif = (CompoundDirectory) createReader().read(getDataAsIIS());
|
||||
|
||||
Directory ifd1 = exif.getDirectory(1);
|
||||
assertNotNull(ifd1);
|
||||
|
||||
// Assert 'JPEG compression' (thumbnail only)
|
||||
assertNotNull(ifd1.getEntryById(TIFF.TAG_COMPRESSION));
|
||||
assertEquals(6, ifd1.getEntryById(TIFF.TAG_COMPRESSION).getValue());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBadDirectoryCount() 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-bad-directory-entry-count.jpg"));
|
||||
stream.seek(4424 + 10);
|
||||
|
||||
Directory directory = createReader().read(new SubImageInputStream(stream, 214 - 6));
|
||||
assertEquals(7, directory.size()); // TIFF structure says 8, but the last entry isn't there
|
||||
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(3, exif.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTIFFWithBadExifIFD() throws IOException {
|
||||
// This image seems to contain bad TIFF data. But as other tools are able to read, so should we..
|
||||
// It seems that the EXIF data (at offset 494196 or 0x78a74) overlaps with a custom
|
||||
// Microsoft 'OLE Property Set' entry at 0x78a70 (UNDEFINED, count 5632)...
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/tiff/chifley_logo.tif"));
|
||||
Directory directory = createReader().read(stream);
|
||||
assertEquals(22, directory.size());
|
||||
|
||||
// Some (all?) of the EXIF data is duplicated in the XMP, meaning PhotoShop can probably re-create it
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(0, exif.size()); // EXIFTool reports "Warning: Bad ExifIFD directory"
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifJPEGWithInteropSubDirR98() throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-R98.jpg"));
|
||||
stream.seek(30);
|
||||
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 1360));
|
||||
assertEquals(17, directory.size());
|
||||
assertEquals(2, directory.directoryCount());
|
||||
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(23, exif.size());
|
||||
|
||||
// The interop IFD is empty (entry count is 0)
|
||||
Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
|
||||
assertNotNull(interop);
|
||||
assertEquals(2, interop.size());
|
||||
|
||||
assertNotNull(interop.getEntryById(1)); // InteropIndex
|
||||
assertEquals("ASCII", interop.getEntryById(1).getTypeName());
|
||||
assertEquals("R98", interop.getEntryById(1).getValue()); // Known values: R98, THM or R03
|
||||
|
||||
assertNotNull(interop.getEntryById(2)); // InteropVersion
|
||||
assertEquals("UNDEFINED", interop.getEntryById(2).getTypeName());
|
||||
assertArrayEquals(new byte[] {'0', '1', '0', '0'}, (byte[]) interop.getEntryById(2).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifJPEGWithInteropSubDirEmpty() throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-empty.jpg"));
|
||||
stream.seek(30);
|
||||
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 1360));
|
||||
assertEquals(11, directory.size());
|
||||
assertEquals(1, directory.directoryCount());
|
||||
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(24, exif.size());
|
||||
|
||||
// The interop IFD is empty (entry count is 0)
|
||||
Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
|
||||
assertNotNull(interop);
|
||||
assertEquals(0, interop.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifJPEGWithInteropSubDirEOF() throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-eof.jpg"));
|
||||
stream.seek(30);
|
||||
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 236));
|
||||
assertEquals(8, directory.size());
|
||||
assertEquals(1, directory.directoryCount());
|
||||
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(5, exif.size());
|
||||
|
||||
// The interop IFD isn't there (offset points to outside the TIFF structure)...
|
||||
// Have double-checked using ExifTool, which says "Warning : Bad InteropOffset SubDirectory start"
|
||||
Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
|
||||
assertNotNull(interop);
|
||||
assertEquals(0, interop.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifJPEGWithInteropSubDirBad() throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-bad.jpg"));
|
||||
stream.seek(30);
|
||||
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 12185));
|
||||
assertEquals(16, directory.size());
|
||||
assertEquals(2, directory.directoryCount());
|
||||
|
||||
Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
|
||||
assertNotNull(exif);
|
||||
assertEquals(26, exif.size());
|
||||
|
||||
// JPEG starts at offset 1666 and length 10519, interop IFD points to offset 1900...
|
||||
// Have double-checked using ExifTool, which says "Warning : Bad InteropIFD directory"
|
||||
Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
|
||||
assertNotNull(interop);
|
||||
assertEquals(0, interop.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifWithMissingEOFMarker() throws IOException {
|
||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getResource("/exif/noeof.tif"))) {
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(stream);
|
||||
assertEquals(15, directory.size());
|
||||
assertEquals(1, directory.directoryCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadExifWithEmptyTag() throws IOException {
|
||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getResource("/exif/emptyexiftag.tif"))) {
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(stream);
|
||||
assertEquals(3, directory.directoryCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadValueBeyondEOF() throws IOException {
|
||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getResource("/exif/value-beyond-eof.tif"))) {
|
||||
CompoundDirectory directory = (CompoundDirectory) createReader().read(stream);
|
||||
assertEquals(1, directory.directoryCount());
|
||||
assertEquals(5, directory.size());
|
||||
|
||||
assertEquals(1, directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION).getValue());
|
||||
assertEquals(10, directory.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
assertEquals(10, directory.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
|
||||
assertEquals(42L, directory.getEntryById(32935).getValue());
|
||||
// NOTE: Assumes current implementation, could possibly change in the future.
|
||||
assertTrue(directory.getEntryById(32934).getValue() instanceof EOFException);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* TIFFWriterTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
*/
|
||||
public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
protected TIFFReader createReader() {
|
||||
return new TIFFReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TIFFWriter createWriter() {
|
||||
return new TIFFWriter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteReadSimple() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(106, data.length);
|
||||
assertEquals('M', data[0]);
|
||||
assertEquals('M', data[1]);
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(42, data[3]);
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(5, read.size());
|
||||
|
||||
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
|
||||
assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
|
||||
assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
|
||||
assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteMotorola() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(60, data.length);
|
||||
assertEquals('M', data[0]);
|
||||
assertEquals('M', data[1]);
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(42, data[3]);
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteIntel() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(60, data.length);
|
||||
assertEquals('I', data[0]);
|
||||
assertEquals('I', data[1]);
|
||||
assertEquals(42, data[2]);
|
||||
assertEquals(0, data[3]);
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNesting() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(1, read.size());
|
||||
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWriteRead() throws IOException {
|
||||
Directory original = createReader().read(getDataAsIIS());
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
||||
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
|
||||
|
||||
try {
|
||||
createWriter().write(original, imageOutput);
|
||||
}
|
||||
finally {
|
||||
imageOutput.close();
|
||||
}
|
||||
|
||||
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertEquals(original, read);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSize() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSizeNested() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
}
|
||||
|
||||
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
streamPos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
streamPos += len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||
|
@ -29,7 +29,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
@ -56,7 +56,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// This is in essence an embedded TIFF file.
|
||||
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||
directory = new EXIFReader().read(pInput);
|
||||
directory = new TIFFReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,8 +31,8 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTC;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Node;
|
||||
@ -369,12 +369,10 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
private Node createLayerInfoNode() {
|
||||
IIOMetadataNode layers = new IIOMetadataNode("Layers");
|
||||
IIOMetadataNode node;
|
||||
|
||||
|
||||
for (PSDLayerInfo psdLayerInfo : layerInfo) {
|
||||
// TODO: Group in layer and use sub node for blend mode?
|
||||
node = new IIOMetadataNode("LayerInfo");
|
||||
IIOMetadataNode node = new IIOMetadataNode("LayerInfo");
|
||||
node.setAttribute("name", psdLayerInfo.getLayerName());
|
||||
node.setAttribute("top", String.valueOf(psdLayerInfo.top));
|
||||
node.setAttribute("left", String.valueOf(psdLayerInfo.left));
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
|
||||
/**
|
||||
* TIFFExtension
|
||||
*
|
||||
@ -81,7 +83,7 @@ interface TIFFExtension {
|
||||
|
||||
/**
|
||||
* For use with Photometric: 5 (Separated), when image data is in a color space other than CMYK.
|
||||
* See {@link com.twelvemonkeys.imageio.metadata.exif.TIFF#TAG_INK_NAMES InkNames} field for a
|
||||
* See {@link TIFF#TAG_INK_NAMES InkNames} field for a
|
||||
* description of the inks to be used.
|
||||
*/
|
||||
int INKSET_NOT_CMYK = 2;
|
||||
|
@ -29,11 +29,12 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.IFD;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@ -70,7 +71,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
* or {@link #mergeTree(String, Node)} methods.
|
||||
*/
|
||||
public TIFFImageMetadata() {
|
||||
this(new TIFFIFD(Collections.<Entry>emptyList()));
|
||||
this(new IFD(Collections.<Entry>emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +95,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
* or {@link #mergeTree(String, Node)} methods.
|
||||
*/
|
||||
public TIFFImageMetadata(final Collection<Entry> entries) {
|
||||
this(new TIFFIFD(entries));
|
||||
this(new IFD(entries));
|
||||
}
|
||||
|
||||
protected IIOMetadataNode getNativeTree() {
|
||||
@ -921,7 +922,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
// TODO: Consistency validation?
|
||||
|
||||
// Finally create a new IFD from merged values
|
||||
ifd = new TIFFIFD(entries.values());
|
||||
ifd = new IFD(entries.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -941,7 +942,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
// TODO: Consistency validation?
|
||||
|
||||
// Finally create a new IFD from merged values
|
||||
ifd = new TIFFIFD(entries.values());
|
||||
ifd = new IFD(entries.values());
|
||||
}
|
||||
|
||||
private void mergeEntries(final String formatName, final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||
@ -1027,25 +1028,25 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
int x = Math.round(xRes * scale * RATIONAL_SCALE_FACTOR);
|
||||
int y = Math.round(yRes * scale * RATIONAL_SCALE_FACTOR);
|
||||
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(x, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(x, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resUnitValue));
|
||||
new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resUnitValue));
|
||||
}
|
||||
else if (aspect != null) {
|
||||
if (aspect >= 1) {
|
||||
int v = Math.round(aspect * RATIONAL_SCALE_FACTOR);
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
|
||||
}
|
||||
else {
|
||||
int v = Math.round(RATIONAL_SCALE_FACTOR / aspect);
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(1)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(1)));
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||
}
|
||||
|
||||
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
|
||||
new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
|
||||
}
|
||||
// Else give up...
|
||||
}
|
||||
@ -1086,37 +1087,37 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
// We do all comparisons in lower case, for compatibility
|
||||
keyword = keyword.toLowerCase();
|
||||
|
||||
TIFFImageWriter.TIFFEntry entry;
|
||||
TIFFEntry entry;
|
||||
|
||||
if ("documentname".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_DOCUMENT_NAME, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_DOCUMENT_NAME, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("imagedescription".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_IMAGE_DESCRIPTION, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_IMAGE_DESCRIPTION, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("make".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MAKE, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_MAKE, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("model".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MODEL, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_MODEL, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("pagename".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_PAGE_NAME, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_PAGE_NAME, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("software".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("artist".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("hostcomputer".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_HOST_COMPUTER, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_HOST_COMPUTER, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("inknames".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_INK_NAMES, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_INK_NAMES, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else if ("copyright".equals(keyword)) {
|
||||
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_COPYRIGHT, TIFF.TYPE_ASCII, value);
|
||||
entry = new TIFFEntry(TIFF.TAG_COPYRIGHT, TIFF.TYPE_ASCII, value);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
@ -1148,7 +1149,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
entries.add(toEntry(nodes.item(i)));
|
||||
}
|
||||
|
||||
return new TIFFIFD(entries);
|
||||
return new IFD(entries);
|
||||
}
|
||||
|
||||
private Entry toEntry(final Node node) throws IIOInvalidTreeException {
|
||||
@ -1158,14 +1159,14 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
int tag = Integer.parseInt(getAttribute(node, "parentTagNumber"));
|
||||
Directory subIFD = toIFD(node);
|
||||
|
||||
return new TIFFImageWriter.TIFFEntry(tag, TIFF.TYPE_IFD, subIFD);
|
||||
return new TIFFEntry(tag, TIFF.TYPE_IFD, subIFD);
|
||||
}
|
||||
else if (name.equals("TIFFField")) {
|
||||
int tag = Integer.parseInt(getAttribute(node, "number"));
|
||||
short type = getTIFFType(node);
|
||||
Object value = getValue(node, type);
|
||||
|
||||
return value != null ? new TIFFImageWriter.TIFFEntry(tag, type, value) : null;
|
||||
return value != null ? new TIFFEntry(tag, type, value) : null;
|
||||
}
|
||||
else {
|
||||
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" or \"TIFFField\" node: " + name, node);
|
||||
@ -1337,11 +1338,4 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
public Entry getTIFFField(final int tagNumber) {
|
||||
return ifd.getEntryById(tagNumber);
|
||||
}
|
||||
|
||||
// TODO: Replace with IFD class when moved to new package and made public!
|
||||
private final static class TIFFIFD extends AbstractDirectory {
|
||||
public TIFFIFD(final Collection<Entry> entries) {
|
||||
super(entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
@ -171,7 +171,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
if (IFDs == null) {
|
||||
IFDs = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
||||
IFDs = (CompoundDirectory) new TIFFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.println("Byte order: " + imageInput.getByteOrder());
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
|
@ -30,12 +30,12 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||
@ -54,7 +54,6 @@ import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.zip.Deflater;
|
||||
@ -111,7 +110,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
/**
|
||||
* Metadata writer for sequence writing
|
||||
*/
|
||||
private EXIFWriter sequenceExifWriter = null;
|
||||
private TIFFWriter sequenceTiffWriter = null;
|
||||
|
||||
/**
|
||||
* Position of last IFD Pointer on active sequence writing
|
||||
@ -129,87 +128,21 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// TODO: Allow appending/partly overwrite of existing file...
|
||||
}
|
||||
|
||||
static final class TIFFEntry extends AbstractEntry {
|
||||
// TODO: Expose a merge of this and the EXIFEntry class...
|
||||
private final short type;
|
||||
|
||||
private static short guessType(final Object val) {
|
||||
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
|
||||
Object value = Validate.notNull(val);
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
if (array) {
|
||||
value = Array.get(value, 0);
|
||||
}
|
||||
|
||||
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||
// TODO: Check for negative values and use signed types?
|
||||
if (value instanceof Byte) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
if (value instanceof Short) {
|
||||
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Rational) {
|
||||
return TIFF.TYPE_RATIONAL;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return TIFF.TYPE_ASCII;
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
|
||||
}
|
||||
|
||||
TIFFEntry(final int identifier, final Object value) {
|
||||
this(identifier, guessType(value), value);
|
||||
}
|
||||
|
||||
TIFFEntry(int identifier, short type, Object value) {
|
||||
super(identifier, value);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
assertOutput();
|
||||
configureStreamByteOrder(streamMetadata, imageOutput);
|
||||
|
||||
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
|
||||
EXIFWriter exifWriter = new EXIFWriter();
|
||||
exifWriter.writeTIFFHeader(imageOutput);
|
||||
TIFFWriter tiffWriter = new TIFFWriter();
|
||||
tiffWriter.writeTIFFHeader(imageOutput);
|
||||
|
||||
writePage(image, param, exifWriter, imageOutput.getStreamPosition());
|
||||
writePage(image, param, tiffWriter, imageOutput.getStreamPosition());
|
||||
|
||||
imageOutput.flush();
|
||||
}
|
||||
|
||||
private long writePage(IIOImage image, ImageWriteParam param, EXIFWriter exifWriter, long lastIFDPointerOffset)
|
||||
private long writePage(IIOImage image, ImageWriteParam param, TIFFWriter tiffWriter, long lastIFDPointerOffset)
|
||||
throws IOException {
|
||||
RenderedImage renderedImage = image.getRenderedImage();
|
||||
|
||||
@ -382,14 +315,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// This implementation, allows semi-streaming-compatible uncompressed TIFFs
|
||||
long streamPosition = imageOutput.getStreamPosition();
|
||||
|
||||
long ifdSize = exifWriter.computeIFDSize(entries.values());
|
||||
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
||||
long stripOffset = streamPosition + 4 + ifdSize + 4;
|
||||
long stripByteCount = (renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7) / 8;
|
||||
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
||||
|
||||
long ifdPointer = exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||
|
||||
// If we have a previous IFD, update pointer
|
||||
@ -438,7 +371,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
||||
|
||||
long ifdPointer = exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
|
||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||
|
||||
@ -958,8 +891,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// Ignore streamMetadata. ByteOrder is determined from OutputStream
|
||||
assertOutput();
|
||||
isWritingSequence = true;
|
||||
sequenceExifWriter = new EXIFWriter();
|
||||
sequenceExifWriter.writeTIFFHeader(imageOutput);
|
||||
sequenceTiffWriter = new TIFFWriter();
|
||||
sequenceTiffWriter.writeTIFFHeader(imageOutput);
|
||||
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||
}
|
||||
|
||||
@ -973,7 +906,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
imageOutput.flushBefore(sequenceLastIFDPos);
|
||||
}
|
||||
|
||||
sequenceLastIFDPos = writePage(image, param, sequenceExifWriter, sequenceLastIFDPos);
|
||||
sequenceLastIFDPos = writePage(image, param, sequenceTiffWriter, sequenceLastIFDPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -983,7 +916,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
isWritingSequence = false;
|
||||
sequenceExifWriter = null;
|
||||
sequenceTiffWriter = null;
|
||||
sequenceLastIFDPos = -1;
|
||||
imageOutput.flush();
|
||||
}
|
||||
@ -993,7 +926,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
super.resetMembers();
|
||||
|
||||
isWritingSequence = false;
|
||||
sequenceExifWriter = null;
|
||||
sequenceTiffWriter = null;
|
||||
sequenceLastIFDPos = -1;
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,10 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.junit.Test;
|
||||
@ -49,7 +50,7 @@ public class TIFFImageMetadataTest {
|
||||
// TODO: Candidate abstract super method
|
||||
private IIOMetadata createMetadata(final String resource) throws IOException {
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
|
||||
Directory ifd = new EXIFReader().read(input);
|
||||
Directory ifd = new TIFFReader().read(input);
|
||||
// System.err.println("ifd: " + ifd);
|
||||
return new TIFFImageMetadata(ifd);
|
||||
}
|
||||
@ -477,9 +478,9 @@ public class TIFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardChromaSamplesPerPixel() {
|
||||
Set<Entry> entries = new HashSet<>();
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 4));
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8})); // This is incorrect, just making sure the correct value is selected
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 4));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8})); // This is incorrect, just making sure the correct value is selected
|
||||
|
||||
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||
assertNotNull(chromaNode);
|
||||
@ -491,8 +492,8 @@ public class TIFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardChromaSamplesPerPixelFallbackBitsPerSample() {
|
||||
Set<Entry> entries = new HashSet<>();
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
|
||||
|
||||
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||
assertNotNull(chromaNode);
|
||||
@ -504,7 +505,7 @@ public class TIFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardChromaSamplesPerPixelFallbackDefault() {
|
||||
Set<Entry> entries = new HashSet<>();
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
|
||||
|
||||
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||
assertNotNull(chromaNode);
|
||||
@ -515,7 +516,7 @@ public class TIFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardDataBitsPerSampleFallbackDefault() {
|
||||
Set<Entry> entries = new HashSet<>();
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
|
||||
|
||||
IIOMetadataNode dataNode = new TIFFImageMetadata(entries).getStandardDataNode();
|
||||
assertNotNull(dataNode);
|
||||
@ -526,7 +527,7 @@ public class TIFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardNodeSamplesPerPixelFallbackDefault() {
|
||||
Set<Entry> entries = new HashSet<>();
|
||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
|
||||
|
||||
// Just to make sure we haven't accidentally missed something
|
||||
IIOMetadataNode standardTree = (IIOMetadataNode) new TIFFImageMetadata(entries).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
@ -656,4 +657,4 @@ public class TIFFImageMetadataTest {
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
@ -131,7 +131,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
|
||||
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Directory ifds = new TIFFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
|
||||
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||
assertNotNull(resolutionUnit);
|
||||
@ -179,7 +179,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
|
||||
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Directory ifds = new TIFFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||
assertNotNull(software);
|
||||
assertEquals(softwareString, software.getValueAsString());
|
||||
@ -227,7 +227,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
|
||||
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Directory ifds = new TIFFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
|
||||
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||
assertNotNull(resolutionUnit);
|
||||
@ -278,7 +278,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
|
||||
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Directory ifds = new TIFFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||
assertNotNull(software);
|
||||
assertEquals(softwareString, software.getValueAsString());
|
||||
|
Loading…
x
Reference in New Issue
Block a user