mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
commit
6ef5a5a7ee
@ -422,9 +422,9 @@ Build the project (using [Maven](http://maven.apache.org/download.cgi)):
|
|||||||
|
|
||||||
$ mvn package
|
$ mvn package
|
||||||
|
|
||||||
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
|
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
|
||||||
|
|
||||||
It's possible to build using OpenJDK, but some tests will fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether. To build using JDK 8, you need to pass `-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider` to revert to the color manangement system used in Java 7.
|
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
|
||||||
|
|
||||||
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
|
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
|
||||||
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
|
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
|
||||||
|
@ -58,6 +58,11 @@
|
|||||||
<artifactId>imageio-bmp</artifactId>
|
<artifactId>imageio-bmp</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-hdr</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-icns</artifactId>
|
<artifactId>imageio-icns</artifactId>
|
||||||
|
@ -98,15 +98,6 @@ public abstract class AbstractMetadata extends IIOMetadata implements Cloneable
|
|||||||
if (!root.getNodeName().equals(formatName)) {
|
if (!root.getNodeName().equals(formatName)) {
|
||||||
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Merge both native and standard!
|
|
||||||
Node node = root.getFirstChild();
|
|
||||||
while (node != null) {
|
|
||||||
// TODO: Merge values from node into this
|
|
||||||
|
|
||||||
// Move to the next sibling
|
|
||||||
node = node.getNextSibling();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,6 +63,7 @@ public abstract class ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||||
|
ImageIO.setUseCache(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ImageWriter createImageWriter();
|
protected abstract ImageWriter createImageWriter();
|
||||||
@ -120,23 +121,20 @@ public abstract class ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
for (RenderedImage testData : getTestData()) {
|
for (RenderedImage testData : getTestData()) {
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
|
||||||
writer.setOutput(stream);
|
|
||||||
|
|
||||||
try {
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
writer.write(drawSomething((BufferedImage) testData));
|
writer.write(drawSomething((BufferedImage) testData));
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
stream.close(); // Force data to be written
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue("No image data written", buffer.size() > 0);
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testWriteNull() throws IOException {
|
public void testWriteNull() throws IOException {
|
||||||
ImageWriter writer = createImageWriter();
|
ImageWriter writer = createImageWriter();
|
||||||
|
@ -38,13 +38,14 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
|||||||
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class EXIFEntry extends AbstractEntry {
|
final class EXIFEntry extends AbstractEntry {
|
||||||
|
// TODO: Expose as TIFFEntry
|
||||||
final private short type;
|
final private short type;
|
||||||
|
|
||||||
EXIFEntry(final int identifier, final Object value, final short type) {
|
EXIFEntry(final int identifier, final Object value, final short type) {
|
||||||
super(identifier, value);
|
super(identifier, value);
|
||||||
|
|
||||||
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
||||||
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type));
|
throw new IllegalArgumentException(String.format("Illegal TIFF type: %s", type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Validate that type is applicable to value?
|
// TODO: Validate that type is applicable to value?
|
||||||
@ -114,6 +115,8 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
return "PlanarConfiguration";
|
return "PlanarConfiguration";
|
||||||
case TIFF.TAG_RESOLUTION_UNIT:
|
case TIFF.TAG_RESOLUTION_UNIT:
|
||||||
return "ResolutionUnit";
|
return "ResolutionUnit";
|
||||||
|
case TIFF.TAG_PAGE_NAME:
|
||||||
|
return "PageName";
|
||||||
case TIFF.TAG_PAGE_NUMBER:
|
case TIFF.TAG_PAGE_NUMBER:
|
||||||
return "PageNumber";
|
return "PageNumber";
|
||||||
case TIFF.TAG_SOFTWARE:
|
case TIFF.TAG_SOFTWARE:
|
||||||
@ -228,6 +231,8 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
return "DateTimeDigitized";
|
return "DateTimeDigitized";
|
||||||
case EXIF.TAG_IMAGE_NUMBER:
|
case EXIF.TAG_IMAGE_NUMBER:
|
||||||
return "ImageNumber";
|
return "ImageNumber";
|
||||||
|
case EXIF.TAG_MAKER_NOTE:
|
||||||
|
return "MakerNote";
|
||||||
case EXIF.TAG_USER_COMMENT:
|
case EXIF.TAG_USER_COMMENT:
|
||||||
return "UserComment";
|
return "UserComment";
|
||||||
|
|
||||||
|
@ -40,6 +40,7 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,9 +249,12 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFF.TYPE_UNDEFINED:
|
case TIFF.TYPE_UNDEFINED:
|
||||||
case TIFF.TYPE_BYTE:
|
case TIFF.TYPE_BYTE:
|
||||||
|
case TIFF.TYPE_SBYTE:
|
||||||
stream.write((byte[]) value);
|
stream.write((byte[]) value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TIFF.TYPE_SHORT:
|
case TIFF.TYPE_SHORT:
|
||||||
|
case TIFF.TYPE_SSHORT:
|
||||||
short[] shorts;
|
short[] shorts;
|
||||||
|
|
||||||
if (value instanceof short[]) {
|
if (value instanceof short[]) {
|
||||||
@ -279,7 +283,9 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
|
|
||||||
stream.writeShorts(shorts, 0, shorts.length);
|
stream.writeShorts(shorts, 0, shorts.length);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TIFF.TYPE_LONG:
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
int[] ints;
|
int[] ints;
|
||||||
|
|
||||||
if (value instanceof int[]) {
|
if (value instanceof int[]) {
|
||||||
@ -298,17 +304,45 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stream.writeInts(ints, 0, ints.length);
|
stream.writeInts(ints, 0, ints.length);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TIFF.TYPE_RATIONAL:
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
Rational[] rationals = (Rational[]) value;
|
Rational[] rationals = (Rational[]) value;
|
||||||
for (Rational rational : rationals) {
|
for (Rational rational : rationals) {
|
||||||
stream.writeInt((int) rational.numerator());
|
stream.writeInt((int) rational.numerator());
|
||||||
stream.writeInt((int) rational.denominator());
|
stream.writeInt((int) rational.denominator());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: More types
|
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:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||||
@ -319,27 +353,36 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
// }
|
// }
|
||||||
else {
|
else {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFF.TYPE_UNDEFINED:
|
|
||||||
case TIFF.TYPE_BYTE:
|
case TIFF.TYPE_BYTE:
|
||||||
stream.writeByte((Integer) value);
|
case TIFF.TYPE_SBYTE:
|
||||||
|
case TIFF.TYPE_UNDEFINED:
|
||||||
|
stream.writeByte(((Number) value).intValue());
|
||||||
break;
|
break;
|
||||||
case TIFF.TYPE_ASCII:
|
case TIFF.TYPE_ASCII:
|
||||||
byte[] bytes = ((String) value).getBytes(Charset.forName("UTF-8"));
|
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||||
stream.write(bytes);
|
stream.write(bytes);
|
||||||
stream.write(0);
|
stream.write(0);
|
||||||
break;
|
break;
|
||||||
case TIFF.TYPE_SHORT:
|
case TIFF.TYPE_SHORT:
|
||||||
stream.writeShort((Integer) value);
|
case TIFF.TYPE_SSHORT:
|
||||||
|
stream.writeShort(((Number) value).intValue());
|
||||||
break;
|
break;
|
||||||
case TIFF.TYPE_LONG:
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
stream.writeInt(((Number) value).intValue());
|
stream.writeInt(((Number) value).intValue());
|
||||||
break;
|
break;
|
||||||
case TIFF.TYPE_RATIONAL:
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
Rational rational = (Rational) value;
|
Rational rational = (Rational) value;
|
||||||
stream.writeInt((int) rational.numerator());
|
stream.writeInt((int) rational.numerator());
|
||||||
stream.writeInt((int) rational.denominator());
|
stream.writeInt((int) rational.denominator());
|
||||||
break;
|
break;
|
||||||
// TODO: More types
|
case TIFF.TYPE_FLOAT:
|
||||||
|
stream.writeFloat(((Number) value).floatValue());
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_DOUBLE:
|
||||||
|
stream.writeDouble(((Number) value).doubleValue());
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||||
@ -356,11 +399,26 @@ public final class EXIFWriter extends MetadataWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private short getType(final Entry entry) {
|
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) {
|
if (entry instanceof EXIFEntry) {
|
||||||
EXIFEntry exifEntry = (EXIFEntry) entry;
|
EXIFEntry exifEntry = (EXIFEntry) entry;
|
||||||
return exifEntry.getType();
|
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());
|
Object value = Validate.notNull(entry.getValue());
|
||||||
|
|
||||||
boolean array = value.getClass().isArray();
|
boolean array = value.getClass().isArray();
|
||||||
|
@ -95,6 +95,7 @@ public interface TIFF {
|
|||||||
null, null,
|
null, null,
|
||||||
"LONG8", "SLONG8", "IFD8"
|
"LONG8", "SLONG8", "IFD8"
|
||||||
};
|
};
|
||||||
|
/** Length of the corresponding type, in bytes. */
|
||||||
int[] TYPE_LENGTHS = {
|
int[] TYPE_LENGTHS = {
|
||||||
-1,
|
-1,
|
||||||
1, 1, 2, 4, 8,
|
1, 1, 2, 4, 8,
|
||||||
@ -165,6 +166,7 @@ public interface TIFF {
|
|||||||
int TAG_IMAGE_DESCRIPTION = 270;
|
int TAG_IMAGE_DESCRIPTION = 270;
|
||||||
int TAG_MAKE = 271;
|
int TAG_MAKE = 271;
|
||||||
int TAG_MODEL = 272;
|
int TAG_MODEL = 272;
|
||||||
|
int TAG_PAGE_NAME = 285;
|
||||||
int TAG_PAGE_NUMBER = 297;
|
int TAG_PAGE_NUMBER = 297;
|
||||||
int TAG_SOFTWARE = 305;
|
int TAG_SOFTWARE = 305;
|
||||||
int TAG_ARTIST = 315;
|
int TAG_ARTIST = 315;
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
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.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Calendar;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageMetadata.
|
* TIFFImageMetadata.
|
||||||
@ -21,18 +27,46 @@ import java.util.Calendar;
|
|||||||
* @author last modified by $Author: harald.kuhr$
|
* @author last modified by $Author: harald.kuhr$
|
||||||
* @version $Id: TIFFImageMetadata.java,v 1.0 17/04/15 harald.kuhr Exp$
|
* @version $Id: TIFFImageMetadata.java,v 1.0 17/04/15 harald.kuhr Exp$
|
||||||
*/
|
*/
|
||||||
final class TIFFImageMetadata extends AbstractMetadata {
|
public final class TIFFImageMetadata extends AbstractMetadata {
|
||||||
|
|
||||||
private final Directory ifd;
|
static final int RATIONAL_SCALE_FACTOR = 100000;
|
||||||
|
|
||||||
TIFFImageMetadata(final Directory ifd) {
|
private final Directory original;
|
||||||
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
private Directory ifd;
|
||||||
this.ifd = Validate.notNull(ifd, "IFD");
|
|
||||||
|
/**
|
||||||
|
* Creates an empty TIFF metadata object.
|
||||||
|
*
|
||||||
|
* Client code can update or change the metadata using the
|
||||||
|
* {@link #setFromTree(String, Node)}
|
||||||
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
|
*/
|
||||||
|
public TIFFImageMetadata() {
|
||||||
|
this(new TIFFIFD(Collections.<Entry>emptyList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public boolean isReadOnly() {
|
* Creates a TIFF metadata object, using the values from the given IFD.
|
||||||
return false;
|
*
|
||||||
|
* Client code can update or change the metadata using the
|
||||||
|
* {@link #setFromTree(String, Node)}
|
||||||
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
|
*/
|
||||||
|
public TIFFImageMetadata(final Directory ifd) {
|
||||||
|
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
||||||
|
this.ifd = Validate.notNull(ifd, "IFD");
|
||||||
|
this.original = ifd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a TIFF metadata object, using the values from the given entries.
|
||||||
|
*
|
||||||
|
* Client code can update or change the metadata using the
|
||||||
|
* {@link #setFromTree(String, Node)}
|
||||||
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
|
*/
|
||||||
|
public TIFFImageMetadata(final Collection<Entry> entries) {
|
||||||
|
this(new TIFFIFD(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IIOMetadataNode getNativeTree() {
|
protected IIOMetadataNode getNativeTree() {
|
||||||
@ -99,7 +133,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
||||||
valueNode.appendChild(elementNode);
|
valueNode.appendChild(elementNode);
|
||||||
|
|
||||||
setValue(value, unsigned, elementNode);
|
setTIFFNativeValue(value, unsigned, elementNode);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
@ -107,7 +141,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
|
||||||
valueNode.appendChild(elementNode);
|
valueNode.appendChild(elementNode);
|
||||||
|
|
||||||
setValue(val, unsigned, elementNode);
|
setTIFFNativeValue(val, unsigned, elementNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +153,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
return ifdNode;
|
return ifdNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) {
|
private void setTIFFNativeValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) {
|
||||||
if (unsigned && value instanceof Byte) {
|
if (unsigned && value instanceof Byte) {
|
||||||
elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF));
|
elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF));
|
||||||
}
|
}
|
||||||
@ -289,12 +323,12 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
||||||
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
int photometricValue = ((Number) photometricTag.getValue()).intValue(); // No default for this tag!
|
int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
|
||||||
|
|
||||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
||||||
int numChannelsValue = samplesPerPixelTag != null
|
int numChannelsValue = samplesPerPixelTag != null
|
||||||
? ((Number) samplesPerPixelTag.getValue()).intValue()
|
? getValueAsInt(samplesPerPixelTag)
|
||||||
: bitsPerSampleTag.valueCount();
|
: bitsPerSampleTag.valueCount();
|
||||||
|
|
||||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||||
@ -393,7 +427,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
||||||
int compressionValue = compressionTag == null
|
int compressionValue = compressionTag == null
|
||||||
? TIFFBaseline.COMPRESSION_NONE
|
? TIFFBaseline.COMPRESSION_NONE
|
||||||
: ((Number) compressionTag.getValue()).intValue();
|
: getValueAsInt(compressionTag);
|
||||||
|
|
||||||
// Naming is identical to JAI ImageIO metadata as far as possible
|
// Naming is identical to JAI ImageIO metadata as far as possible
|
||||||
switch (compressionValue) {
|
switch (compressionValue) {
|
||||||
@ -502,7 +536,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION);
|
Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION);
|
||||||
int planarConfigurationValue = planarConfigurationTag == null
|
int planarConfigurationValue = planarConfigurationTag == null
|
||||||
? TIFFBaseline.PLANARCONFIG_CHUNKY
|
? TIFFBaseline.PLANARCONFIG_CHUNKY
|
||||||
: ((Number) planarConfigurationTag.getValue()).intValue();
|
: getValueAsInt(planarConfigurationTag);
|
||||||
|
|
||||||
switch (planarConfigurationValue) {
|
switch (planarConfigurationValue) {
|
||||||
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
@ -519,14 +553,16 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
int photometricInterpretationValue = photometricInterpretationTag == null
|
int photometricInterpretationValue = photometricInterpretationTag == null
|
||||||
? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO
|
? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO
|
||||||
: ((Number) photometricInterpretationTag.getValue()).intValue();
|
: getValueAsInt(photometricInterpretationTag);
|
||||||
|
|
||||||
Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT);
|
Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT);
|
||||||
|
// TODO: Fix for sampleformat 1 1 1 (as int[]) ??!?!?
|
||||||
int sampleFormatValue = samleFormatTag == null
|
int sampleFormatValue = samleFormatTag == null
|
||||||
? TIFFBaseline.SAMPLEFORMAT_UINT
|
? TIFFBaseline.SAMPLEFORMAT_UINT
|
||||||
: ((Number) samleFormatTag.getValue()).intValue();
|
: getValueAsInt(samleFormatTag);
|
||||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
node.appendChild(sampleFormat);
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
switch (sampleFormatValue) {
|
switch (sampleFormatValue) {
|
||||||
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
||||||
if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
|
if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
|
||||||
@ -562,13 +598,13 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
int numChannelsValue = samplesPerPixelTag != null
|
int numChannelsValue = samplesPerPixelTag != null
|
||||||
? ((Number) samplesPerPixelTag.getValue()).intValue()
|
? getValueAsInt(samplesPerPixelTag)
|
||||||
: bitsPerSampleTag.valueCount();
|
: bitsPerSampleTag.valueCount();
|
||||||
|
|
||||||
// SampleMSB
|
// SampleMSB
|
||||||
Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER);
|
Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER);
|
||||||
int fillOrder = fillOrderTag != null
|
int fillOrder = fillOrderTag != null
|
||||||
? ((Number) fillOrderTag.getValue()).intValue()
|
? getValueAsInt(fillOrderTag)
|
||||||
: TIFFBaseline.FILL_LEFT_TO_RIGHT;
|
: TIFFBaseline.FILL_LEFT_TO_RIGHT;
|
||||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||||
node.appendChild(sampleMSB);
|
node.appendChild(sampleMSB);
|
||||||
@ -588,6 +624,22 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getValueAsInt(final Entry entry) {
|
||||||
|
Object value = entry.getValue();
|
||||||
|
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
else if (value instanceof short[]) {
|
||||||
|
return ((short[]) value)[0];
|
||||||
|
}
|
||||||
|
else if (value instanceof int[]) {
|
||||||
|
return ((int[]) value)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unsupported type: " + entry);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Candidate superclass method!
|
// TODO: Candidate superclass method!
|
||||||
private String createListValue(final int itemCount, final String... values) {
|
private String createListValue(final int itemCount, final String... values) {
|
||||||
StringBuilder buffer = new StringBuilder();
|
StringBuilder buffer = new StringBuilder();
|
||||||
@ -620,7 +672,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// ImageOrientation
|
// ImageOrientation
|
||||||
Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION);
|
Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION);
|
||||||
if (orientationTag != null) {
|
if (orientationTag != null) {
|
||||||
int orientationValue = ((Number) orientationTag.getValue()).intValue();
|
int orientationValue = getValueAsInt(orientationTag);
|
||||||
|
|
||||||
String value = null;
|
String value = null;
|
||||||
switch (orientationValue) {
|
switch (orientationValue) {
|
||||||
@ -659,7 +711,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : ((Number) resUnitTag.getValue()).intValue();
|
int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : getValueAsInt(resUnitTag);
|
||||||
if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) {
|
if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) {
|
||||||
// 10 mm in 1 cm or 25.4 mm in 1 inch
|
// 10 mm in 1 cm or 25.4 mm in 1 inch
|
||||||
double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4;
|
double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4;
|
||||||
@ -703,7 +755,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
if (extraSamplesTag != null) {
|
if (extraSamplesTag != null) {
|
||||||
int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number)
|
int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number)
|
||||||
? ((Number) extraSamplesTag.getValue()).intValue()
|
? getValueAsInt(extraSamplesTag)
|
||||||
: ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue();
|
: ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue();
|
||||||
|
|
||||||
// Other values exists, these are not alpha
|
// Other values exists, these are not alpha
|
||||||
@ -739,7 +791,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
if (subFileTypeTag != null) {
|
if (subFileTypeTag != null) {
|
||||||
// NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values...
|
// NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values...
|
||||||
String value = null;
|
String value = null;
|
||||||
int subFileTypeValue = ((Number) subFileTypeTag.getValue()).intValue();
|
int subFileTypeValue = getValueAsInt(subFileTypeTag);
|
||||||
if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) {
|
if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) {
|
||||||
value = "TransparencyMask";
|
value = "TransparencyMask";
|
||||||
}
|
}
|
||||||
@ -795,6 +847,7 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION);
|
addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_MAKE);
|
addTextEntryIfPresent(text, TIFF.TAG_MAKE);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_MODEL);
|
addTextEntryIfPresent(text, TIFF.TAG_MODEL);
|
||||||
|
addTextEntryIfPresent(text, TIFF.TAG_PAGE_NAME);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE);
|
addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_ARTIST);
|
addTextEntryIfPresent(text, TIFF.TAG_ARTIST);
|
||||||
addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER);
|
addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER);
|
||||||
@ -821,4 +874,435 @@ final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
// See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information
|
// See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information
|
||||||
return super.getStandardTileNode();
|
return super.getStandardTileNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mutation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadOnly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||||
|
// Standard validation
|
||||||
|
super.mergeTree(formatName, root);
|
||||||
|
|
||||||
|
// Set by "merging" with empty map
|
||||||
|
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>();
|
||||||
|
mergeEntries(formatName, root, entries);
|
||||||
|
|
||||||
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
|
// Finally create a new IFD from merged values
|
||||||
|
ifd = new TIFFIFD(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||||
|
// Standard validation
|
||||||
|
super.mergeTree(formatName, root);
|
||||||
|
|
||||||
|
// Clone entries (shallow clone, as entries themselves are immutable)
|
||||||
|
LinkedHashMap<Integer, Entry> entries = new LinkedHashMap<>(ifd.size() + 10);
|
||||||
|
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeEntries(formatName, root, entries);
|
||||||
|
|
||||||
|
// TODO: Consistency validation?
|
||||||
|
|
||||||
|
// Finally create a new IFD from merged values
|
||||||
|
ifd = new TIFFIFD(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeEntries(final String formatName, final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
// Merge from both native and standard trees
|
||||||
|
if (getNativeMetadataFormatName().equals(formatName)) {
|
||||||
|
mergeNativeTree(root, entries);
|
||||||
|
}
|
||||||
|
else if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||||
|
mergeStandardTree(root, entries);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Should already be checked for
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeStandardTree(final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
NodeList nodes = root.getChildNodes();
|
||||||
|
|
||||||
|
// Merge selected values from standard tree
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
Node node = nodes.item(i);
|
||||||
|
|
||||||
|
if ("Dimension".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardDimensionNode(node, entries);
|
||||||
|
}
|
||||||
|
else if ("Document".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardDocumentNode(node, entries);
|
||||||
|
}
|
||||||
|
else if ("Text".equals(node.getNodeName())) {
|
||||||
|
mergeFromStandardTextNode(node, entries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardDimensionNode(final Node dimensionNode, final Map<Integer, Entry> entries) {
|
||||||
|
// Dimension: xRes/yRes
|
||||||
|
// - If set, set res unit to pixels per cm as this better reflects values?
|
||||||
|
// - Or, convert to DPI, if we already had values in DPI??
|
||||||
|
// Also, if we have only aspect, set these values, and use unknown as unit?
|
||||||
|
// TODO: ImageOrientation => Orientation
|
||||||
|
NodeList children = dimensionNode.getChildNodes();
|
||||||
|
|
||||||
|
Float aspect = null;
|
||||||
|
Float xRes = null;
|
||||||
|
Float yRes = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
|
Node child = children.item(i);
|
||||||
|
String nodeName = child.getNodeName();
|
||||||
|
|
||||||
|
if ("PixelAspectRatio".equals(nodeName)) {
|
||||||
|
aspect = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
else if ("HorizontalPixelSize".equals(nodeName)) {
|
||||||
|
xRes = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
else if ("VerticalPixelSize".equals(nodeName)) {
|
||||||
|
yRes = Float.parseFloat(getAttribute(child, "value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have one size compute the other
|
||||||
|
if (xRes == null && yRes != null) {
|
||||||
|
xRes = yRes * (aspect != null ? aspect : 1f);
|
||||||
|
}
|
||||||
|
else if (yRes == null && xRes != null) {
|
||||||
|
yRes = xRes / (aspect != null ? aspect : 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have resolution
|
||||||
|
if (xRes != null && yRes != null) {
|
||||||
|
// If old unit was DPI, convert values and keep DPI, otherwise use PPCM
|
||||||
|
Entry resUnitEntry = entries.get(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
int resUnitValue = resUnitEntry != null && resUnitEntry.getValue() != null
|
||||||
|
&& ((Number) resUnitEntry.getValue()).intValue() == TIFFBaseline.RESOLUTION_UNIT_DPI
|
||||||
|
? TIFFBaseline.RESOLUTION_UNIT_DPI
|
||||||
|
: TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
|
||||||
|
// Units from standard format are pixels per mm, convert to cm or inches
|
||||||
|
float scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4f;
|
||||||
|
|
||||||
|
int x = Math.round(xRes * scale * RATIONAL_SCALE_FACTOR);
|
||||||
|
int y = Math.round(yRes * scale * RATIONAL_SCALE_FACTOR);
|
||||||
|
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(x, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||||
|
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resUnitValue));
|
||||||
|
}
|
||||||
|
else if (aspect != null) {
|
||||||
|
if (aspect >= 1) {
|
||||||
|
int v = Math.round(aspect * RATIONAL_SCALE_FACTOR);
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int v = Math.round(RATIONAL_SCALE_FACTOR / aspect);
|
||||||
|
entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(1)));
|
||||||
|
entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||||
|
new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
|
||||||
|
}
|
||||||
|
// Else give up...
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardDocumentNode(final Node documentNode, final Map<Integer, Entry> entries) {
|
||||||
|
// Document: SubfileType, CreationDate
|
||||||
|
NodeList children = documentNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
|
Node child = children.item(i);
|
||||||
|
String nodeName = child.getNodeName();
|
||||||
|
|
||||||
|
if ("SubimageInterpretation".equals(nodeName)) {
|
||||||
|
// TODO: SubFileType
|
||||||
|
}
|
||||||
|
else if ("ImageCreationTime".equals(nodeName)) {
|
||||||
|
// TODO: CreationDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeFromStandardTextNode(final Node textNode, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
NodeList textEntries = textNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < textEntries.getLength(); i++) {
|
||||||
|
Node textEntry = textEntries.item(i);
|
||||||
|
|
||||||
|
if (!"TextEntry".equals(textEntry.getNodeName())) {
|
||||||
|
throw new IIOInvalidTreeException("Text node should only contain TextEntry nodes", textNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String keyword = getAttribute(textEntry, "keyword");
|
||||||
|
String value = getAttribute(textEntry, "value");
|
||||||
|
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName,
|
||||||
|
// Software, Artist, HostComputer, InkNames, Copyright
|
||||||
|
if (value != null && !value.isEmpty() && keyword != null) {
|
||||||
|
// We do all comparisons in lower case, for compatibility
|
||||||
|
keyword = keyword.toLowerCase();
|
||||||
|
|
||||||
|
TIFFImageWriter.TIFFEntry entry;
|
||||||
|
|
||||||
|
if ("documentname".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_DOCUMENT_NAME, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("imagedescription".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_IMAGE_DESCRIPTION, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("make".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MAKE, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("model".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MODEL, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("pagename".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_PAGE_NAME, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("software".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("artist".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("hostcomputer".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_HOST_COMPUTER, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("inknames".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_INK_NAMES, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else if ("copyright".equals(keyword)) {
|
||||||
|
entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_COPYRIGHT, TIFF.TYPE_ASCII, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeNativeTree(final Node root, final Map<Integer, Entry> entries) throws IIOInvalidTreeException {
|
||||||
|
Directory ifd = toIFD(root.getFirstChild());
|
||||||
|
|
||||||
|
// Merge (overwrite) entries with entries from IFD
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Directory toIFD(final Node ifdNode) throws IIOInvalidTreeException {
|
||||||
|
if (ifdNode == null || !ifdNode.getNodeName().equals("TIFFIFD")) {
|
||||||
|
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
NodeList nodes = ifdNode.getChildNodes();
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.getLength(); i++) {
|
||||||
|
entries.add(toEntry(nodes.item(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TIFFIFD(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry toEntry(final Node node) throws IIOInvalidTreeException {
|
||||||
|
String name = node.getNodeName();
|
||||||
|
|
||||||
|
if (name.equals("TIFFIFD")) {
|
||||||
|
int tag = Integer.parseInt(getAttribute(node, "parentTagNumber"));
|
||||||
|
Directory subIFD = toIFD(node);
|
||||||
|
|
||||||
|
return new TIFFImageWriter.TIFFEntry(tag, TIFF.TYPE_IFD, subIFD);
|
||||||
|
}
|
||||||
|
else if (name.equals("TIFFField")) {
|
||||||
|
int tag = Integer.parseInt(getAttribute(node, "number"));
|
||||||
|
short type = getTIFFType(node);
|
||||||
|
Object value = getValue(node, type);
|
||||||
|
|
||||||
|
return value != null ? new TIFFImageWriter.TIFFEntry(tag, type, value) : null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IIOInvalidTreeException("Expected \"TIFFIFD\" or \"TIFFField\" node: " + name, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private short getTIFFType(final Node node) throws IIOInvalidTreeException {
|
||||||
|
Node containerNode = node.getFirstChild();
|
||||||
|
if (containerNode == null) {
|
||||||
|
throw new IIOInvalidTreeException("Missing value wrapper node", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
String nodeName = containerNode.getNodeName();
|
||||||
|
if (!nodeName.startsWith("TIFF")) {
|
||||||
|
throw new IIOInvalidTreeException("Unexpected value wrapper node, expected type", containerNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String typeName = nodeName.substring(4);
|
||||||
|
|
||||||
|
if (typeName.equals("Undefined")) {
|
||||||
|
return TIFF.TYPE_UNDEFINED;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeName = typeName.substring(0, typeName.length() - 1).toUpperCase();
|
||||||
|
|
||||||
|
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||||
|
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||||
|
return (short) i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOInvalidTreeException("Unknown TIFF type: " + typeName, containerNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getValue(final Node node, final short type) throws IIOInvalidTreeException {
|
||||||
|
Node child = node.getFirstChild();
|
||||||
|
|
||||||
|
if (child != null) {
|
||||||
|
String typeName = child.getNodeName();
|
||||||
|
|
||||||
|
if (type == TIFF.TYPE_UNDEFINED) {
|
||||||
|
String values = getAttribute(child, "value");
|
||||||
|
String[] vals = values.split(",\\s?");
|
||||||
|
|
||||||
|
byte[] bytes = new byte[vals.length];
|
||||||
|
for (int i = 0; i < vals.length; i++) {
|
||||||
|
bytes[i] = Byte.parseByte(vals[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
NodeList valueNodes = child.getChildNodes();
|
||||||
|
|
||||||
|
// Create array for each type
|
||||||
|
int count = valueNodes.getLength();
|
||||||
|
Object value = createArrayForType(type, count);
|
||||||
|
|
||||||
|
// Parse each value
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Node valueNode = valueNodes.item(i);
|
||||||
|
|
||||||
|
if (!typeName.startsWith(valueNode.getNodeName())) {
|
||||||
|
throw new IIOInvalidTreeException("Value node does not match container node", child);
|
||||||
|
}
|
||||||
|
|
||||||
|
String stringValue = getAttribute(valueNode, "value");
|
||||||
|
|
||||||
|
// NOTE: The reason for parsing "wider" type, is to allow for unsigned values
|
||||||
|
switch (type) {
|
||||||
|
case TIFF.TYPE_BYTE:
|
||||||
|
case TIFF.TYPE_SBYTE:
|
||||||
|
((byte[]) value)[i] = (byte) Short.parseShort(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_ASCII:
|
||||||
|
((String[]) value)[i] = stringValue;
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_SHORT:
|
||||||
|
case TIFF.TYPE_SSHORT:
|
||||||
|
((short[]) value)[i] = (short) Integer.parseInt(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
|
((int[]) value)[i] = (int) Long.parseLong(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
|
String[] numDenom = stringValue.split("/");
|
||||||
|
((Rational[]) value)[i] = numDenom.length > 1
|
||||||
|
? new Rational(Long.parseLong(numDenom[0]), Long.parseLong(numDenom[1]))
|
||||||
|
: new Rational(Long.parseLong(numDenom[0]));
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_FLOAT:
|
||||||
|
((float[]) value)[i] = Float.parseFloat(stringValue);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_DOUBLE:
|
||||||
|
((double[]) value)[i] = Double.parseDouble(stringValue);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported TIFF type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize value
|
||||||
|
if (count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (count == 1) {
|
||||||
|
return Array.get(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOInvalidTreeException("Empty TIFField node", node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object createArrayForType(final short type, final int length) {
|
||||||
|
switch (type) {
|
||||||
|
case TIFF.TYPE_ASCII:
|
||||||
|
return new String[length];
|
||||||
|
case TIFF.TYPE_BYTE:
|
||||||
|
case TIFF.TYPE_SBYTE:
|
||||||
|
case TIFF.TYPE_UNDEFINED: // Not used here, but for completeness
|
||||||
|
return new byte[length];
|
||||||
|
case TIFF.TYPE_SHORT:
|
||||||
|
case TIFF.TYPE_SSHORT:
|
||||||
|
return new short[length];
|
||||||
|
case TIFF.TYPE_LONG:
|
||||||
|
case TIFF.TYPE_SLONG:
|
||||||
|
return new int[length];
|
||||||
|
case TIFF.TYPE_IFD:
|
||||||
|
return new long[length];
|
||||||
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
case TIFF.TYPE_SRATIONAL:
|
||||||
|
return new Rational[length];
|
||||||
|
case TIFF.TYPE_FLOAT:
|
||||||
|
return new float[length];
|
||||||
|
case TIFF.TYPE_DOUBLE:
|
||||||
|
return new double[length];
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported TIFF type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAttribute(final Node node, final String attribute) {
|
||||||
|
return node instanceof Element ? ((Element) node).getAttribute(attribute) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
|
||||||
|
ifd = original;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory getIFD() {
|
||||||
|
return ifd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,7 +36,10 @@ import com.twelvemonkeys.imageio.metadata.Entry;
|
|||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
|
||||||
|
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;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
@ -64,7 +67,9 @@ import java.awt.image.*;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
@ -82,15 +87,16 @@ import java.util.zip.InflaterInputStream;
|
|||||||
* In addition, it supports many common TIFF extensions such as:
|
* In addition, it supports many common TIFF extensions such as:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Tiling</li>
|
* <li>Tiling</li>
|
||||||
|
* <li>Class F (Facsimile), CCITT T.4 and T.6 compression (types 3 and 4), 1 bit per sample</li>
|
||||||
* <li>LZW Compression (type 5)</li>
|
* <li>LZW Compression (type 5)</li>
|
||||||
* <li>"Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined</li>
|
* <li>"Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined</li>
|
||||||
* <li>JPEG Compression (type 7)</li>
|
* <li>JPEG Compression (type 7)</li>
|
||||||
* <li>ZLib (aka Adobe-style Deflate) Compression (type 8)</li>
|
* <li>ZLib (aka Adobe-style Deflate) Compression (type 8)</li>
|
||||||
* <li>Deflate Compression (type 32946)</li>
|
* <li>Deflate Compression (type 32946)</li>
|
||||||
* <li>Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression</li>
|
* <li>Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression</li>
|
||||||
* <li>Alpha channel (ExtraSamples type 1/Associated Alpha)</li>
|
* <li>Alpha channel (ExtraSamples types 1/Associated Alpha and 2/Unassociated Alpha)</li>
|
||||||
* <li>CMYK data (PhotometricInterpretation type 5/Separated)</li>
|
* <li>Class S, CMYK data (PhotometricInterpretation type 5/Separated)</li>
|
||||||
* <li>YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG</li>
|
* <li>Class Y, YCbCr data (PhotometricInterpretation type 6/YCbCr for both JPEG and other compressions</li>
|
||||||
* <li>Planar data (PlanarConfiguration type 2/Planar)</li>
|
* <li>Planar data (PlanarConfiguration type 2/Planar)</li>
|
||||||
* <li>ICC profiles (ICCProfile)</li>
|
* <li>ICC profiles (ICCProfile)</li>
|
||||||
* <li>BitsPerSample values up to 16 for most PhotometricInterpretations</li>
|
* <li>BitsPerSample values up to 16 for most PhotometricInterpretations</li>
|
||||||
@ -119,7 +125,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
|
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
|
||||||
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
||||||
// TODO: Implement readAsRaster directly
|
// TODO: Implement readAsRaster directly
|
||||||
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
|
|
||||||
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||||
|
|
||||||
// TODOs Extension support
|
// TODOs Extension support
|
||||||
@ -136,6 +141,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
|
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
|
||||||
// Source region
|
// Source region
|
||||||
// Subsampling
|
// Subsampling
|
||||||
|
// IIOMetadata (stay close to Sun's TIFF metadata)
|
||||||
|
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
||||||
|
|
||||||
@ -167,6 +173,73 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
||||||
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Entry tiffXMP = IFDs.getEntryById(TIFF.TAG_XMP);
|
||||||
|
if (tiffXMP != null) {
|
||||||
|
byte[] value = (byte[]) tiffXMP.getValue();
|
||||||
|
|
||||||
|
// The XMPReader doesn't like null-termination...
|
||||||
|
int len = value.length;
|
||||||
|
for (int i = len - 1; i > 0; i--) {
|
||||||
|
if (value[i] == 0) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(value, 0, len));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("xmp: " + xmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry tiffIPTC = IFDs.getEntryById(TIFF.TAG_IPTC);
|
||||||
|
if (tiffIPTC != null) {
|
||||||
|
Object value = tiffIPTC.getValue();
|
||||||
|
if (value instanceof short[]) {
|
||||||
|
System.err.println("short[]: " + value);
|
||||||
|
}
|
||||||
|
if (value instanceof long[]) {
|
||||||
|
// As seen in a Magick produced image...
|
||||||
|
System.err.println("long[]: " + value);
|
||||||
|
long[] longs = (long[]) value;
|
||||||
|
value = new byte[longs.length * 8];
|
||||||
|
ByteBuffer.wrap((byte[]) value).asLongBuffer().put(longs);
|
||||||
|
}
|
||||||
|
if (value instanceof float[]) {
|
||||||
|
System.err.println("float[]: " + value);
|
||||||
|
}
|
||||||
|
if (value instanceof double[]) {
|
||||||
|
System.err.println("double[]: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory iptc = new IPTCReader().read(new ByteArrayImageInputStream((byte[]) value));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("iptc: " + iptc);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry tiffPSD = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP);
|
||||||
|
if (tiffPSD != null) {
|
||||||
|
Directory psd = new PSDReader().read(new ByteArrayImageInputStream((byte[]) tiffPSD.getValue()));
|
||||||
|
System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
System.err.println("psd: " + psd);
|
||||||
|
}
|
||||||
|
Entry tiffPSD2 = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA);
|
||||||
|
if (tiffPSD2 != null) {
|
||||||
|
byte[] value = (byte[]) tiffPSD2.getValue();
|
||||||
|
String foo = "Adobe Photoshop Document Data Block";
|
||||||
|
|
||||||
|
if (Arrays.equals(foo.getBytes(StandardCharsets.US_ASCII), Arrays.copyOf(value, foo.length()))) {
|
||||||
|
System.err.println("foo: " + foo);
|
||||||
|
// int offset = foo.length() + 1;
|
||||||
|
// ImageInputStream input = new ByteArrayImageInputStream(value, offset, value.length - offset);
|
||||||
|
// input.setByteOrder(ByteOrder.LITTLE_ENDIAN); // TODO: WHY???!
|
||||||
|
// Directory psd2 = new PSDReader().read(input);
|
||||||
|
// System.err.println("-----------------------------------------------------------------------------");
|
||||||
|
// System.err.println("psd2: " + psd2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -414,11 +487,17 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
if (bitsPerSample == 16) {
|
if (bitsPerSample == 16) {
|
||||||
return DataBuffer.TYPE_SHORT;
|
return DataBuffer.TYPE_SHORT;
|
||||||
}
|
}
|
||||||
throw new IIOException("Unsupported BitPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
|
throw new IIOException("Unsupported BitsPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
|
||||||
case TIFFExtension.SAMPLEFORMAT_FP:
|
case TIFFExtension.SAMPLEFORMAT_FP:
|
||||||
throw new IIOException("Unsupported TIFF SampleFormat: (3/Floating point)");
|
throw new IIOException("Unsupported TIFF SampleFormat: 3 (Floating point)");
|
||||||
case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
|
case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
|
||||||
throw new IIOException("Unsupported TIFF SampleFormat (4/Undefined)");
|
// Spec says:
|
||||||
|
// A field value of “undefined” is a statement by the writer that it did not know how
|
||||||
|
// to interpret the data samples; for example, if it were copying an existing image. A
|
||||||
|
// reader would typically treat an image with “undefined” data as if the field were
|
||||||
|
// not present (i.e. as unsigned integer data).
|
||||||
|
// TODO: We should probably issue a warning instead, and assume SAMPLEFORMAT_UINT
|
||||||
|
throw new IIOException("Unsupported TIFF SampleFormat: 4 (Undefined)");
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat);
|
throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat);
|
||||||
}
|
}
|
||||||
@ -888,7 +967,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
imageInput.seek(realJPEGOffset);
|
imageInput.seek(realJPEGOffset);
|
||||||
|
|
||||||
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
|
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Integer.MAX_VALUE);
|
||||||
jpegReader.setInput(stream);
|
jpegReader.setInput(stream);
|
||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
@ -1500,27 +1579,30 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// param.setSourceSubsampling(sub, sub, 0, 0);
|
// param.setSourceSubsampling(sub, sub, 0, 0);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
try {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
// int width = reader.getWidth(imageNo);
|
// int width = reader.getWidth(imageNo);
|
||||||
// int height = reader.getHeight(imageNo);
|
// int height = reader.getHeight(imageNo);
|
||||||
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
||||||
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
|
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
|
||||||
|
// param.setSourceRegion(new Rectangle(3, 3, 9, 9));
|
||||||
// param.setDestinationOffset(new Point(50, 150));
|
// param.setDestinationOffset(new Point(50, 150));
|
||||||
// param.setSourceSubsampling(2, 2, 0, 0);
|
// param.setSourceSubsampling(2, 2, 0, 0);
|
||||||
BufferedImage image = reader.read(imageNo, param);
|
BufferedImage image = reader.read(imageNo, param);
|
||||||
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
|
|
||||||
IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
if (metadata.getNativeMetadataFormatName() != null) {
|
if (metadata.getNativeMetadataFormatName() != null) {
|
||||||
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
|
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
|
||||||
|
}
|
||||||
|
/*else*/
|
||||||
|
if (metadata.isStandardMetadataFormatSupported()) {
|
||||||
|
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/*else*/ if (metadata.isStandardMetadataFormatSupported()) {
|
|
||||||
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("image: " + image);
|
System.err.println("image: " + image);
|
||||||
|
|
||||||
// File tempFile = File.createTempFile("lzw-", ".bin");
|
// File tempFile = File.createTempFile("lzw-", ".bin");
|
||||||
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||||
@ -1536,7 +1618,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
//
|
//
|
||||||
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
|
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
|
||||||
|
|
||||||
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||||
//
|
//
|
||||||
// int maxW = 800;
|
// int maxW = 800;
|
||||||
// int maxH = 800;
|
// int maxH = 800;
|
||||||
@ -1553,30 +1635,35 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
|
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
|
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
|
||||||
start = System.currentTimeMillis();
|
start = System.currentTimeMillis();
|
||||||
image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
|
image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
|
||||||
System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
|
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int numThumbnails = reader.getNumThumbnails(0);
|
int numThumbnails = reader.getNumThumbnails(0);
|
||||||
for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
|
for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
|
||||||
BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
|
BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
|
||||||
// System.err.println("thumbnail: " + thumbnail);
|
// System.err.println("thumbnail: " + thumbnail);
|
||||||
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOException e) {
|
||||||
|
System.err.println("Could not read thumbnails: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IIOException e) {
|
catch (Throwable t) {
|
||||||
System.err.println("Could not read thumbnails: " + e.getMessage());
|
System.err.println(file + " image " + imageNo + " can't be read:");
|
||||||
e.printStackTrace();
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Throwable t) {
|
catch (Throwable t) {
|
||||||
System.err.println(file);
|
System.err.println(file + " can't be read:");
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@ -31,6 +31,7 @@ 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.AbstractEntry;
|
||||||
|
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.exif.EXIFWriter;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
@ -39,9 +40,12 @@ 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;
|
||||||
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
@ -50,10 +54,9 @@ 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.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
|
||||||
@ -65,13 +68,16 @@ import java.util.zip.DeflaterOutputStream;
|
|||||||
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public final class TIFFImageWriter extends ImageWriterBase {
|
public final class TIFFImageWriter extends ImageWriterBase {
|
||||||
|
// Short term
|
||||||
|
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
||||||
|
|
||||||
// Long term
|
// Long term
|
||||||
// TODO: Support tiling
|
// TODO: Support tiling
|
||||||
// TODO: Support thumbnails
|
// TODO: Support thumbnails
|
||||||
// TODO: Support ImageIO metadata
|
|
||||||
// TODO: Support CCITT Modified Huffman compression (2)
|
// TODO: Support CCITT Modified Huffman compression (2)
|
||||||
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
|
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
|
||||||
// TODO: CCITT compressions T.4 and T.6
|
// TODO: CCITT compressions T.4 and T.6
|
||||||
|
// TODO: Support JPEG compression of CMYK data (pending JPEGImageWriter CMYK write support)
|
||||||
// ----
|
// ----
|
||||||
// TODO: Support storing multiple images in one stream (multi-page TIFF)
|
// TODO: Support storing multiple images in one stream (multi-page TIFF)
|
||||||
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
|
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
|
||||||
@ -91,6 +97,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Support LZW compression (5)
|
// Support LZW compression (5)
|
||||||
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
|
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
|
||||||
// Use sensible defaults for compression based on input? None is sensible... :-)
|
// Use sensible defaults for compression based on input? None is sensible... :-)
|
||||||
|
// Support resolution, resolution unit and software tags from ImageIO metadata
|
||||||
|
|
||||||
public static final Rational STANDARD_DPI = new Rational(72);
|
public static final Rational STANDARD_DPI = new Rational(72);
|
||||||
|
|
||||||
@ -98,14 +105,81 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOutput(final Object output) {
|
||||||
|
super.setOutput(output);
|
||||||
|
|
||||||
|
// TODO: Allow appending/partly overwrite of existing file...
|
||||||
|
}
|
||||||
|
|
||||||
static final class TIFFEntry extends AbstractEntry {
|
static final class TIFFEntry extends AbstractEntry {
|
||||||
TIFFEntry(Object identifier, Object value) {
|
// 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);
|
super(identifier, value);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
return TIFF.TYPE_NAMES[type];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||||
// TODO: Validate input
|
// TODO: Validate input
|
||||||
|
|
||||||
assertOutput();
|
assertOutput();
|
||||||
@ -120,6 +194,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
ColorModel colorModel = renderedImage.getColorModel();
|
ColorModel colorModel = renderedImage.getColorModel();
|
||||||
int numComponents = colorModel.getNumComponents();
|
int numComponents = colorModel.getNumComponents();
|
||||||
|
|
||||||
|
TIFFImageMetadata metadata;
|
||||||
|
if (image.getMetadata() != null) {
|
||||||
|
metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
metadata = initMeta(null, ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||||
|
}
|
||||||
|
|
||||||
SampleModel sampleModel = renderedImage.getSampleModel();
|
SampleModel sampleModel = renderedImage.getSampleModel();
|
||||||
|
|
||||||
int[] bandOffsets;
|
int[] bandOffsets;
|
||||||
@ -140,7 +222,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Entry> entries = new ArrayList<>();
|
Set<Entry> entries = new LinkedHashSet<>();
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
||||||
// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
|
// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
|
||||||
@ -157,10 +239,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write compression field from param or metadata
|
// Write compression field from param or metadata
|
||||||
|
// TODO: Support COPY_FROM_METADATA
|
||||||
int compression = TIFFImageWriteParam.getCompressionType(param);
|
int compression = TIFFImageWriteParam.getCompressionType(param);
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
||||||
|
|
||||||
// TODO: Let param/metadata control predictor
|
// TODO: Let param/metadata control predictor
|
||||||
|
// TODO: Depending on param.getCompressionMode(): DISABLED/EXPLICIT/COPY_FROM_METADATA/DEFAULT
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
@ -169,7 +253,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We might want to support CMYK in JPEG as well...
|
// TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support.
|
||||||
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
|
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
|
||||||
TIFFExtension.PHOTOMETRIC_YCBCR :
|
TIFFExtension.PHOTOMETRIC_YCBCR :
|
||||||
getPhotometricInterpretation(colorModel);
|
getPhotometricInterpretation(colorModel);
|
||||||
@ -189,15 +273,24 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default sample format SAMPLEFORMAT_UINT need not be written
|
||||||
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
||||||
}
|
}
|
||||||
|
// TODO: Float values!
|
||||||
|
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer")); // TODO: Get from metadata (optional) + fill in version number
|
// Get Software from metadata, or use default
|
||||||
|
Entry software = metadata.getIFD().getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
entries.add(software != null ? software : new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer " + originatingProvider.getVersion()));
|
||||||
|
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
// Get X/YResolution and ResolutionUnit from metadata if set, otherwise use defaults
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
// TODO: Add logic here OR in metadata merging, to make sure these 3 values are consistent.
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
Entry xRes = metadata.getIFD().getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
entries.add(xRes != null ? xRes : new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
||||||
|
Entry yRes = metadata.getIFD().getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
entries.add(yRes != null ? yRes : new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
||||||
|
Entry resUnit = metadata.getIFD().getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
entries.add(resUnit != null ? resUnit : new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||||
|
|
||||||
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
||||||
entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
||||||
@ -208,7 +301,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
|
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
|
||||||
entries.add(dummyStripOffsets); // Updated later
|
entries.add(dummyStripOffsets); // Updated later
|
||||||
|
|
||||||
// TODO: If tiled, write tile indexes etc, or always do that?
|
// TODO: If tiled, write tile indexes etc
|
||||||
|
// Depending on param.getTilingMode
|
||||||
|
|
||||||
EXIFWriter exifWriter = new EXIFWriter();
|
EXIFWriter exifWriter = new EXIFWriter();
|
||||||
|
|
||||||
@ -233,6 +327,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// TODO: Create compressor stream per Tile/Strip
|
// TODO: Create compressor stream per Tile/Strip
|
||||||
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
||||||
|
|
||||||
if (!writers.hasNext()) {
|
if (!writers.hasNext()) {
|
||||||
// This can only happen if someone deliberately uninstalled it
|
// This can only happen if someone deliberately uninstalled it
|
||||||
throw new IIOException("No JPEG ImageWriter found!");
|
throw new IIOException("No JPEG ImageWriter found!");
|
||||||
@ -607,13 +702,75 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// Metadata
|
// Metadata
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public TIFFImageMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
return null;
|
return initMeta(null, imageType, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
public TIFFImageMetadata convertImageMetadata(final IIOMetadata inData,
|
||||||
return null;
|
final ImageTypeSpecifier imageType,
|
||||||
|
final ImageWriteParam param) {
|
||||||
|
Validate.notNull(inData, "inData");
|
||||||
|
Validate.notNull(imageType, "imageType");
|
||||||
|
|
||||||
|
Directory ifd;
|
||||||
|
|
||||||
|
if (inData instanceof TIFFImageMetadata) {
|
||||||
|
ifd = ((TIFFImageMetadata) inData).getIFD();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
||||||
|
outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
||||||
|
}
|
||||||
|
else if (inData.isStandardMetadataFormatSupported()) {
|
||||||
|
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Unknown format, we can't convert it
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOInvalidTreeException e) {
|
||||||
|
// TODO: How to issue warning when warning requires imageIndex??? Use -1?
|
||||||
|
}
|
||||||
|
|
||||||
|
ifd = outData.getIFD();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite in values with values from imageType and param as needed
|
||||||
|
return initMeta(ifd, imageType, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TIFFImageMetadata initMeta(final Directory ifd, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||||
|
Validate.notNull(imageType, "imageType");
|
||||||
|
|
||||||
|
Map<Integer, Entry> entries = new LinkedHashMap<>(ifd != null ? ifd.size() + 10 : 20);
|
||||||
|
|
||||||
|
if (ifd != null) {
|
||||||
|
for (Entry entry : ifd) {
|
||||||
|
entries.put((Integer) entry.getIdentifier(), entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Set values from imageType
|
||||||
|
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel())));
|
||||||
|
|
||||||
|
// TODO: Set values from param if != null + combined values...
|
||||||
|
|
||||||
|
return new TIFFImageMetadata(entries.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
|
||||||
|
return super.getDefaultStreamMetadata(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata convertStreamMetadata(final IIOMetadata inData, final ImageWriteParam param) {
|
||||||
|
return super.convertStreamMetadata(inData, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Param
|
// Param
|
||||||
@ -762,5 +919,4 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
TIFFImageReader.showIt(read, output.getName());
|
TIFFImageReader.showIt(read, output.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,599 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.spi.IIORegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFFImageMetadataTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: TIFFImageMetadataTest.java,v 1.0 30/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public class TIFFImageMetadataTest {
|
||||||
|
|
||||||
|
static {
|
||||||
|
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||||
|
ImageIO.setUseCache(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Candidate super method
|
||||||
|
private URL getClassLoaderResource(final String resource) {
|
||||||
|
return getClass().getResource(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Candidate abstract super method
|
||||||
|
private IIOMetadata createMetadata(final String resource) throws IOException {
|
||||||
|
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
|
||||||
|
Directory ifd = new EXIFReader().read(input);
|
||||||
|
// System.err.println("ifd: " + ifd);
|
||||||
|
return new TIFFImageMetadata(ifd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetadataStandardFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/smallliz.tif");
|
||||||
|
Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
|
||||||
|
// Root: "javax_imageio_1.0"
|
||||||
|
assertNotNull(root);
|
||||||
|
assertEquals(IIOMetadataFormatImpl.standardMetadataFormatName, root.getNodeName());
|
||||||
|
assertEquals(6, root.getChildNodes().getLength());
|
||||||
|
|
||||||
|
// "Chroma"
|
||||||
|
Node chroma = root.getFirstChild();
|
||||||
|
assertEquals("Chroma", chroma.getNodeName());
|
||||||
|
|
||||||
|
assertEquals(3, chroma.getChildNodes().getLength());
|
||||||
|
|
||||||
|
Node colorSpaceType = chroma.getFirstChild();
|
||||||
|
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
|
||||||
|
assertEquals("YCbCr", ((Element) colorSpaceType).getAttribute("value"));
|
||||||
|
|
||||||
|
Node numChannels = colorSpaceType.getNextSibling();
|
||||||
|
assertEquals("NumChannels", numChannels.getNodeName());
|
||||||
|
assertEquals("3", ((Element) numChannels).getAttribute("value"));
|
||||||
|
|
||||||
|
Node blackIsZero = numChannels.getNextSibling();
|
||||||
|
assertEquals("BlackIsZero", blackIsZero.getNodeName());
|
||||||
|
assertEquals(0, blackIsZero.getAttributes().getLength());
|
||||||
|
|
||||||
|
// "Compression"
|
||||||
|
Node compression = chroma.getNextSibling();
|
||||||
|
assertEquals("Compression", compression.getNodeName());
|
||||||
|
assertEquals(2, compression.getChildNodes().getLength());
|
||||||
|
|
||||||
|
Node compressionTypeName = compression.getFirstChild();
|
||||||
|
assertEquals("CompressionTypeName", compressionTypeName.getNodeName());
|
||||||
|
assertEquals("Old JPEG", ((Element) compressionTypeName).getAttribute("value"));
|
||||||
|
|
||||||
|
Node lossless = compressionTypeName.getNextSibling();
|
||||||
|
assertEquals("Lossless", lossless.getNodeName());
|
||||||
|
assertEquals("FALSE", ((Element) lossless).getAttribute("value"));
|
||||||
|
|
||||||
|
// "Data"
|
||||||
|
Node data = compression.getNextSibling();
|
||||||
|
assertEquals("Data", data.getNodeName());
|
||||||
|
assertEquals(4, data.getChildNodes().getLength());
|
||||||
|
|
||||||
|
Node planarConfiguration = data.getFirstChild();
|
||||||
|
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
|
||||||
|
assertEquals("PixelInterleaved", ((Element) planarConfiguration).getAttribute("value"));
|
||||||
|
|
||||||
|
Node sampleFormat = planarConfiguration.getNextSibling();
|
||||||
|
assertEquals("SampleFormat", sampleFormat.getNodeName());
|
||||||
|
assertEquals("UnsignedIntegral", ((Element) sampleFormat).getAttribute("value"));
|
||||||
|
|
||||||
|
Node bitsPerSample = sampleFormat.getNextSibling();
|
||||||
|
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
|
||||||
|
assertEquals("8 8 8", ((Element) bitsPerSample).getAttribute("value"));
|
||||||
|
|
||||||
|
Node sampleMSB = bitsPerSample.getNextSibling();
|
||||||
|
assertEquals("SampleMSB", sampleMSB.getNodeName());
|
||||||
|
assertEquals("0 0 0", ((Element) sampleMSB).getAttribute("value"));
|
||||||
|
|
||||||
|
// "Dimension"
|
||||||
|
Node dimension = data.getNextSibling();
|
||||||
|
assertEquals("Dimension", dimension.getNodeName());
|
||||||
|
assertEquals(3, dimension.getChildNodes().getLength());
|
||||||
|
|
||||||
|
Node pixelAspectRatio = dimension.getFirstChild();
|
||||||
|
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||||
|
assertEquals("1.0", ((Element) pixelAspectRatio).getAttribute("value"));
|
||||||
|
|
||||||
|
Node horizontalPixelSize = pixelAspectRatio.getNextSibling();
|
||||||
|
assertEquals("HorizontalPixelSize", horizontalPixelSize.getNodeName());
|
||||||
|
assertEquals("0.254", ((Element) horizontalPixelSize).getAttribute("value"));
|
||||||
|
|
||||||
|
Node verticalPixelSize = horizontalPixelSize.getNextSibling();
|
||||||
|
assertEquals("VerticalPixelSize", verticalPixelSize.getNodeName());
|
||||||
|
assertEquals("0.254", ((Element) verticalPixelSize).getAttribute("value"));
|
||||||
|
|
||||||
|
// "Document"
|
||||||
|
Node document = dimension.getNextSibling();
|
||||||
|
assertEquals("Document", document.getNodeName());
|
||||||
|
assertEquals(1, document.getChildNodes().getLength());
|
||||||
|
|
||||||
|
Node formatVersion = document.getFirstChild();
|
||||||
|
assertEquals("FormatVersion", formatVersion.getNodeName());
|
||||||
|
assertEquals("6.0", ((Element) formatVersion).getAttribute("value"));
|
||||||
|
|
||||||
|
// "Text"
|
||||||
|
Node text = document.getNextSibling();
|
||||||
|
assertEquals("Text", text.getNodeName());
|
||||||
|
assertEquals(1, text.getChildNodes().getLength());
|
||||||
|
|
||||||
|
// NOTE: Could be multiple "TextEntry" elements, with different "keyword" attributes
|
||||||
|
Node textEntry = text.getFirstChild();
|
||||||
|
assertEquals("TextEntry", textEntry.getNodeName());
|
||||||
|
assertEquals("Software", ((Element) textEntry).getAttribute("keyword"));
|
||||||
|
assertEquals("HP IL v1.1", ((Element) textEntry).getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMetadataNativeFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
|
||||||
|
Node root = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
|
||||||
|
// Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
|
||||||
|
assertNotNull(root);
|
||||||
|
assertEquals(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
|
||||||
|
assertEquals(1, root.getChildNodes().getLength());
|
||||||
|
|
||||||
|
// IFD: "TIFFIFD"
|
||||||
|
Node ifd = root.getFirstChild();
|
||||||
|
assertEquals("TIFFIFD", ifd.getNodeName());
|
||||||
|
|
||||||
|
NodeList entries = ifd.getChildNodes();
|
||||||
|
assertEquals(13, entries.getLength());
|
||||||
|
|
||||||
|
String[] stripOffsets = {
|
||||||
|
"8", "150", "292", "434", "576", "718", "860", "1002", "1144", "1286",
|
||||||
|
"1793", "3823", "7580", "12225", "17737", "23978", "30534", "36863", "42975", "49180",
|
||||||
|
"55361", "61470", "67022", "71646", "74255", "75241", "75411", "75553", "75695", "75837",
|
||||||
|
"75979", "76316", "77899", "80466", "84068", "88471", "93623", "99105", "104483", "109663",
|
||||||
|
"114969", "120472", "126083", "131289", "135545", "138810", "140808", "141840", "141982", "142124",
|
||||||
|
"142266", "142408", "142615", "144074", "146327", "149721", "154066", "158927", "164022", "169217",
|
||||||
|
"174409", "179657", "185166", "190684", "196236", "201560", "206064", "209497", "211612", "212419",
|
||||||
|
"212561", "212703", "212845", "212987", "213129", "213271", "213413"
|
||||||
|
};
|
||||||
|
|
||||||
|
String[] stripByteCounts = {
|
||||||
|
"142", "142", "142", "142", "142", "142", "142", "142", "142", "507",
|
||||||
|
"2030", "3757", "4645", "5512", "6241", "6556", "6329", "6112", "6205", "6181",
|
||||||
|
"6109", "5552", "4624", "2609", "986", "170", "142", "142", "142", "142",
|
||||||
|
"337", "1583", "2567", "3602", "4403", "5152", "5482", "5378", "5180", "5306",
|
||||||
|
"5503", "5611", "5206", "4256", "3265", "1998", "1032", "142", "142", "142",
|
||||||
|
"142", "207", "1459", "2253", "3394", "4345", "4861", "5095", "5195", "5192",
|
||||||
|
"5248", "5509", "5518", "5552", "5324", "4504", "3433", "2115", "807", "142",
|
||||||
|
"142", "142", "142", "142", "142", "142", "128"
|
||||||
|
};
|
||||||
|
|
||||||
|
// The 13 entries
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, "512");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_HEIGHT, TIFF.TYPE_SHORT, "384");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_BITS_PER_SAMPLE, TIFF.TYPE_SHORT, "8", "8", "8");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, "5");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, "2");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffsets);
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_SAMPLES_PER_PIXEL, TIFF.TYPE_SHORT, "3");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, "5");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCounts);
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_PLANAR_CONFIGURATION, TIFF.TYPE_SHORT, "1");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0");
|
||||||
|
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0");
|
||||||
|
assertSingleNodeWithValue(entries, 32995, TIFF.TYPE_SHORT, "0"); // Matteing tag, obsoleted by ExtraSamples tag in TIFF 6.0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTreeDetached() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
Node nativeTree = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
assertNotNull(nativeTree);
|
||||||
|
|
||||||
|
Node nativeTree2 = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
|
assertNotNull(nativeTree2);
|
||||||
|
|
||||||
|
assertNotSame(nativeTree, nativeTree2);
|
||||||
|
assertNodeEquals("Unmodified trees differs", nativeTree, nativeTree2); // Both not modified
|
||||||
|
|
||||||
|
// Modify one of the trees
|
||||||
|
Node ifdNode = nativeTree2.getFirstChild();
|
||||||
|
ifdNode.removeChild(ifdNode.getFirstChild());
|
||||||
|
IIOMetadataNode tiffField = new IIOMetadataNode("TIFFField");
|
||||||
|
ifdNode.appendChild(tiffField);
|
||||||
|
|
||||||
|
assertNodeNotEquals("Modified tree does not differ", nativeTree, nativeTree2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeTree() throws IOException {
|
||||||
|
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
|
||||||
|
Node nativeTree = metadata.getAsTree(nativeFormat);
|
||||||
|
assertNotNull(nativeTree);
|
||||||
|
|
||||||
|
IIOMetadataNode newTree = new IIOMetadataNode("com_sun_media_imageio_plugins_tiff_image_1.0");
|
||||||
|
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
|
||||||
|
newTree.appendChild(ifdNode);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifdNode, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_DPI);
|
||||||
|
createTIFFFieldNode(ifdNode, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(300));
|
||||||
|
createTIFFFieldNode(ifdNode, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(30001, 100));
|
||||||
|
|
||||||
|
metadata.mergeTree(nativeFormat, newTree);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||||
|
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||||
|
assertEquals(new Rational(30001, 100), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||||
|
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||||
|
|
||||||
|
Node mergedTree = metadata.getAsTree(nativeFormat);
|
||||||
|
NodeList fields = mergedTree.getFirstChild().getChildNodes();
|
||||||
|
|
||||||
|
// Validate there's one and only one resolution unit, x res and y res
|
||||||
|
// Validate resolution unit == 1, x res & y res
|
||||||
|
assertSingleNodeWithValue(fields, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, String.valueOf(TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||||
|
assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300");
|
||||||
|
assertSingleNodeWithValue(fields, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, "30001/100");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeTreeStandardFormat() throws IOException {
|
||||||
|
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/zackthecat.tif");
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
|
||||||
|
Node standardTree = metadata.getAsTree(standardFormat);
|
||||||
|
assertNotNull(standardTree);
|
||||||
|
|
||||||
|
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
|
||||||
|
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||||
|
newTree.appendChild(dimensionNode);
|
||||||
|
|
||||||
|
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
|
||||||
|
dimensionNode.appendChild(horizontalPixelSize);
|
||||||
|
horizontalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
|
||||||
|
|
||||||
|
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
|
||||||
|
dimensionNode.appendChild(verticalPixelSize);
|
||||||
|
verticalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, newTree);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||||
|
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||||
|
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||||
|
|
||||||
|
// Should keep DPI as unit
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||||
|
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergeTreeStandardFormatAspectOnly() throws IOException {
|
||||||
|
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
|
||||||
|
Node standardTree = metadata.getAsTree(standardFormat);
|
||||||
|
assertNotNull(standardTree);
|
||||||
|
|
||||||
|
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
|
||||||
|
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||||
|
newTree.appendChild(dimensionNode);
|
||||||
|
|
||||||
|
IIOMetadataNode aspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
dimensionNode.appendChild(aspectRatio);
|
||||||
|
aspectRatio.setAttribute("value", String.valueOf(3f / 2f));
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, newTree);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||||
|
assertEquals(new Rational(3, 2), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||||
|
assertEquals(new Rational(1), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||||
|
assertEquals(TIFFBaseline.RESOLUTION_UNIT_NONE, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testMergeTreeUnsupportedFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = "com_foo_bar_tiff_42";
|
||||||
|
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testMergeTreeFormatMisMatch() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testMergeTreeInvalid() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test that failed merge leaves metadata unchanged
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetFromTreeEmpty() throws IOException {
|
||||||
|
// Read from file, set empty to see that all is cleared
|
||||||
|
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
|
||||||
|
root.appendChild(new IIOMetadataNode("TIFFIFD"));
|
||||||
|
|
||||||
|
metadata.setFromTree(nativeFormat, root);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
assertNotNull(ifd);
|
||||||
|
assertEquals(0, ifd.size());
|
||||||
|
|
||||||
|
Node tree = metadata.getAsTree(nativeFormat);
|
||||||
|
|
||||||
|
assertNotNull(tree);
|
||||||
|
assertNotNull(tree.getFirstChild());
|
||||||
|
assertEquals(1, tree.getChildNodes().getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetFromTree() throws IOException {
|
||||||
|
String softwareString = "12M UberTIFF 1.0";
|
||||||
|
|
||||||
|
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
|
||||||
|
root.appendChild(ifdNode);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifdNode, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
|
||||||
|
|
||||||
|
metadata.setFromTree(nativeFormat, root);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
assertNotNull(ifd);
|
||||||
|
assertEquals(1, ifd.size());
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
|
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
|
||||||
|
Node tree = metadata.getAsTree(nativeFormat);
|
||||||
|
|
||||||
|
assertNotNull(tree);
|
||||||
|
assertNotNull(tree.getFirstChild());
|
||||||
|
assertEquals(1, tree.getChildNodes().getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetFromTreeStandardFormat() throws IOException {
|
||||||
|
String softwareString = "12M UberTIFF 1.0";
|
||||||
|
String copyrightString = "Copyright (C) TwelveMonkeys, 2015";
|
||||||
|
|
||||||
|
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
IIOMetadataNode root = new IIOMetadataNode(standardFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||||
|
root.appendChild(textNode);
|
||||||
|
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
textNode.appendChild(textEntry);
|
||||||
|
|
||||||
|
textEntry.setAttribute("keyword", "SOFTWARE"); // Spelling should not matter
|
||||||
|
textEntry.setAttribute("value", softwareString);
|
||||||
|
|
||||||
|
textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
textNode.appendChild(textEntry);
|
||||||
|
|
||||||
|
textEntry.setAttribute("keyword", "copyright"); // Spelling should not matter
|
||||||
|
textEntry.setAttribute("value", copyrightString);
|
||||||
|
|
||||||
|
metadata.setFromTree(standardFormat, root);
|
||||||
|
|
||||||
|
Directory ifd = metadata.getIFD();
|
||||||
|
assertNotNull(ifd);
|
||||||
|
assertEquals(2, ifd.size());
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
|
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
|
||||||
|
assertNotNull(ifd.getEntryById(TIFF.TAG_COPYRIGHT));
|
||||||
|
assertEquals(copyrightString, ifd.getEntryById(TIFF.TAG_COPYRIGHT).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testSetFromTreeUnsupportedFormat() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = "com_foo_bar_tiff_42";
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testSetFromTreeFormatMisMatch() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IIOInvalidTreeException.class)
|
||||||
|
public void testSetFromTreeInvalid() throws IOException {
|
||||||
|
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {
|
||||||
|
String tagNumber = String.valueOf(tag);
|
||||||
|
String typeName = StringUtil.capitalize(TIFF.TYPE_NAMES[type].toLowerCase());
|
||||||
|
|
||||||
|
boolean foundTag = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < fields.getLength(); i++) {
|
||||||
|
Element field = (Element) fields.item(i);
|
||||||
|
|
||||||
|
if (tagNumber.equals(field.getAttribute("number"))) {
|
||||||
|
assertFalse("Duplicate tag " + tagNumber + " found", foundTag);
|
||||||
|
|
||||||
|
assertEquals(1, field.getChildNodes().getLength());
|
||||||
|
Node containerNode = field.getFirstChild();
|
||||||
|
assertEquals("TIFF" + typeName + "s", containerNode.getNodeName());
|
||||||
|
|
||||||
|
NodeList valueNodes = containerNode.getChildNodes();
|
||||||
|
assertEquals("Unexpected number of values for tag " + tagNumber, expectedValue.length, valueNodes.getLength());
|
||||||
|
|
||||||
|
for (int j = 0; j < expectedValue.length; j++) {
|
||||||
|
Element valueNode = (Element) valueNodes.item(j);
|
||||||
|
assertEquals("TIFF" + typeName, valueNode.getNodeName());
|
||||||
|
assertEquals("Unexpected tag " + tagNumber + " value", expectedValue[j], valueNode.getAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
foundTag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No tag " + tagNumber + " found", foundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test that failed set leaves metadata unchanged
|
||||||
|
|
||||||
|
static void createTIFFFieldNode(final IIOMetadataNode parentIFDNode, int tag, short type, Object value) {
|
||||||
|
IIOMetadataNode fieldNode = new IIOMetadataNode("TIFFField");
|
||||||
|
parentIFDNode.appendChild(fieldNode);
|
||||||
|
|
||||||
|
fieldNode.setAttribute("number", String.valueOf(tag));
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case TIFF.TYPE_ASCII:
|
||||||
|
createTIFFFieldContainerNode(fieldNode, "Ascii", value);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_BYTE:
|
||||||
|
createTIFFFieldContainerNode(fieldNode, "Byte", value);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_SHORT:
|
||||||
|
createTIFFFieldContainerNode(fieldNode, "Short", value);
|
||||||
|
break;
|
||||||
|
case TIFF.TYPE_RATIONAL:
|
||||||
|
createTIFFFieldContainerNode(fieldNode, "Rational", value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void createTIFFFieldContainerNode(final IIOMetadataNode fieldNode, final String type, final Object value) {
|
||||||
|
IIOMetadataNode containerNode = new IIOMetadataNode("TIFF" + type + "s");
|
||||||
|
fieldNode.appendChild(containerNode);
|
||||||
|
|
||||||
|
IIOMetadataNode valueNode = new IIOMetadataNode("TIFF" + type);
|
||||||
|
valueNode.setAttribute("value", String.valueOf(value));
|
||||||
|
containerNode.appendChild(valueNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNodeNotEquals(final String message, final Node expected, final Node actual) {
|
||||||
|
// Lame, lazy implementation...
|
||||||
|
try {
|
||||||
|
assertNodeEquals(message, expected, actual);
|
||||||
|
}
|
||||||
|
catch (AssertionError ignore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNodeEquals(final String message, final Node expected, final Node actual) {
|
||||||
|
assertEquals(message + " class differs", expected.getClass(), actual.getClass());
|
||||||
|
assertEquals(message, expected.getNodeValue(), actual.getNodeValue());
|
||||||
|
|
||||||
|
if (expected instanceof IIOMetadataNode) {
|
||||||
|
IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
|
||||||
|
IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
|
||||||
|
|
||||||
|
assertEquals(message, expectedIIO.getUserObject(), actualIIO.getUserObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeList expectedChildNodes = expected.getChildNodes();
|
||||||
|
NodeList actualChildNodes = actual.getChildNodes();
|
||||||
|
|
||||||
|
assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes),
|
||||||
|
expectedChildNodes.getLength(), actualChildNodes.getLength());
|
||||||
|
|
||||||
|
for (int i = 0; i < expectedChildNodes.getLength(); i++) {
|
||||||
|
Node expectedChild = expectedChildNodes.item(i);
|
||||||
|
Node actualChild = actualChildNodes.item(i);
|
||||||
|
|
||||||
|
assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName());
|
||||||
|
assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toString(final NodeList list) {
|
||||||
|
if (list.getLength() == 0) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < list.getLength(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = list.item(i);
|
||||||
|
builder.append(node.getLocalName());
|
||||||
|
}
|
||||||
|
builder.append("]");
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -28,15 +28,33 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.ImageWriter;
|
||||||
import java.awt.*;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataTest.createTIFFFieldNode;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageWriterTest
|
* TIFFImageWriterTest
|
||||||
*
|
*
|
||||||
@ -55,19 +73,208 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<? extends RenderedImage> getTestData() {
|
protected List<? extends RenderedImage> getTestData() {
|
||||||
BufferedImage image = new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB);
|
return Arrays.asList(
|
||||||
Graphics2D graphics = image.createGraphics();
|
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
|
||||||
try {
|
new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB),
|
||||||
graphics.setColor(Color.RED);
|
new BufferedImage(300, 200, BufferedImage.TYPE_3BYTE_BGR),
|
||||||
graphics.fillRect(0, 0, 100, 200);
|
new BufferedImage(300, 200, BufferedImage.TYPE_4BYTE_ABGR),
|
||||||
graphics.setColor(Color.BLUE);
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
|
||||||
graphics.fillRect(100, 0, 100, 200);
|
new BufferedImage(300, 200, BufferedImage.TYPE_USHORT_GRAY),
|
||||||
graphics.clearRect(200, 0, 100, 200);
|
// new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_BINARY), // TODO!
|
||||||
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_INDEXED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Test write bilevel stays bilevel
|
||||||
|
// TODO: Test write indexed stays indexed
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomResolutionNative() throws IOException {
|
||||||
|
// Issue 139 Writing TIFF files with custom resolution value
|
||||||
|
Rational resolutionValue = new Rational(1200);
|
||||||
|
int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
|
||||||
|
customMeta.appendChild(ifd);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resolutionUnitValue);
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
|
||||||
|
|
||||||
|
metadata.mergeTree(nativeFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
}
|
}
|
||||||
finally {
|
catch (IOException e) {
|
||||||
graphics.dispose();
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Arrays.asList(image);
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
|
||||||
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
assertNotNull(resolutionUnit);
|
||||||
|
assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
|
||||||
|
|
||||||
|
Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
assertNotNull(xResolution);
|
||||||
|
assertEquals(resolutionValue, xResolution.getValue());
|
||||||
|
|
||||||
|
Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
assertNotNull(yResolution);
|
||||||
|
assertEquals(resolutionValue, yResolution.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomSoftwareNative() throws IOException {
|
||||||
|
String softwareString = "12M TIFF Test 1.0 (build $foo$)";
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
|
||||||
|
customMeta.appendChild(ifd);
|
||||||
|
|
||||||
|
createTIFFFieldNode(ifd, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
|
||||||
|
|
||||||
|
metadata.mergeTree(nativeFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
assertNotNull(software);
|
||||||
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomResolutionStandard() throws IOException {
|
||||||
|
// Issue 139 Writing TIFF files with custom resolution value
|
||||||
|
double resolutionValue = 300 / 25.4; // 300 dpi, 1 inch = 2.54 cm or 25.4 mm
|
||||||
|
int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
|
||||||
|
Rational expectedResolutionValue = new Rational(Math.round(resolutionValue * 10 * TIFFImageMetadata.RATIONAL_SCALE_FACTOR), TIFFImageMetadata.RATIONAL_SCALE_FACTOR);
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
customMeta.appendChild(dimension);
|
||||||
|
|
||||||
|
IIOMetadataNode xSize = new IIOMetadataNode("HorizontalPixelSize");
|
||||||
|
dimension.appendChild(xSize);
|
||||||
|
xSize.setAttribute("value", String.valueOf(resolutionValue));
|
||||||
|
|
||||||
|
IIOMetadataNode ySize = new IIOMetadataNode("VerticalPixelSize");
|
||||||
|
dimension.appendChild(ySize);
|
||||||
|
ySize.setAttribute("value", String.valueOf(resolutionValue));
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
|
||||||
|
Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||||
|
assertNotNull(resolutionUnit);
|
||||||
|
assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
|
||||||
|
|
||||||
|
Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||||
|
assertNotNull(xResolution);
|
||||||
|
assertEquals(expectedResolutionValue, xResolution.getValue());
|
||||||
|
|
||||||
|
Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||||
|
assertNotNull(yResolution);
|
||||||
|
assertEquals(expectedResolutionValue, yResolution.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteWithCustomSoftwareStandard() throws IOException {
|
||||||
|
String softwareString = "12M TIFF Test 1.0 (build $foo$)";
|
||||||
|
|
||||||
|
RenderedImage image = getTestData(0);
|
||||||
|
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||||
|
writer.setOutput(stream);
|
||||||
|
|
||||||
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
|
||||||
|
|
||||||
|
IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Text");
|
||||||
|
customMeta.appendChild(dimension);
|
||||||
|
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
dimension.appendChild(textEntry);
|
||||||
|
textEntry.setAttribute("keyword", "Software");
|
||||||
|
textEntry.setAttribute("value", softwareString);
|
||||||
|
|
||||||
|
metadata.mergeTree(standardFormat, customMeta);
|
||||||
|
|
||||||
|
writer.write(null, new IIOImage(image, null, metadata), null);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("No image data written", buffer.size() > 0);
|
||||||
|
|
||||||
|
Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
|
||||||
|
Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
|
||||||
|
assertNotNull(software);
|
||||||
|
assertEquals(softwareString, software.getValueAsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user