mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
#204 TIFF metadata refactor
This commit is contained in:
parent
7a0660c4d7
commit
a86b76256b
@ -29,11 +29,14 @@
|
|||||||
package com.twelvemonkeys.contrib.tiff;
|
package com.twelvemonkeys.contrib.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.AffineTransformOp;
|
import com.twelvemonkeys.image.AffineTransformOp;
|
||||||
import com.twelvemonkeys.imageio.metadata.*;
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
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 com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
@ -44,7 +47,6 @@ import java.awt.geom.AffineTransform;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -57,7 +59,7 @@ import java.util.List;
|
|||||||
* @author last modified by $Author$
|
* @author last modified by $Author$
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class TIFFUtilities {
|
public final class TIFFUtilities {
|
||||||
private TIFFUtilities() {
|
private TIFFUtilities() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +203,7 @@ public class TIFFUtilities {
|
|||||||
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
|
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
|
||||||
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
|
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
|
||||||
|
|
||||||
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
|
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(imageInput);
|
||||||
|
|
||||||
int pageCount = IFDs.directoryCount();
|
int pageCount = IFDs.directoryCount();
|
||||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
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 {
|
public static void writePages(ImageOutputStream imageOutput, List<TIFFPage> pages) throws IOException {
|
||||||
EXIFWriter exif = new EXIFWriter();
|
TIFFWriter exif = new TIFFWriter();
|
||||||
long nextPagePos = imageOutput.getStreamPosition();
|
long nextPagePos = imageOutput.getStreamPosition();
|
||||||
if (nextPagePos == 0) {
|
if (nextPagePos == 0) {
|
||||||
exif.writeTIFFHeader(imageOutput);
|
exif.writeTIFFHeader(imageOutput);
|
||||||
@ -316,9 +318,9 @@ public class TIFFUtilities {
|
|||||||
this.stream = stream;
|
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);
|
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 {
|
private List<Entry> writeDirectoryData(Directory IFD, ImageOutputStream outputStream) throws IOException {
|
||||||
@ -506,78 +508,9 @@ public class TIFFUtilities {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
|
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
|
||||||
IFD = new AbstractDirectory(newIDFData) {
|
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@ package com.twelvemonkeys.imageio.path;
|
|||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
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.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
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
|
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) {
|
|| magic >>> 16 == TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC << 8) {
|
||||||
// TIFF version
|
// TIFF version
|
||||||
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(stream);
|
CompoundDirectory IFDs = (CompoundDirectory) new TIFFReader().read(stream);
|
||||||
|
|
||||||
Directory directory = IFDs.getDirectory(0);
|
Directory directory = IFDs.getDirectory(0);
|
||||||
Entry photoshop = directory.getEntryById(TIFF.TAG_PHOTOSHOP);
|
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.color.YCbCrConverter;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.imageio.util.IIOUtil;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
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.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
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.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
@ -790,9 +790,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
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 {
|
else {
|
||||||
ImageInputStream stream = new MemoryCacheImageInputStream(data);
|
ImageInputStream stream = new MemoryCacheImageInputStream(data);
|
||||||
CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
|
CompoundDirectory exifMetadata = (CompoundDirectory) new TIFFReader().read(stream);
|
||||||
|
|
||||||
if (exifMetadata.directoryCount() == 2) {
|
if (exifMetadata.directoryCount() == 2) {
|
||||||
Directory ifd1 = exifMetadata.getDirectory(1);
|
Directory ifd1 = exifMetadata.getDirectory(1);
|
||||||
|
@ -29,10 +29,10 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
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.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ public class EXIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
|||||||
assertNotNull(segments);
|
assertNotNull(segments);
|
||||||
assertFalse(segments.isEmpty());
|
assertFalse(segments.isEmpty());
|
||||||
|
|
||||||
EXIFReader reader = new EXIFReader();
|
TIFFReader reader = new TIFFReader();
|
||||||
InputStream data = segments.get(0).data();
|
InputStream data = segments.get(0).data();
|
||||||
if (data.read() < 0) {
|
if (data.read() < 0) {
|
||||||
throw new AssertionError("EOF!");
|
throw new AssertionError("EOF!");
|
||||||
|
@ -28,22 +28,12 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.exif;
|
package com.twelvemonkeys.imageio.metadata.exif;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFReader
|
* EXIFReader
|
||||||
@ -51,528 +41,15 @@ import java.util.*;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
* @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 {
|
public final class EXIFReader extends MetadataReader {
|
||||||
|
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.metadata.exif.debug"));
|
private final TIFFReader delegate = new TIFFReader();
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public Directory read(final ImageInputStream input) throws IOException {
|
public Directory read(final ImageInputStream input) throws IOException {
|
||||||
Validate.notNull(input, "input");
|
return delegate.read(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"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, Harald Kuhr
|
* Copyright (c) 2009, Harald Kuhr
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@ -28,20 +28,14 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.exif;
|
package com.twelvemonkeys.imageio.metadata.exif;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
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 javax.imageio.stream.ImageOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Array;
|
import java.util.Collection;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFWriter
|
* EXIFWriter
|
||||||
@ -49,430 +43,19 @@ import java.util.*;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
|
* @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 {
|
public final class EXIFWriter extends MetadataWriter {
|
||||||
|
|
||||||
static final int WORD_LENGTH = 2;
|
private final TIFFWriter delegate = new TIFFWriter();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||||
Validate.notNull(directory);
|
return delegate.write(directory, stream);
|
||||||
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 {
|
|
||||||
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;
|
public boolean write(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||||
|
return delegate.write(entries, stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,191 +38,84 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
* Represents a rational number with a {@code long} numerator and {@code long} denominator.
|
* 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.
|
* Rational numbers are stored in reduced form with the sign stored with the numerator.
|
||||||
* Rationals are immutable.
|
* 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="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$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$
|
* @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> {
|
public final class Rational extends Number implements Comparable<Rational> {
|
||||||
// TODO: Document public API
|
private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate;
|
||||||
// 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) {
|
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) {
|
public Rational(final long pNumerator, final long pDenominator) {
|
||||||
if (pDenominator == 0) {
|
this(new com.twelvemonkeys.imageio.metadata.tiff.Rational(pNumerator, pDenominator));
|
||||||
throw new IllegalArgumentException("denominator == 0");
|
|
||||||
}
|
|
||||||
if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) {
|
|
||||||
throw new IllegalArgumentException("value == Long.MIN_VALUE");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce fractions
|
private Rational(final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate) {
|
||||||
long gcd = gcd(pNumerator, pDenominator);
|
this.delegate = delegate;
|
||||||
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() {
|
public long numerator() {
|
||||||
return numerator;
|
return delegate.numerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long denominator() {
|
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
|
@Override
|
||||||
public int intValue() {
|
public int intValue() {
|
||||||
return (int) doubleValue();
|
return delegate.intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long longValue() {
|
public long longValue() {
|
||||||
return (long) doubleValue();
|
return delegate.longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float floatValue() {
|
public float floatValue() {
|
||||||
return (float) doubleValue();
|
return delegate.floatValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double doubleValue() {
|
public double doubleValue() {
|
||||||
if (this == NaN) {
|
return delegate.doubleValue();
|
||||||
return Double.NaN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return numerator / (double) denominator;
|
public int compareTo(Rational pOther) {
|
||||||
|
return delegate.compareTo(pOther.delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Float.floatToIntBits(floatValue());
|
return delegate.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object pOther) {
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (this == NaN) {
|
return delegate.toString();
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,207 +34,8 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
* @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 extends com.twelvemonkeys.imageio.metadata.tiff.TIFF {
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,9 @@ package com.twelvemonkeys.imageio.metadata.jpeg;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.PSD;
|
||||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
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.XMP;
|
||||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
@ -42,7 +42,10 @@ import javax.imageio.ImageIO;
|
|||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.color.ICC_Profile;
|
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.nio.charset.Charset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -279,13 +282,13 @@ public final class JPEGSegmentUtil {
|
|||||||
ImageInputStream stream = new ByteArrayImageInputStream(segment.data, segment.offset() + 1, segment.length() - 1);
|
ImageInputStream stream = new ByteArrayImageInputStream(segment.data, segment.offset() + 1, segment.length() - 1);
|
||||||
|
|
||||||
// Root entry is TIFF, that contains the EXIF sub-IFD
|
// 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);
|
System.err.println("EXIF: " + tiff);
|
||||||
}
|
}
|
||||||
else if (XMP.NS_XAP.equals(segment.identifier())) {
|
else if (XMP.NS_XAP.equals(segment.identifier())) {
|
||||||
Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(segment.data, segment.offset(), segment.length()));
|
Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(segment.data, segment.offset(), segment.length()));
|
||||||
System.err.println("XMP: " + xmp);
|
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())) {
|
else if ("Photoshop 3.0".equals(segment.identifier())) {
|
||||||
// TODO: The "Photoshop 3.0" segment contains several image resources, of which one might contain
|
// 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("colorSpace: " + colorSpace);
|
||||||
}
|
}
|
||||||
System.err.println("PSD: " + psd);
|
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())) {
|
else if ("ICC_PROFILE".equals(segment.identifier())) {
|
||||||
// Skip
|
// Skip
|
||||||
}
|
}
|
||||||
else {
|
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.
|
* 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.AbstractDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
@ -34,14 +34,14 @@ import com.twelvemonkeys.imageio.metadata.Entry;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IFD
|
* Represents a TIFF Image File Directory.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: IFD.java,v 1.0 23.12.11 16:24 haraldk Exp$
|
* @version $Id: IFD.java,v 1.0 23.12.11 16:24 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class IFD extends AbstractDirectory {
|
public final class IFD extends AbstractDirectory {
|
||||||
protected IFD(final Collection<? extends Entry> pEntries) {
|
public IFD(final Collection<? extends Entry> pEntries) {
|
||||||
super(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.
|
* 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.AbstractCompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
@ -34,14 +34,14 @@ import com.twelvemonkeys.imageio.metadata.Directory;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFDirectory
|
* TIFFDirectory
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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 {
|
final class TIFFDirectory extends AbstractCompoundDirectory {
|
||||||
EXIFDirectory(final Collection<? extends Directory> directories) {
|
TIFFDirectory(final Collection<? extends Directory> directories) {
|
||||||
super(directories);
|
super(directories);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,22 +26,54 @@
|
|||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* 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.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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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 {
|
public final class TIFFEntry extends AbstractEntry {
|
||||||
// TODO: Expose as TIFFEntry
|
|
||||||
final private short type;
|
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);
|
super(identifier, value);
|
||||||
|
|
||||||
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
||||||
@ -284,4 +316,82 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
public String getTypeName() {
|
public String getTypeName() {
|
||||||
return TIFF.TYPE_NAMES[type];
|
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
|
* Unknown
|
@ -30,8 +30,8 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@ -149,20 +149,6 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
|
|||||||
assertEquals("", directory.getEntryById(TIFF.TAG_IMAGE_DESCRIPTION).getValue());
|
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
|
@Test
|
||||||
public void testReadBadDirectoryCount() throws IOException {
|
public void testReadBadDirectoryCount() throws IOException {
|
||||||
// This image seems to contain bad Exif. But as other tools are able to read, so should we..
|
// 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;
|
package com.twelvemonkeys.imageio.metadata.exif;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.*;
|
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.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -41,18 +45,16 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFWriterTest
|
* TIFFWriterTest
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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 {
|
public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||||
|
|
||||||
@ -61,28 +63,28 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EXIFReader createReader() {
|
protected TIFFReader createReader() {
|
||||||
return new EXIFReader();
|
return new TIFFReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EXIFWriter createWriter() {
|
protected TIFFWriter createWriter() {
|
||||||
return new EXIFWriter();
|
return new TIFFWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteReadSimple() throws IOException {
|
public void testWriteReadSimple() throws IOException {
|
||||||
ArrayList<Entry> entries = new ArrayList<>();
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
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") {});
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
Directory directory = new AbstractDirectory(entries) {};
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
new EXIFWriter().write(directory, imageStream);
|
new TIFFWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@ -95,13 +97,11 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
assertEquals(0, data[2]);
|
assertEquals(0, data[2]);
|
||||||
assertEquals(42, data[3]);
|
assertEquals(42, data[3]);
|
||||||
|
|
||||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
assertNotNull(read);
|
assertNotNull(read);
|
||||||
assertEquals(5, read.size());
|
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));
|
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
public void testWriteMotorola() throws IOException {
|
public void testWriteMotorola() throws IOException {
|
||||||
ArrayList<Entry> entries = new ArrayList<>();
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
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) {};
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
@ -130,7 +130,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
|
|
||||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||||
|
|
||||||
new EXIFWriter().write(directory, imageStream);
|
new TIFFWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@ -143,7 +143,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
assertEquals(0, data[2]);
|
assertEquals(0, data[2]);
|
||||||
assertEquals(42, data[3]);
|
assertEquals(42, data[3]);
|
||||||
|
|
||||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
assertNotNull(read);
|
assertNotNull(read);
|
||||||
assertEquals(2, read.size());
|
assertEquals(2, read.size());
|
||||||
@ -157,7 +157,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
public void testWriteIntel() throws IOException {
|
public void testWriteIntel() throws IOException {
|
||||||
ArrayList<Entry> entries = new ArrayList<>();
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
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) {};
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
@ -165,7 +165,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
|
|
||||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||||
|
|
||||||
new EXIFWriter().write(directory, imageStream);
|
new TIFFWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@ -178,7 +178,7 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
assertEquals(42, data[2]);
|
assertEquals(42, data[2]);
|
||||||
assertEquals(0, data[3]);
|
assertEquals(0, data[3]);
|
||||||
|
|
||||||
Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
assertNotNull(read);
|
assertNotNull(read);
|
||||||
assertEquals(2, read.size());
|
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());
|
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
|
@Test
|
||||||
public void testReadWriteRead() throws IOException {
|
public void testReadWriteRead() throws IOException {
|
||||||
Directory original = createReader().read(getDataAsIIS());
|
Directory original = createReader().read(getDataAsIIS());
|
||||||
|
|
||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
||||||
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
|
|
||||||
|
|
||||||
try {
|
try (ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output)) {
|
||||||
createWriter().write(original, imageOutput);
|
createWriter().write(original, imageOutput);
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
imageOutput.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
|
|
||||||
@ -236,35 +206,16 @@ public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testComputeIFDSize() throws IOException {
|
public void testComputeIFDSize() throws IOException {
|
||||||
ArrayList<Entry> entries = new ArrayList<>();
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
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") {});
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
|
|
||||||
EXIFWriter writer = createWriter();
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
ImageOutputStream stream = new NullImageOutputStream();
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
writer.write(new IFD(entries), stream);
|
writer.write(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);
|
|
||||||
|
|
||||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
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.
|
* 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.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.DirectoryAbstractTest;
|
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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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() {
|
public void testZeroDenominator() {
|
||||||
try {
|
|
||||||
new Rational(1, 0);
|
new Rational(1, 0);
|
||||||
fail("IllegalArgumentException expected");
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException expected) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Find a solution to this problem, as we should be able to work with it...
|
// TODO: Find a solution to this problem, as we should be able to work with it...
|
||||||
public void testLongMinValue() {
|
@Test(expected = IllegalArgumentException.class)
|
||||||
try {
|
public void testLongMinValueNumerator() {
|
||||||
new Rational(Long.MIN_VALUE, 1);
|
new Rational(Long.MIN_VALUE, 1);
|
||||||
fail("IllegalArgumentException expected");
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException expected) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testLongMinValueDenominator() {
|
||||||
new Rational(1, Long.MIN_VALUE);
|
new Rational(1, Long.MIN_VALUE);
|
||||||
fail("IllegalArgumentException expected");
|
|
||||||
}
|
|
||||||
catch (IllegalArgumentException expected) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testEquals() {
|
public void testEquals() {
|
||||||
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
||||||
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
||||||
@ -47,9 +40,9 @@ public class RationalTestCase extends TestCase {
|
|||||||
assertEquals(x, y);
|
assertEquals(x, y);
|
||||||
assertEquals(x.numerator(), y.numerator());
|
assertEquals(x.numerator(), y.numerator());
|
||||||
assertEquals(x.denominator(), y.denominator());
|
assertEquals(x.denominator(), y.denominator());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testEqualsBoundaries() {
|
public void testEqualsBoundaries() {
|
||||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1));
|
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));
|
assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReciprocal() {
|
public void testReciprocal() {
|
||||||
assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal());
|
assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal());
|
||||||
assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal());
|
assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testNegate() {
|
public void testNegate() {
|
||||||
assertEquals(new Rational(-1, 99), new Rational(1, 99).negate());
|
assertEquals(new Rational(-1, 99), new Rational(1, 99).negate());
|
||||||
assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate());
|
assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testPlus() {
|
public void testPlus() {
|
||||||
Rational x, y;
|
Rational x, y;
|
||||||
|
|
||||||
@ -96,6 +92,7 @@ public class RationalTestCase extends TestCase {
|
|||||||
assertEquals(x, x.plus(Rational.ZERO));
|
assertEquals(x, x.plus(Rational.ZERO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testTimes() {
|
public void testTimes() {
|
||||||
Rational x, y;
|
Rational x, y;
|
||||||
|
|
||||||
@ -113,6 +110,7 @@ public class RationalTestCase extends TestCase {
|
|||||||
assertEquals(Rational.ZERO, x.times(Rational.ZERO));
|
assertEquals(Rational.ZERO, x.times(Rational.ZERO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testMinus() {
|
public void testMinus() {
|
||||||
// 1/6 - -4/-8 = -1/3
|
// 1/6 - -4/-8 = -1/3
|
||||||
Rational x = new Rational(1, 6);
|
Rational x = new Rational(1, 6);
|
||||||
@ -123,6 +121,7 @@ public class RationalTestCase extends TestCase {
|
|||||||
assertEquals(x, x.minus(Rational.ZERO));
|
assertEquals(x, x.minus(Rational.ZERO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testDivides() {
|
public void testDivides() {
|
||||||
// 3037141/3247033 / 3246599/3037547 = 841/961
|
// 3037141/3247033 / 3246599/3037547 = 841/961
|
||||||
Rational x = new Rational(3037141, 3247033);
|
Rational x = new Rational(3037141, 3247033);
|
||||||
@ -133,11 +132,8 @@ public class RationalTestCase extends TestCase {
|
|||||||
assertEquals(Rational.ZERO, new Rational(0, 386).divides(x));
|
assertEquals(Rational.ZERO, new Rational(0, 386).divides(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = ArithmeticException.class)
|
||||||
public void testDivideZero() {
|
public void testDivideZero() {
|
||||||
try {
|
|
||||||
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
||||||
}
|
}
|
||||||
catch (ArithmeticException expected) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -26,7 +26,7 @@
|
|||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* 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.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectoryAbstractTest;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectoryAbstractTest;
|
||||||
@ -35,15 +35,15 @@ import com.twelvemonkeys.imageio.metadata.Directory;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFDirectoryTest
|
* TIFFDirectoryTest
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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
|
@Override
|
||||||
protected CompoundDirectory createCompoundDirectory(Collection<Directory> directories) {
|
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.
|
* 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.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.EntryAbstractTest;
|
import com.twelvemonkeys.imageio.metadata.EntryAbstractTest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIFEntryTest
|
* TIFFEntryTest
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @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
|
@Override
|
||||||
protected Entry createEntry(final Object value) {
|
protected Entry createEntry(final Object value) {
|
||||||
return createEXIFEntry(TIFF.TAG_COPYRIGHT, value, (short) 2);
|
return createEXIFEntry(TIFF.TAG_COPYRIGHT, value, (short) 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EXIFEntry createEXIFEntry(final int identifier, final Object value, final int type) {
|
private TIFFEntry createEXIFEntry(final int identifier, final Object value, final int type) {
|
||||||
return new EXIFEntry(identifier, value, (short) type);
|
return new TIFFEntry(identifier, (short) type, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@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.
|
* 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;
|
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||||
|
|
@ -29,7 +29,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
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 javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -56,7 +56,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
|||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
// This is in essence an embedded TIFF file.
|
// 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)
|
// 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
|
@Override
|
||||||
|
@ -31,8 +31,8 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
|||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.iptc.IPTC;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
import com.twelvemonkeys.util.FilterIterator;
|
import com.twelvemonkeys.util.FilterIterator;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@ -369,12 +369,10 @@ public final class PSDMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
private Node createLayerInfoNode() {
|
private Node createLayerInfoNode() {
|
||||||
IIOMetadataNode layers = new IIOMetadataNode("Layers");
|
IIOMetadataNode layers = new IIOMetadataNode("Layers");
|
||||||
IIOMetadataNode node;
|
|
||||||
|
|
||||||
|
|
||||||
for (PSDLayerInfo psdLayerInfo : layerInfo) {
|
for (PSDLayerInfo psdLayerInfo : layerInfo) {
|
||||||
// TODO: Group in layer and use sub node for blend mode?
|
// 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("name", psdLayerInfo.getLayerName());
|
||||||
node.setAttribute("top", String.valueOf(psdLayerInfo.top));
|
node.setAttribute("top", String.valueOf(psdLayerInfo.top));
|
||||||
node.setAttribute("left", String.valueOf(psdLayerInfo.left));
|
node.setAttribute("left", String.valueOf(psdLayerInfo.left));
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFExtension
|
* TIFFExtension
|
||||||
*
|
*
|
||||||
@ -81,7 +83,7 @@ interface TIFFExtension {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* For use with Photometric: 5 (Separated), when image data is in a color space other than CMYK.
|
* 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.
|
* description of the inks to be used.
|
||||||
*/
|
*/
|
||||||
int INKSET_NOT_CMYK = 2;
|
int INKSET_NOT_CMYK = 2;
|
||||||
|
@ -29,11 +29,12 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.IFD;
|
||||||
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.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@ -70,7 +71,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
* or {@link #mergeTree(String, Node)} methods.
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
*/
|
*/
|
||||||
public TIFFImageMetadata() {
|
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.
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
*/
|
*/
|
||||||
public TIFFImageMetadata(final Collection<Entry> entries) {
|
public TIFFImageMetadata(final Collection<Entry> entries) {
|
||||||
this(new TIFFIFD(entries));
|
this(new IFD(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IIOMetadataNode getNativeTree() {
|
protected IIOMetadataNode getNativeTree() {
|
||||||
@ -921,7 +922,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// TODO: Consistency validation?
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
// Finally create a new IFD from merged values
|
// Finally create a new IFD from merged values
|
||||||
ifd = new TIFFIFD(entries.values());
|
ifd = new IFD(entries.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -941,7 +942,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// TODO: Consistency validation?
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
// Finally create a new IFD from merged values
|
// 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 {
|
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 x = Math.round(xRes * scale * RATIONAL_SCALE_FACTOR);
|
||||||
int y = Math.round(yRes * 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_X_RESOLUTION, new 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_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
|
||||||
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
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) {
|
else if (aspect != null) {
|
||||||
if (aspect >= 1) {
|
if (aspect >= 1) {
|
||||||
int v = Math.round(aspect * RATIONAL_SCALE_FACTOR);
|
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_X_RESOLUTION, new 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_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int v = Math.round(RATIONAL_SCALE_FACTOR / aspect);
|
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_X_RESOLUTION, new 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_Y_RESOLUTION, new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
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...
|
// Else give up...
|
||||||
}
|
}
|
||||||
@ -1086,37 +1087,37 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// We do all comparisons in lower case, for compatibility
|
// We do all comparisons in lower case, for compatibility
|
||||||
keyword = keyword.toLowerCase();
|
keyword = keyword.toLowerCase();
|
||||||
|
|
||||||
TIFFImageWriter.TIFFEntry entry;
|
TIFFEntry entry;
|
||||||
|
|
||||||
if ("documentname".equals(keyword)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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)) {
|
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 {
|
else {
|
||||||
continue;
|
continue;
|
||||||
@ -1148,7 +1149,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
entries.add(toEntry(nodes.item(i)));
|
entries.add(toEntry(nodes.item(i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TIFFIFD(entries);
|
return new IFD(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Entry toEntry(final Node node) throws IIOInvalidTreeException {
|
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"));
|
int tag = Integer.parseInt(getAttribute(node, "parentTagNumber"));
|
||||||
Directory subIFD = toIFD(node);
|
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")) {
|
else if (name.equals("TIFFField")) {
|
||||||
int tag = Integer.parseInt(getAttribute(node, "number"));
|
int tag = Integer.parseInt(getAttribute(node, "number"));
|
||||||
short type = getTIFFType(node);
|
short type = getTIFFType(node);
|
||||||
Object value = getValue(node, type);
|
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 {
|
else {
|
||||||
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" or \"TIFFField\" node: " + name, node);
|
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) {
|
public Entry getTIFFField(final int tagNumber) {
|
||||||
return ifd.getEntryById(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.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
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.iptc.IPTCReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
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.metadata.xmp.XMPReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
@ -171,7 +171,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IFDs == null) {
|
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) {
|
if (DEBUG) {
|
||||||
System.err.println("Byte order: " + imageInput.getByteOrder());
|
System.err.println("Byte order: " + imageInput.getByteOrder());
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
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 com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
@ -30,12 +30,12 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
|
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.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.stream.SubImageOutputStream;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||||
@ -54,7 +54,6 @@ import java.awt.color.ColorSpace;
|
|||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
@ -111,7 +110,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
/**
|
/**
|
||||||
* Metadata writer for sequence writing
|
* Metadata writer for sequence writing
|
||||||
*/
|
*/
|
||||||
private EXIFWriter sequenceExifWriter = null;
|
private TIFFWriter sequenceTiffWriter = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position of last IFD Pointer on active sequence writing
|
* 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...
|
// 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
|
@Override
|
||||||
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||||
assertOutput();
|
assertOutput();
|
||||||
configureStreamByteOrder(streamMetadata, imageOutput);
|
configureStreamByteOrder(streamMetadata, imageOutput);
|
||||||
|
|
||||||
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
|
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
|
||||||
EXIFWriter exifWriter = new EXIFWriter();
|
TIFFWriter tiffWriter = new TIFFWriter();
|
||||||
exifWriter.writeTIFFHeader(imageOutput);
|
tiffWriter.writeTIFFHeader(imageOutput);
|
||||||
|
|
||||||
writePage(image, param, exifWriter, imageOutput.getStreamPosition());
|
writePage(image, param, tiffWriter, imageOutput.getStreamPosition());
|
||||||
|
|
||||||
imageOutput.flush();
|
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 {
|
throws IOException {
|
||||||
RenderedImage renderedImage = image.getRenderedImage();
|
RenderedImage renderedImage = image.getRenderedImage();
|
||||||
|
|
||||||
@ -382,14 +315,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// This implementation, allows semi-streaming-compatible uncompressed TIFFs
|
// This implementation, allows semi-streaming-compatible uncompressed TIFFs
|
||||||
long streamPosition = imageOutput.getStreamPosition();
|
long streamPosition = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
long ifdSize = exifWriter.computeIFDSize(entries.values());
|
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
||||||
long stripOffset = streamPosition + 4 + ifdSize + 4;
|
long stripOffset = streamPosition + 4 + ifdSize + 4;
|
||||||
long stripByteCount = (renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7) / 8;
|
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_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
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();
|
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
// If we have a previous IFD, update pointer
|
// 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_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
|
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();
|
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
@ -958,8 +891,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Ignore streamMetadata. ByteOrder is determined from OutputStream
|
// Ignore streamMetadata. ByteOrder is determined from OutputStream
|
||||||
assertOutput();
|
assertOutput();
|
||||||
isWritingSequence = true;
|
isWritingSequence = true;
|
||||||
sequenceExifWriter = new EXIFWriter();
|
sequenceTiffWriter = new TIFFWriter();
|
||||||
sequenceExifWriter.writeTIFFHeader(imageOutput);
|
sequenceTiffWriter.writeTIFFHeader(imageOutput);
|
||||||
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,7 +906,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
imageOutput.flushBefore(sequenceLastIFDPos);
|
imageOutput.flushBefore(sequenceLastIFDPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
sequenceLastIFDPos = writePage(image, param, sequenceExifWriter, sequenceLastIFDPos);
|
sequenceLastIFDPos = writePage(image, param, sequenceTiffWriter, sequenceLastIFDPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -983,7 +916,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isWritingSequence = false;
|
isWritingSequence = false;
|
||||||
sequenceExifWriter = null;
|
sequenceTiffWriter = null;
|
||||||
sequenceLastIFDPos = -1;
|
sequenceLastIFDPos = -1;
|
||||||
imageOutput.flush();
|
imageOutput.flush();
|
||||||
}
|
}
|
||||||
@ -993,7 +926,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
super.resetMembers();
|
super.resetMembers();
|
||||||
|
|
||||||
isWritingSequence = false;
|
isWritingSequence = false;
|
||||||
sequenceExifWriter = null;
|
sequenceTiffWriter = null;
|
||||||
sequenceLastIFDPos = -1;
|
sequenceLastIFDPos = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,10 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -49,7 +50,7 @@ public class TIFFImageMetadataTest {
|
|||||||
// TODO: Candidate abstract super method
|
// TODO: Candidate abstract super method
|
||||||
private IIOMetadata createMetadata(final String resource) throws IOException {
|
private IIOMetadata createMetadata(final String resource) throws IOException {
|
||||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
|
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
|
||||||
Directory ifd = new EXIFReader().read(input);
|
Directory ifd = new TIFFReader().read(input);
|
||||||
// System.err.println("ifd: " + ifd);
|
// System.err.println("ifd: " + ifd);
|
||||||
return new TIFFImageMetadata(ifd);
|
return new TIFFImageMetadata(ifd);
|
||||||
}
|
}
|
||||||
@ -477,9 +478,9 @@ public class TIFFImageMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandardChromaSamplesPerPixel() {
|
public void testStandardChromaSamplesPerPixel() {
|
||||||
Set<Entry> entries = new HashSet<>();
|
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));
|
||||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 4));
|
entries.add(new 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_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();
|
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||||
assertNotNull(chromaNode);
|
assertNotNull(chromaNode);
|
||||||
@ -491,8 +492,8 @@ public class TIFFImageMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandardChromaSamplesPerPixelFallbackBitsPerSample() {
|
public void testStandardChromaSamplesPerPixelFallbackBitsPerSample() {
|
||||||
Set<Entry> entries = new HashSet<>();
|
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));
|
||||||
entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
|
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
|
||||||
|
|
||||||
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||||
assertNotNull(chromaNode);
|
assertNotNull(chromaNode);
|
||||||
@ -504,7 +505,7 @@ public class TIFFImageMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandardChromaSamplesPerPixelFallbackDefault() {
|
public void testStandardChromaSamplesPerPixelFallbackDefault() {
|
||||||
Set<Entry> entries = new HashSet<>();
|
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();
|
IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
|
||||||
assertNotNull(chromaNode);
|
assertNotNull(chromaNode);
|
||||||
@ -515,7 +516,7 @@ public class TIFFImageMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandardDataBitsPerSampleFallbackDefault() {
|
public void testStandardDataBitsPerSampleFallbackDefault() {
|
||||||
Set<Entry> entries = new HashSet<>();
|
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();
|
IIOMetadataNode dataNode = new TIFFImageMetadata(entries).getStandardDataNode();
|
||||||
assertNotNull(dataNode);
|
assertNotNull(dataNode);
|
||||||
@ -526,7 +527,7 @@ public class TIFFImageMetadataTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandardNodeSamplesPerPixelFallbackDefault() {
|
public void testStandardNodeSamplesPerPixelFallbackDefault() {
|
||||||
Set<Entry> entries = new HashSet<>();
|
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
|
// Just to make sure we haven't accidentally missed something
|
||||||
IIOMetadataNode standardTree = (IIOMetadataNode) new TIFFImageMetadata(entries).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
IIOMetadataNode standardTree = (IIOMetadataNode) new TIFFImageMetadata(entries).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
@ -30,9 +30,9 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
@ -131,7 +131,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
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);
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
assertNotNull(resolutionUnit);
|
assertNotNull(resolutionUnit);
|
||||||
@ -179,7 +179,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
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);
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
assertNotNull(software);
|
assertNotNull(software);
|
||||||
assertEquals(softwareString, software.getValueAsString());
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
@ -227,7 +227,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
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);
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
assertNotNull(resolutionUnit);
|
assertNotNull(resolutionUnit);
|
||||||
@ -278,7 +278,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
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);
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
assertNotNull(software);
|
assertNotNull(software);
|
||||||
assertEquals(softwareString, software.getValueAsString());
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user