mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-04 11:26:44 -04:00
Merge remote-tracking branch 'remotes/haraldk/master' into CCITTWriter
This commit is contained in:
65
README.md
65
README.md
@@ -1,7 +1,6 @@
|
|||||||
## Latest
|
## Latest
|
||||||
|
|
||||||
TwelveMonkeys ImageIO 3.1.1 is released (Jun. 11th, 2015).
|
TwelveMonkeys ImageIO [3.1.2](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.1.2%22) is released (Aug. 14th, 2015).
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
|
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
|
||||||
@@ -422,9 +421,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`.
|
||||||
@@ -462,12 +461,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-jpeg</artifactId>
|
<artifactId>imageio-jpeg</artifactId>
|
||||||
<version>3.1.1</version> <!-- Alternatively, build your own version -->
|
<version>3.1.2</version> <!-- Alternatively, build your own version -->
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-tiff</artifactId>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<version>3.1.1</version> <!-- Alternatively, build your own version -->
|
<version>3.1.2</version> <!-- Alternatively, build your own version -->
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -475,13 +474,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
|||||||
|
|
||||||
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
||||||
|
|
||||||
twelvemonkeys-common-lang-3.1.1.jar
|
twelvemonkeys-common-lang-3.1.2.jar
|
||||||
twelvemonkeys-common-io-3.1.1.jar
|
twelvemonkeys-common-io-3.1.2.jar
|
||||||
twelvemonkeys-common-image-3.1.1.jar
|
twelvemonkeys-common-image-3.1.2.jar
|
||||||
twelvemonkeys-imageio-core-3.1.1.jar
|
twelvemonkeys-imageio-core-3.1.2.jar
|
||||||
twelvemonkeys-imageio-metadata-3.1.1.jar
|
twelvemonkeys-imageio-metadata-3.1.2.jar
|
||||||
twelvemonkeys-imageio-jpeg-3.1.1.jar
|
twelvemonkeys-imageio-jpeg-3.1.2.jar
|
||||||
twelvemonkeys-imageio-tiff-3.1.1.jar
|
twelvemonkeys-imageio-tiff-3.1.2.jar
|
||||||
|
|
||||||
### Links to prebuilt binaries
|
### Links to prebuilt binaries
|
||||||
|
|
||||||
@@ -490,36 +489,36 @@ To depend on the JPEG and TIFF plugin in your IDE or program, add all of the fol
|
|||||||
Requires Java 7 or later.
|
Requires Java 7 or later.
|
||||||
|
|
||||||
Common dependencies
|
Common dependencies
|
||||||
* [common-lang-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.1.1/common-lang-3.1.1.jar)
|
* [common-lang-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.1.2/common-lang-3.1.2.jar)
|
||||||
* [common-io-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.1.1/common-io-3.1.1.jar)
|
* [common-io-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.1.2/common-io-3.1.2.jar)
|
||||||
* [common-image-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.1.1/common-image-3.1.1.jar)
|
* [common-image-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.1.2/common-image-3.1.2.jar)
|
||||||
|
|
||||||
ImageIO dependencies
|
ImageIO dependencies
|
||||||
* [imageio-core-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.1.1/imageio-core-3.1.1.jar)
|
* [imageio-core-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.1.2/imageio-core-3.1.2.jar)
|
||||||
* [imageio-metadata-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.1.1/imageio-metadata-3.1.1.jar)
|
* [imageio-metadata-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.1.2/imageio-metadata-3.1.2.jar)
|
||||||
|
|
||||||
ImageIO plugins
|
ImageIO plugins
|
||||||
* [imageio-bmp-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.1.1/imageio-bmp-3.1.1.jar)
|
* [imageio-bmp-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.1.2/imageio-bmp-3.1.2.jar)
|
||||||
* [imageio-jpeg-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.1.1/imageio-jpeg-3.1.1.jar)
|
* [imageio-jpeg-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.1.2/imageio-jpeg-3.1.2.jar)
|
||||||
* [imageio-tiff-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.1.1/imageio-tiff-3.1.1.jar)
|
* [imageio-tiff-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.1.2/imageio-tiff-3.1.2.jar)
|
||||||
* [imageio-pnm-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.1.1/imageio-pnm-3.1.1.jar)
|
* [imageio-pnm-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.1.2/imageio-pnm-3.1.2.jar)
|
||||||
* [imageio-psd-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.1.1/imageio-psd-3.1.1.jar)
|
* [imageio-psd-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.1.2/imageio-psd-3.1.2.jar)
|
||||||
* [imageio-iff-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.1.1/imageio-iff-3.1.1.jar)
|
* [imageio-iff-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.1.2/imageio-iff-3.1.2.jar)
|
||||||
* [imageio-pcx-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.1.1/imageio-pcx-3.1.1.jar)
|
* [imageio-pcx-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.1.2/imageio-pcx-3.1.2.jar)
|
||||||
* [imageio-pict-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.1.1/imageio-pict-3.1.1.jar)
|
* [imageio-pict-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.1.2/imageio-pict-3.1.2.jar)
|
||||||
* [imageio-sgi-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.1.1/imageio-sgi-3.1.1.jar)
|
* [imageio-sgi-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.1.2/imageio-sgi-3.1.2.jar)
|
||||||
* [imageio-tga-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.1.1/imageio-tga-3.1.1.jar)
|
* [imageio-tga-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.1.2/imageio-tga-3.1.2.jar)
|
||||||
* [imageio-icns-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.1.1/imageio-icns-3.1.1.jar)
|
* [imageio-icns-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.1.2/imageio-icns-3.1.2.jar)
|
||||||
* [imageio-thumbsdb-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.1.1/imageio-thumbsdb-3.1.1.jar)
|
* [imageio-thumbsdb-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.1.2/imageio-thumbsdb-3.1.2.jar)
|
||||||
|
|
||||||
ImageIO plugins requiring 3rd party libs
|
ImageIO plugins requiring 3rd party libs
|
||||||
* [imageio-batik-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.1.1/imageio-batik-3.1.1.jar)
|
* [imageio-batik-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.1.2/imageio-batik-3.1.2.jar)
|
||||||
|
|
||||||
Photoshop Path support for ImageIO
|
Photoshop Path support for ImageIO
|
||||||
* [imageio-clippath-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.1.1/imageio-clippath-3.1.1.jar)
|
* [imageio-clippath-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.1.2/imageio-clippath-3.1.2.jar)
|
||||||
|
|
||||||
Servlet support
|
Servlet support
|
||||||
* [servlet-3.1.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.1.1/servlet-3.1.1.jar)
|
* [servlet-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.1.2/servlet-3.1.2.jar)
|
||||||
|
|
||||||
##### Old version (3.0.x)
|
##### Old version (3.0.x)
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
|
|||||||
protected final DIBHeader header;
|
protected final DIBHeader header;
|
||||||
|
|
||||||
protected BufferedImage image;
|
protected BufferedImage image;
|
||||||
|
protected BitmapMask mask;
|
||||||
|
|
||||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||||
Validate.notNull(pEntry, "entry");
|
Validate.notNull(pEntry, "entry");
|
||||||
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
|
|||||||
protected final int getBitCount() {
|
protected final int getBitCount() {
|
||||||
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setMask(final BitmapMask mask) {
|
||||||
|
this.mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean hasMask() {
|
||||||
|
return header.getHeight() == getHeight() * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,6 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBuffer;
|
import java.awt.image.DataBuffer;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
protected final int[] bits;
|
protected final int[] bits;
|
||||||
protected final int[] colors;
|
protected final int[] colors;
|
||||||
|
|
||||||
private BitmapMask mask;
|
|
||||||
|
|
||||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||||
super(pEntry, pHeader);
|
super(pEntry, pHeader);
|
||||||
bits = new int[getWidth() * getHeight()];
|
bits = new int[getWidth() * getHeight()];
|
||||||
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
// This is slightly obscure, and should probably be moved..
|
// This is slightly obscure, and should probably be moved..
|
||||||
Hashtable<String, Object> properties = null;
|
Hashtable<String, Object> properties = null;
|
||||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||||
properties = new Hashtable<String, Object>(1);
|
properties = new Hashtable<>(1);
|
||||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
|
|
||||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
||||||
|
|
||||||
//System.out.println("Image: " + image);
|
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
IndexColorModel createColorModel() {
|
IndexColorModel createColorModel() {
|
||||||
// NOTE: This is a hack to make room for transparent pixel for mask
|
// NOTE: This is a hack to make room for transparent pixel for mask
|
||||||
int bits = getBitCount();
|
int bits = getBitCount();
|
||||||
|
|
||||||
int colors = this.colors.length;
|
int colors = this.colors.length;
|
||||||
int trans = -1;
|
int transparent = -1;
|
||||||
|
|
||||||
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
||||||
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
||||||
if (colors > (1 << getBitCount())) {
|
if (colors > (1 << getBitCount())) {
|
||||||
int index = findTransIndexMaybeRemap(this.colors, this.bits);
|
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
// No duplicate found, increase bitcount
|
// No duplicate found, increase bitcount
|
||||||
bits++;
|
bits++;
|
||||||
trans = this.colors.length - 1;
|
transparent = this.colors.length - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Found a duplicate, use it as trans
|
// Found a duplicate, use it as transparent
|
||||||
trans = index;
|
transparent = index;
|
||||||
colors--;
|
colors--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||||
return new InverseColorMapIndexColorModel(
|
return new IndexColorModel(
|
||||||
bits, colors, this.colors, 0, true, trans,
|
bits, colors, this.colors, 0, true, transparent,
|
||||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
|
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||||
// Look for unused colors, to use as transparent
|
// Look for unused colors, to use as transparent
|
||||||
final boolean[] used = new boolean[pColors.length - 1];
|
boolean[] used = new boolean[colors.length - 1];
|
||||||
for (int pBit : pBits) {
|
for (int bit : bits) {
|
||||||
if (!used[pBit]) {
|
if (!used[bit]) {
|
||||||
used[pBit] = true;
|
used[bit] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to find duplicates in colormap, and remap
|
// Try to find duplicates in colormap, and remap
|
||||||
int trans = -1;
|
int transparent = -1;
|
||||||
int duplicate = -1;
|
int duplicate = -1;
|
||||||
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
|
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
|
||||||
for (int j = i + 1; j < pColors.length - 1; j++) {
|
for (int j = i + 1; j < colors.length - 1; j++) {
|
||||||
if (pColors[i] == pColors[j]) {
|
if (colors[i] == colors[j]) {
|
||||||
trans = j;
|
transparent = j;
|
||||||
duplicate = i;
|
duplicate = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trans != -1) {
|
if (transparent != -1) {
|
||||||
// Remap duplicate
|
// Remap duplicate
|
||||||
for (int i = 0; i < pBits.length; i++) {
|
for (int i = 0; i < bits.length; i++) {
|
||||||
if (pBits[i] == trans) {
|
if (bits[i] == transparent) {
|
||||||
pBits[i] = duplicate;
|
bits[i] = duplicate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return trans;
|
return transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
image = createImageIndexed();
|
image = createImageIndexed();
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMask(final BitmapMask pMask) {
|
|
||||||
mask = pMask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
|
|||||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapMask extends BitmapDescriptor {
|
class BitmapMask extends BitmapDescriptor {
|
||||||
protected final BitmapIndexed mask;
|
protected final BitmapIndexed bitMask;
|
||||||
|
|
||||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
||||||
super(pParent, pHeader);
|
super(pParent, pHeader);
|
||||||
mask = new BitmapIndexed(pParent, pHeader);
|
bitMask = new BitmapIndexed(pParent, pHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTransparent(final int pX, final int pY) {
|
boolean isTransparent(final int pX, final int pY) {
|
||||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||||
return mask.bits[pX + pY * getWidth()] != 0;
|
return bitMask.bits[pX + pY * getWidth()] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
return mask.getImage();
|
return bitMask.getImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||||
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
|
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
||||||
|
// might still have bitmask, but we don't read or use it.
|
||||||
|
if (mask != null) {
|
||||||
|
image = createMaskedImage();
|
||||||
|
mask = null;
|
||||||
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BufferedImage createMaskedImage() {
|
||||||
|
BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
|
||||||
|
Graphics2D graphics = masked.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.drawImage(image, 0, 0, null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||||
|
|
||||||
|
byte[] trans = {0x0};
|
||||||
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
|
for (int x = 0; x < getWidth(); x++) {
|
||||||
|
if (mask.isTransparent(x, y)) {
|
||||||
|
alphaRaster.setDataElements(x, y, trans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,14 +66,15 @@ import java.util.List;
|
|||||||
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||||
// TODO: Support loading icons from DLLs, see
|
// TODO: Support loading icons from DLLs, see
|
||||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry
|
||||||
|
// (seem impossible as the PNGs are all true color)
|
||||||
abstract class DIBImageReader extends ImageReaderBase {
|
abstract class DIBImageReader extends ImageReaderBase {
|
||||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||||
private Directory directory;
|
private Directory directory;
|
||||||
|
|
||||||
// TODO: Review these, make sure we don't have a memory leak
|
// TODO: Review these, make sure we don't have a memory leak
|
||||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||||
|
|
||||||
private ImageReader pngImageReader;
|
private ImageReader pngImageReader;
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return getImageTypesPNG(entry);
|
return getImageTypesPNG(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
List<ImageTypeSpecifier> types = new ArrayList<>();
|
||||||
DIBHeader header = getHeader(entry);
|
DIBHeader header = getHeader(entry);
|
||||||
|
|
||||||
// Use data from header to create specifier
|
// Use data from header to create specifier
|
||||||
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
|
// TODO: May have mask?!
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
specifier = new BitmapRGB(entry, header).hasMask()
|
||||||
|
? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
break;
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||||
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Graphics2D g = destination.createGraphics();
|
Graphics2D g = destination.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g.setComposite(AlphaComposite.Src);
|
g.setComposite(AlphaComposite.Src);
|
||||||
g.drawImage(image, 0, 0, null);
|
g.drawImage(image, 0, 0, null);
|
||||||
@@ -335,7 +340,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||||
readBitmapIndexed1(mask.mask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
pBitmap.setMask(mask);
|
pBitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +375,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: If we are reading the mask, we don't abort or progress
|
// NOTE: If we are reading the mask, we don't abort or report progress
|
||||||
if (!pAsMask) {
|
if (!pAsMask) {
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -455,7 +460,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||||
|
|
||||||
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
||||||
// Will create TYPE_USHORT_555;
|
// Will create TYPE_USHORT_555
|
||||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
@@ -480,6 +485,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Might be mask!?
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||||
@@ -494,16 +501,19 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int scanlineStride = pBitmap.getWidth() * 3;
|
||||||
|
// BMP rows are padded to 4 byte boundary
|
||||||
|
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||||
|
|
||||||
WritableRaster raster = Raster.createInterleavedRaster(
|
WritableRaster raster = Raster.createInterleavedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
|
||||||
|
|
||||||
// TODO: Possibly read padding byte here!
|
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||||
|
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
||||||
|
imageInput.readFully(pixels, offset, scanlineStride);
|
||||||
|
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -512,6 +522,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 24 bit icons usually have a bit mask
|
||||||
|
if (pBitmap.hasMask()) {
|
||||||
|
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||||
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
|
|
||||||
|
pBitmap.setMask(mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||||
@@ -535,6 +553,9 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There might be a mask here as well, but we'll ignore it,
|
||||||
|
// and use the 8 bit alpha channel in the ARGB pixel data
|
||||||
}
|
}
|
||||||
|
|
||||||
private Directory getDirectory() throws IOException {
|
private Directory getDirectory() throws IOException {
|
||||||
|
@@ -39,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
|
|||||||
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
||||||
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
||||||
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
||||||
)
|
),
|
||||||
|
// Problematic icon that reports 24 bit in the descriptor, but has separate 1 bit ''mask (height 2 x icon height)!
|
||||||
|
new TestData(getClassLoaderResource("/ico/rgb24bitmask.ico"), new Dimension(32, 32))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico
Normal file
BIN
imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
@@ -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
|
||||||
|
@@ -52,7 +52,7 @@ import java.util.Properties;
|
|||||||
* <p />
|
* <p />
|
||||||
* Color profiles may be configured by placing a property-file
|
* Color profiles may be configured by placing a property-file
|
||||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||||
* on the classpath, specifying the full path to the profile.
|
* on the classpath, specifying the full path to the profiles.
|
||||||
* ICC color profiles are probably already present on your system, or
|
* ICC color profiles are probably already present on your system, or
|
||||||
* can be downloaded from
|
* can be downloaded from
|
||||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||||
@@ -342,7 +342,7 @@ public final class ColorSpaces {
|
|||||||
try {
|
try {
|
||||||
return ICC_Profile.getInstance(profilePath);
|
return ICC_Profile.getInstance(profilePath);
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (SecurityException | IOException ignore) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
ignore.printStackTrace();
|
ignore.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
30
imageio/imageio-hdr/pom.xml
Normal file
30
imageio/imageio-hdr/pom.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio</artifactId>
|
||||||
|
<version>3.2-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>imageio-hdr</artifactId>
|
||||||
|
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||||
|
<description>
|
||||||
|
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-core</artifactId>
|
||||||
|
<type>test-jar</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-metadata</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDR.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
interface HDR {
|
||||||
|
byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
|
||||||
|
byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
|
||||||
|
}
|
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRHeader.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class HDRHeader {
|
||||||
|
private static final String KEY_FORMAT = "FORMAT=";
|
||||||
|
private static final String KEY_PRIMARIES = "PRIMARIES=";
|
||||||
|
private static final String KEY_EXPOSURE = "EXPOSURE=";
|
||||||
|
private static final String KEY_GAMMA = "GAMMA=";
|
||||||
|
private static final String KEY_SOFTWARE = "SOFTWARE=";
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
private String software;
|
||||||
|
|
||||||
|
public static HDRHeader read(final ImageInputStream stream) throws IOException {
|
||||||
|
HDRHeader header = new HDRHeader();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line = stream.readLine().trim();
|
||||||
|
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
// This is the last line before the dimensions
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("#?")) {
|
||||||
|
// Program specifier, don't need that...
|
||||||
|
}
|
||||||
|
else if (line.startsWith("#")) {
|
||||||
|
// Comment (ignore)
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_FORMAT)) {
|
||||||
|
String format = line.substring(KEY_FORMAT.length()).trim();
|
||||||
|
|
||||||
|
if (!format.equals("32-bit_rle_rgbe")) {
|
||||||
|
throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
|
||||||
|
}
|
||||||
|
// TODO: Support the 32-bit_rle_xyze format
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_PRIMARIES)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
// Should contain 8 (RGB + white point) coordinates
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_EXPOSURE)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_GAMMA)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_SOFTWARE)) {
|
||||||
|
header.software = line.substring(KEY_SOFTWARE.length()).trim();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// ...ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Proper parsing of width/height and orientation!
|
||||||
|
String dimensionsLine = stream.readLine().trim();
|
||||||
|
String[] dims = dimensionsLine.split("\\s");
|
||||||
|
|
||||||
|
if (dims[0].equals("-Y") && dims[2].equals("+X")) {
|
||||||
|
header.height = Integer.parseInt(dims[1]);
|
||||||
|
header.width = Integer.parseInt(dims[3]);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftware() {
|
||||||
|
return software;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReadParam.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReadParam extends ImageReadParam {
|
||||||
|
static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
|
||||||
|
|
||||||
|
private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
|
||||||
|
|
||||||
|
public ToneMapper getToneMapper() {
|
||||||
|
return toneMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToneMapper(final ToneMapper toneMapper) {
|
||||||
|
this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReader.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReader extends ImageReaderBase {
|
||||||
|
// Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
|
||||||
|
|
||||||
|
private HDRHeader header;
|
||||||
|
|
||||||
|
protected HDRImageReader(final ImageReaderSpi provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetMembers() {
|
||||||
|
header = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readHeader() throws IOException {
|
||||||
|
if (header == null) {
|
||||||
|
header = HDRHeader.read(imageInput);
|
||||||
|
|
||||||
|
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInput.seek(imageInput.getFlushedPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return header.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return header.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
int width = getWidth(imageIndex);
|
||||||
|
int height = getHeight(imageIndex);
|
||||||
|
|
||||||
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||||
|
|
||||||
|
Rectangle srcRegion = new Rectangle();
|
||||||
|
Rectangle destRegion = new Rectangle();
|
||||||
|
computeRegions(param, width, height, destination, srcRegion, destRegion);
|
||||||
|
|
||||||
|
WritableRaster raster = destination.getRaster()
|
||||||
|
.createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
|
||||||
|
|
||||||
|
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||||
|
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||||
|
|
||||||
|
// Allow pluggable tone mapper via ImageReadParam
|
||||||
|
ToneMapper toneMapper = param instanceof HDRImageReadParam
|
||||||
|
? ((HDRImageReadParam) param).getToneMapper()
|
||||||
|
: HDRImageReadParam.DEFAULT_TONE_MAPPER;
|
||||||
|
|
||||||
|
byte[] rowRGBE = new byte[width * 4];
|
||||||
|
float[] rgb = new float[3];
|
||||||
|
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
// Process one scanline of RGBE data at a time
|
||||||
|
for (int srcY = 0; srcY < height; srcY++) {
|
||||||
|
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||||
|
if (dstY >= destRegion.height) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||||
|
|
||||||
|
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||||
|
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||||
|
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||||
|
if (dstX >= destRegion.width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
|
||||||
|
|
||||||
|
// Map/clamp RGB values into visible range, normally [0...1]
|
||||||
|
toneMapper.map(rgb);
|
||||||
|
|
||||||
|
raster.setDataElements(dstX, dstY, rgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(srcY * 100f / height);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadRaster() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
int width = getWidth(imageIndex);
|
||||||
|
int height = getHeight(imageIndex);
|
||||||
|
|
||||||
|
Rectangle srcRegion = new Rectangle();
|
||||||
|
Rectangle destRegion = new Rectangle();
|
||||||
|
computeRegions(param, width, height, null, srcRegion, destRegion);
|
||||||
|
destRegion = srcRegion; // We don't really care about destination for raster
|
||||||
|
|
||||||
|
BufferedImage destination = new BufferedImage(srcRegion.width, srcRegion.height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
WritableRaster raster = destination.getRaster();
|
||||||
|
|
||||||
|
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||||
|
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||||
|
|
||||||
|
byte[] rowRGBE = new byte[width * 4];
|
||||||
|
byte[] pixelRGBE = new byte[width];
|
||||||
|
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
// Process one scanline of RGBE data at a time
|
||||||
|
for (int srcY = 0; srcY < height; srcY++) {
|
||||||
|
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||||
|
if (dstY >= destRegion.height) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||||
|
|
||||||
|
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||||
|
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||||
|
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||||
|
if (dstX >= destRegion.width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(rowRGBE, srcX * 4, pixelRGBE, 0, 4);
|
||||||
|
raster.setDataElements(dstX, dstY, pixelRGBE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(srcY * 100f / height);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination.getRaster();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReadParam getDefaultReadParam() {
|
||||||
|
return new HDRImageReadParam();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return new HDRMetadata(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws IOException {
|
||||||
|
File file = new File(args[0]);
|
||||||
|
|
||||||
|
BufferedImage image = ImageIO.read(file);
|
||||||
|
|
||||||
|
showIt(image, file.getName());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReaderSpi.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReaderSpi extends ImageReaderSpiBase {
|
||||||
|
public HDRImageReaderSpi() {
|
||||||
|
super(new HDRProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
|
if (!(source instanceof ImageInputStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInputStream stream = (ImageInputStream) source;
|
||||||
|
|
||||||
|
stream.mark();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
|
||||||
|
// although some sources claim that #?RGBE is also used.
|
||||||
|
byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
|
||||||
|
stream.readFully(magic);
|
||||||
|
|
||||||
|
return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
|
||||||
|
|| Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||||
|
return new HDRImageReader(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
|
return "Radiance RGBE High Dynaimc Range (HDR) image reader";
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
|
||||||
|
final class HDRMetadata extends AbstractMetadata {
|
||||||
|
private final HDRHeader header;
|
||||||
|
|
||||||
|
HDRMetadata(final HDRHeader header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
|
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chroma.appendChild(csType);
|
||||||
|
csType.setAttribute("name", "RGB");
|
||||||
|
// TODO: Support XYZ
|
||||||
|
|
||||||
|
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||||
|
numChannels.setAttribute("value", "3");
|
||||||
|
chroma.appendChild(numChannels);
|
||||||
|
|
||||||
|
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||||
|
blackIsZero.setAttribute("value", "TRUE");
|
||||||
|
chroma.appendChild(blackIsZero);
|
||||||
|
|
||||||
|
return chroma;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No compression
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
compressionTypeName.setAttribute("value", "RLE");
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
|
|
||||||
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
|
lossless.setAttribute("value", "TRUE");
|
||||||
|
node.appendChild(lossless);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
|
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||||
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||||
|
bitsPerSample.setAttribute("value", "8 8 8 8");
|
||||||
|
node.appendChild(bitsPerSample);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
|
// TODO: Support other orientations
|
||||||
|
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
|
imageOrientation.setAttribute("value", "Normal");
|
||||||
|
dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
|
return dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No document node
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
|
if (header.getSoftware() != null) {
|
||||||
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
textEntry.setAttribute("keyword", "Software");
|
||||||
|
textEntry.setAttribute("value", header.getSoftware());
|
||||||
|
text.appendChild(textEntry);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tiling
|
||||||
|
|
||||||
|
// No transparency
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRProviderInfo.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class HDRProviderInfo extends ReaderWriterProviderInfo {
|
||||||
|
protected HDRProviderInfo() {
|
||||||
|
super(
|
||||||
|
HDRProviderInfo.class,
|
||||||
|
new String[] {"HDR", "hdr", "RGBE", "rgbe"},
|
||||||
|
new String[] {"hdr", "rgbe", "xyze", "pic"},
|
||||||
|
new String[] {"image/vnd.radiance"},
|
||||||
|
"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
|
||||||
|
new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false, null, null, null, null,
|
||||||
|
true, null, null, null, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,494 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains code to read and write four byte rgbe file format
|
||||||
|
* developed by Greg Ward. It handles the conversions between rgbe and
|
||||||
|
* pixels consisting of floats. The data is assumed to be an array of floats.
|
||||||
|
* By default there are three floats per pixel in the order red, green, blue.
|
||||||
|
* (RGBE_DATA_??? values control this.) Only the mimimal header reading and
|
||||||
|
* writing is implemented. Each routine does error checking and will return
|
||||||
|
* a status value as defined below. This code is intended as a skeleton so
|
||||||
|
* feel free to modify it to suit your needs. <P>
|
||||||
|
* <p/>
|
||||||
|
* Ported to Java and restructured by Kenneth Russell. <BR>
|
||||||
|
* posted to http://www.graphics.cornell.edu/~bjw/ <BR>
|
||||||
|
* written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 <BR>
|
||||||
|
* based on code written by Greg Ward <BR>
|
||||||
|
* <p/>
|
||||||
|
* Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
|
||||||
|
*/
|
||||||
|
final class RGBE {
|
||||||
|
// Flags indicating which fields in a Header are valid
|
||||||
|
private static final int VALID_PROGRAMTYPE = 0x01;
|
||||||
|
private static final int VALID_GAMMA = 0x02;
|
||||||
|
private static final int VALID_EXPOSURE = 0x04;
|
||||||
|
|
||||||
|
private static final String gammaString = "GAMMA=";
|
||||||
|
private static final String exposureString = "EXPOSURE=";
|
||||||
|
|
||||||
|
private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
|
||||||
|
|
||||||
|
public static class Header {
|
||||||
|
// Indicates which fields are valid
|
||||||
|
private int valid;
|
||||||
|
|
||||||
|
// Listed at beginning of file to identify it after "#?".
|
||||||
|
// Defaults to "RGBE"
|
||||||
|
private String programType;
|
||||||
|
|
||||||
|
// Image has already been gamma corrected with given gamma.
|
||||||
|
// Defaults to 1.0 (no correction)
|
||||||
|
private float gamma;
|
||||||
|
|
||||||
|
// A value of 1.0 in an image corresponds to <exposure>
|
||||||
|
// watts/steradian/m^2. Defaults to 1.0.
|
||||||
|
private float exposure;
|
||||||
|
|
||||||
|
// Width and height of image
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
private Header(int valid,
|
||||||
|
String programType,
|
||||||
|
float gamma,
|
||||||
|
float exposure,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
this.valid = valid;
|
||||||
|
this.programType = programType;
|
||||||
|
this.gamma = gamma;
|
||||||
|
this.exposure = exposure;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProgramTypeValid() {
|
||||||
|
return ((valid & VALID_PROGRAMTYPE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGammaValid() {
|
||||||
|
return ((valid & VALID_GAMMA) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExposureValid() {
|
||||||
|
return ((valid & VALID_EXPOSURE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProgramType() {
|
||||||
|
return programType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getGamma() {
|
||||||
|
return gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getExposure() {
|
||||||
|
return exposure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
if (isProgramTypeValid()) {
|
||||||
|
buf.append(" Program type: ");
|
||||||
|
buf.append(getProgramType());
|
||||||
|
}
|
||||||
|
buf.append(" Gamma");
|
||||||
|
if (isGammaValid()) {
|
||||||
|
buf.append(" [valid]");
|
||||||
|
}
|
||||||
|
buf.append(": ");
|
||||||
|
buf.append(getGamma());
|
||||||
|
buf.append(" Exposure");
|
||||||
|
if (isExposureValid()) {
|
||||||
|
buf.append(" [valid]");
|
||||||
|
}
|
||||||
|
buf.append(": ");
|
||||||
|
buf.append(getExposure());
|
||||||
|
buf.append(" Width: ");
|
||||||
|
buf.append(getWidth());
|
||||||
|
buf.append(" Height: ");
|
||||||
|
buf.append(getHeight());
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Header readHeader(final DataInput in) throws IOException {
|
||||||
|
int valid = 0;
|
||||||
|
String programType = null;
|
||||||
|
float gamma = 1.0f;
|
||||||
|
float exposure = 1.0f;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
String buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading magic token");
|
||||||
|
}
|
||||||
|
if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
|
||||||
|
valid |= VALID_PROGRAMTYPE;
|
||||||
|
programType = buf.substring(2);
|
||||||
|
buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading line after magic token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean foundFormat = false;
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
|
||||||
|
foundFormat = true;
|
||||||
|
}
|
||||||
|
else if (buf.startsWith(gammaString)) {
|
||||||
|
valid |= VALID_GAMMA;
|
||||||
|
gamma = Float.parseFloat(buf.substring(gammaString.length()));
|
||||||
|
}
|
||||||
|
else if (buf.startsWith(exposureString)) {
|
||||||
|
valid |= VALID_EXPOSURE;
|
||||||
|
exposure = Float.parseFloat(buf.substring(exposureString.length()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Matcher m = widthHeightPattern.matcher(buf);
|
||||||
|
if (m.matches()) {
|
||||||
|
width = Integer.parseInt(m.group(2));
|
||||||
|
height = Integer.parseInt(m.group(1));
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundFormat) {
|
||||||
|
throw new IOException("No FORMAT specifier found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Header(valid, programType, gamma, exposure, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple read routine. Will not correctly handle run length encoding.
|
||||||
|
*/
|
||||||
|
public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
|
||||||
|
byte[] rgbe = new byte[4];
|
||||||
|
float[] rgb = new float[3];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (numpixels-- > 0) {
|
||||||
|
in.readFully(rgbe);
|
||||||
|
|
||||||
|
rgbe2float(rgb, rgbe, 0);
|
||||||
|
|
||||||
|
data[offset++] = rgb[0];
|
||||||
|
data[offset++] = rgb[1];
|
||||||
|
data[offset++] = rgb[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
|
||||||
|
int numExpected = 4 * numpixels;
|
||||||
|
in.readFully(data, offset, numExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
|
||||||
|
int scanline_width, int num_scanlines) throws IOException {
|
||||||
|
byte[] rgbe = new byte[4];
|
||||||
|
byte[] scanline_buffer = null;
|
||||||
|
int ptr, ptr_end;
|
||||||
|
int count;
|
||||||
|
byte[] buf = new byte[2];
|
||||||
|
|
||||||
|
if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
|
||||||
|
// run length encoding is not allowed so read flat
|
||||||
|
readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read in each successive scanline
|
||||||
|
while (num_scanlines > 0) {
|
||||||
|
in.readFully(rgbe);
|
||||||
|
|
||||||
|
if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
|
||||||
|
// this file is not run length encoded
|
||||||
|
data[offset++] = rgbe[0];
|
||||||
|
data[offset++] = rgbe[1];
|
||||||
|
data[offset++] = rgbe[2];
|
||||||
|
data[offset++] = rgbe[3];
|
||||||
|
readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
|
||||||
|
throw new IOException("Wrong scanline width " +
|
||||||
|
(((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
|
||||||
|
", expected " + scanline_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanline_buffer == null) {
|
||||||
|
scanline_buffer = new byte[4 * scanline_width];
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = 0;
|
||||||
|
// read each of the four channels for the scanline into the buffer
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
ptr_end = (i + 1) * scanline_width;
|
||||||
|
while (ptr < ptr_end) {
|
||||||
|
in.readFully(buf);
|
||||||
|
|
||||||
|
if ((buf[0] & 0xFF) > 128) {
|
||||||
|
// a run of the same value
|
||||||
|
count = (buf[0] & 0xFF) - 128;
|
||||||
|
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||||
|
throw new IOException("Bad scanline data");
|
||||||
|
}
|
||||||
|
while (count-- > 0) {
|
||||||
|
scanline_buffer[ptr++] = buf[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// a non-run
|
||||||
|
count = buf[0] & 0xFF;
|
||||||
|
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||||
|
throw new IOException("Bad scanline data");
|
||||||
|
}
|
||||||
|
scanline_buffer[ptr++] = buf[1];
|
||||||
|
if (--count > 0) {
|
||||||
|
in.readFully(scanline_buffer, ptr, count);
|
||||||
|
ptr += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// copy byte data to output
|
||||||
|
for (int i = 0; i < scanline_width; i++) {
|
||||||
|
data[offset++] = scanline_buffer[i];
|
||||||
|
data[offset++] = scanline_buffer[i + scanline_width];
|
||||||
|
data[offset++] = scanline_buffer[i + 2 * scanline_width];
|
||||||
|
data[offset++] = scanline_buffer[i + 3 * scanline_width];
|
||||||
|
}
|
||||||
|
num_scanlines--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard conversion from float pixels to rgbe pixels.
|
||||||
|
*/
|
||||||
|
public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
|
||||||
|
float v;
|
||||||
|
int e;
|
||||||
|
|
||||||
|
v = red;
|
||||||
|
if (green > v) {
|
||||||
|
v = green;
|
||||||
|
}
|
||||||
|
if (blue > v) {
|
||||||
|
v = blue;
|
||||||
|
}
|
||||||
|
if (v < 1e-32f) {
|
||||||
|
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FracExp fe = frexp(v);
|
||||||
|
v = (float) (fe.getFraction() * 256.0 / v);
|
||||||
|
rgbe[0] = (byte) (red * v);
|
||||||
|
rgbe[1] = (byte) (green * v);
|
||||||
|
rgbe[2] = (byte) (blue * v);
|
||||||
|
rgbe[3] = (byte) (fe.getExponent() + 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard conversion from rgbe to float pixels. Note: Ward uses
|
||||||
|
* ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
|
||||||
|
* range [0,1] to map back into the range [0,1].
|
||||||
|
*/
|
||||||
|
public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
|
||||||
|
float f;
|
||||||
|
|
||||||
|
if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
|
||||||
|
f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
|
||||||
|
rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
|
||||||
|
rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
|
||||||
|
rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rgb[0] = 0;
|
||||||
|
rgb[1] = 0;
|
||||||
|
rgb[2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double ldexp(double value, int exp) {
|
||||||
|
if (!finite(value) || value == 0.0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
value = scalbn(value, exp);
|
||||||
|
// No good way to indicate errno (want to avoid throwing
|
||||||
|
// exceptions because don't know about stability of calculations)
|
||||||
|
// if(!finite(value)||value==0.0) errno = ERANGE;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Internals only below this point
|
||||||
|
//
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Math routines, some fdlibm-derived
|
||||||
|
//
|
||||||
|
|
||||||
|
static class FracExp {
|
||||||
|
private double fraction;
|
||||||
|
private int exponent;
|
||||||
|
|
||||||
|
public FracExp(double fraction, int exponent) {
|
||||||
|
this.fraction = fraction;
|
||||||
|
this.exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFraction() {
|
||||||
|
return fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExponent() {
|
||||||
|
return exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
|
||||||
|
private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
|
||||||
|
private static final double huge = 1.0e+300;
|
||||||
|
private static final double tiny = 1.0e-300;
|
||||||
|
|
||||||
|
private static int hi(double x) {
|
||||||
|
long bits = Double.doubleToRawLongBits(x);
|
||||||
|
return (int) (bits >>> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lo(double x) {
|
||||||
|
long bits = Double.doubleToRawLongBits(x);
|
||||||
|
return (int) bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double fromhilo(int hi, int lo) {
|
||||||
|
return Double.longBitsToDouble((((long) hi) << 32) |
|
||||||
|
(((long) lo) & 0xFFFFFFFFL));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FracExp frexp(double x) {
|
||||||
|
int hx = hi(x);
|
||||||
|
int ix = 0x7fffffff & hx;
|
||||||
|
int lx = lo(x);
|
||||||
|
int e = 0;
|
||||||
|
if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
|
||||||
|
return new FracExp(x, e); // 0,inf,nan
|
||||||
|
}
|
||||||
|
if (ix < 0x00100000) { // subnormal
|
||||||
|
x *= two54;
|
||||||
|
hx = hi(x);
|
||||||
|
ix = hx & 0x7fffffff;
|
||||||
|
e = -54;
|
||||||
|
}
|
||||||
|
e += (ix >> 20) - 1022;
|
||||||
|
hx = (hx & 0x800fffff) | 0x3fe00000;
|
||||||
|
lx = lo(x);
|
||||||
|
return new FracExp(fromhilo(hx, lx), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean finite(double x) {
|
||||||
|
int hx;
|
||||||
|
hx = hi(x);
|
||||||
|
return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copysign(double x, double y) <BR>
|
||||||
|
* copysign(x,y) returns a value with the magnitude of x and
|
||||||
|
* with the sign bit of y.
|
||||||
|
*/
|
||||||
|
private static double copysign(double x, double y) {
|
||||||
|
return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scalbn (double x, int n) <BR>
|
||||||
|
* scalbn(x,n) returns x* 2**n computed by exponent
|
||||||
|
* manipulation rather than by actually performing an
|
||||||
|
* exponentiation or a multiplication.
|
||||||
|
*/
|
||||||
|
private static double scalbn(double x, int n) {
|
||||||
|
int hx = hi(x);
|
||||||
|
int lx = lo(x);
|
||||||
|
int k = (hx & 0x7ff00000) >> 20; // extract exponent
|
||||||
|
if (k == 0) { // 0 or subnormal x
|
||||||
|
if ((lx | (hx & 0x7fffffff)) == 0) {
|
||||||
|
return x; // +-0
|
||||||
|
}
|
||||||
|
x *= two54;
|
||||||
|
hx = hi(x);
|
||||||
|
k = ((hx & 0x7ff00000) >> 20) - 54;
|
||||||
|
if (n < -50000) {
|
||||||
|
return tiny * x; // underflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k == 0x7ff) {
|
||||||
|
return x + x; // NaN or Inf
|
||||||
|
}
|
||||||
|
k = k + n;
|
||||||
|
if (k > 0x7fe) {
|
||||||
|
return huge * copysign(huge, x); // overflow
|
||||||
|
}
|
||||||
|
if (k > 0) {
|
||||||
|
// normal result
|
||||||
|
return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||||
|
}
|
||||||
|
if (k <= -54) {
|
||||||
|
if (n > 50000) {
|
||||||
|
// in case integer overflow in n+k
|
||||||
|
return huge * copysign(huge, x); // overflow
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return tiny * copysign(tiny, x); // underflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k += 54; // subnormal result
|
||||||
|
x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||||
|
return x * twom54;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Test harness
|
||||||
|
//
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
try {
|
||||||
|
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
|
||||||
|
Header header = RGBE.readHeader(in);
|
||||||
|
System.err.println("Header for file \"" + args[i] + "\":");
|
||||||
|
System.err.println(" " + header);
|
||||||
|
byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
|
||||||
|
readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* Normalizes values to range [0...1] using:
|
||||||
|
*
|
||||||
|
* <p><em>V<sub>out</sub> = V<sub>in</sub> / (V<sub>in</sub> + C)</em></p>
|
||||||
|
*
|
||||||
|
* Where <em>C</em> is constant.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class DefaultToneMapper implements ToneMapper {
|
||||||
|
|
||||||
|
private final float constant;
|
||||||
|
|
||||||
|
public DefaultToneMapper() {
|
||||||
|
this(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultToneMapper(final float constant) {
|
||||||
|
this.constant = constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void map(final float[] rgb) {
|
||||||
|
// Default Vo = Vi / (Vi + 1)
|
||||||
|
for (int i = 0; i < rgb.length; i++) {
|
||||||
|
rgb[i] = rgb[i] / (rgb[i] + constant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GammaToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* Normalizes values to range [0...1] using:
|
||||||
|
*
|
||||||
|
* <p><em>V<sub>out</sub> = A V<sub>in</sub><sup>\u03b3</sup></em></p>
|
||||||
|
*
|
||||||
|
* Where <em>A</em> is constant and <em>\u03b3</em> is the gamma.
|
||||||
|
* Values > 1 are clamped.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class GammaToneMapper implements ToneMapper {
|
||||||
|
|
||||||
|
private final float constant;
|
||||||
|
private final float gamma;
|
||||||
|
|
||||||
|
public GammaToneMapper() {
|
||||||
|
this(0.5f, .25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GammaToneMapper(final float constant, final float gamma) {
|
||||||
|
this.constant = constant;
|
||||||
|
this.gamma = gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void map(final float[] rgb) {
|
||||||
|
// Gamma Vo = A * Vi^y
|
||||||
|
for (int i = 0; i < rgb.length; i++) {
|
||||||
|
rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NullToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* This {@code ToneMapper} does *not* normalize or clamp values
|
||||||
|
* to range [0...1], but leaves the values as-is.
|
||||||
|
* Useful for applications that implements custom tone mapping.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class NullToneMapper implements ToneMapper {
|
||||||
|
@Override
|
||||||
|
public void map(float[] rgb) {
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ToneMapper.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public interface ToneMapper {
|
||||||
|
void map(float[] rgb);
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
|
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name "TwelveMonkeys" nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestData() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new HDRImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<HDRImageReader> getReaderClass() {
|
||||||
|
return HDRImageReader.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HDRImageReader createReader() {
|
||||||
|
return new HDRImageReader(createProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFormatNames() {
|
||||||
|
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getSuffixes() {
|
||||||
|
return Arrays.asList("hdr", "rgbe", "xyze");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getMIMETypes() {
|
||||||
|
return Collections.singletonList(
|
||||||
|
"image/vnd.radiance"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
BIN
imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
Normal file
BIN
imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
Normal file
Binary file not shown.
@@ -18,7 +18,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
@@ -267,10 +267,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
// If delegate can determine the spec, we'll just go with that
|
// If delegate can determine the spec, we'll just go with that
|
||||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
try {
|
||||||
|
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||||
|
|
||||||
if (rawType != null) {
|
if (rawType != null) {
|
||||||
return rawType;
|
return rawType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NullPointerException ignore) {
|
||||||
|
// Fall through
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, consult the image metadata
|
// Otherwise, consult the image metadata
|
||||||
@@ -312,22 +317,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
assertInput();
|
assertInput();
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
// CompoundDirectory exif = getExif();
|
|
||||||
// if (exif != null) {
|
|
||||||
// System.err.println("exif: " + exif);
|
|
||||||
// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
|
|
||||||
// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
|
|
||||||
//
|
|
||||||
// if (exifIFDEntry != null) {
|
|
||||||
// Directory exifIFD = (Directory) exifIFDEntry.getValue();
|
|
||||||
// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
|
|
||||||
// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
SOFSegment sof = getSOF();
|
SOFSegment sof = getSOF();
|
||||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||||
|
boolean bogusAdobeDCT = false;
|
||||||
|
|
||||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
|
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
|
||||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
|
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
|
||||||
@@ -338,6 +331,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
sof.marker & 0xf, sof.componentsInFrame()
|
sof.marker & 0xf, sof.componentsInFrame()
|
||||||
));
|
));
|
||||||
|
|
||||||
|
bogusAdobeDCT = true;
|
||||||
adobeDCT = null;
|
adobeDCT = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,11 +340,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||||
if (delegate.canReadRaster() && (
|
if (delegate.canReadRaster() && (
|
||||||
|
bogusAdobeDCT ||
|
||||||
sourceCSType == JPEGColorSpace.CMYK ||
|
sourceCSType == JPEGColorSpace.CMYK ||
|
||||||
sourceCSType == JPEGColorSpace.YCCK ||
|
sourceCSType == JPEGColorSpace.YCCK ||
|
||||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
||||||
profile != null && !ColorSpaces.isCS_sRGB(profile)) ||
|
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
||||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null) { // TODO: Issue warning?
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Reading using raster and extra conversion");
|
System.out.println("Reading using raster and extra conversion");
|
||||||
System.out.println("ICC color profile: " + profile);
|
System.out.println("ICC color profile: " + profile);
|
||||||
@@ -471,12 +465,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
YCbCrConverter.convertYCbCr2RGB(raster);
|
YCbCrConverter.convertYCbCr2RGB(raster);
|
||||||
}
|
}
|
||||||
else if (csType == JPEGColorSpace.YCCK) {
|
else if (csType == JPEGColorSpace.YCCK) {
|
||||||
YCbCrConverter.convertYCCK2CMYK(raster);
|
// TODO: Need to rethink this (non-) inversion, see #147
|
||||||
|
// TODO: Allow param to specify inversion, or possibly the PDF decode array
|
||||||
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
||||||
if ((getAdobeDCT().flags0 & 0x8000) != 0) {
|
boolean invert = true;// || (adobeDCT.flags0 & 0x8000) == 0;
|
||||||
/// TODO: Better yet would be to not inverting in the first place, add flag to convertYCCK2CMYK
|
YCbCrConverter.convertYCCK2CMYK(raster, invert);
|
||||||
invertCMYK(raster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (csType == JPEGColorSpace.CMYK) {
|
else if (csType == JPEGColorSpace.CMYK) {
|
||||||
invertCMYK(raster);
|
invertCMYK(raster);
|
||||||
@@ -948,6 +941,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
delegate.abort();
|
delegate.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReadParam getDefaultReadParam() {
|
||||||
|
return delegate.getDefaultReadParam();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readerSupportsThumbnails() {
|
public boolean readerSupportsThumbnails() {
|
||||||
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
||||||
@@ -1176,19 +1174,28 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void convertYCCK2CMYK(final Raster raster) {
|
static void convertYCCK2CMYK(final Raster raster, final boolean invert) {
|
||||||
final int height = raster.getHeight();
|
final int height = raster.getHeight();
|
||||||
final int width = raster.getWidth();
|
final int width = raster.getWidth();
|
||||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
if (invert) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int y = 0; y < height; y++) {
|
||||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
for (int x = 0; x < width; x++) {
|
||||||
|
convertYCCK2CMYKInverted(data, data, (x + y * width) * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
private static void convertYCCK2CMYKInverted(byte[] ycck, byte[] cmyk, int offset) {
|
||||||
// Inverted
|
// Inverted
|
||||||
int y = 255 - ycck[offset ] & 0xff;
|
int y = 255 - ycck[offset ] & 0xff;
|
||||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||||
@@ -1205,6 +1212,22 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||||
|
int y = ycck[offset ] & 0xff;
|
||||||
|
int cb = ycck[offset + 1] & 0xff;
|
||||||
|
int cr = ycck[offset + 2] & 0xff;
|
||||||
|
int k = ycck[offset + 3] & 0xff;
|
||||||
|
|
||||||
|
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||||
|
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||||
|
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||||
|
|
||||||
|
cmyk[offset ] = clamp(cmykC);
|
||||||
|
cmyk[offset + 1] = clamp(cmykM);
|
||||||
|
cmyk[offset + 2] = clamp(cmykY);
|
||||||
|
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||||
|
}
|
||||||
|
|
||||||
private static byte clamp(int val) {
|
private static byte clamp(int val) {
|
||||||
return (byte) Math.max(0, Math.min(255, val));
|
return (byte) Math.max(0, Math.min(255, val));
|
||||||
}
|
}
|
||||||
|
@@ -1491,4 +1491,40 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRawImageTypeAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||||
|
|
||||||
|
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||||
|
assertNull(rawType); // But no exception, please...
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||||
|
|
||||||
|
assertEquals(310, reader.getWidth(0));
|
||||||
|
assertEquals(384, reader.getHeight(0));
|
||||||
|
|
||||||
|
BufferedImage image = reader.read(0, null);
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(310, image.getWidth());
|
||||||
|
assertEquals(384, image.getHeight());
|
||||||
|
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -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";
|
||||||
|
|
||||||
|
@@ -89,8 +89,8 @@ public final class EXIFReader extends MetadataReader {
|
|||||||
|
|
||||||
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
|
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
|
||||||
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
|
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
|
||||||
List<IFD> ifds = new ArrayList<IFD>();
|
List<IFD> ifds = new ArrayList<>();
|
||||||
List<Entry> entries = new ArrayList<Entry>();
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
|
||||||
pInput.seek(pOffset);
|
pInput.seek(pOffset);
|
||||||
long nextOffset = -1;
|
long nextOffset = -1;
|
||||||
@@ -156,7 +156,7 @@ public final class EXIFReader extends MetadataReader {
|
|||||||
try {
|
try {
|
||||||
if (KNOWN_IFDS.contains(tagId)) {
|
if (KNOWN_IFDS.contains(tagId)) {
|
||||||
long[] pointerOffsets = getPointerOffsets(entry);
|
long[] pointerOffsets = getPointerOffsets(entry);
|
||||||
List<IFD> subIFDs = new ArrayList<IFD>(pointerOffsets.length);
|
List<IFD> subIFDs = new ArrayList<>(pointerOffsets.length);
|
||||||
|
|
||||||
for (long pointerOffset : pointerOffsets) {
|
for (long pointerOffset : pointerOffsets) {
|
||||||
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
||||||
@@ -177,8 +177,11 @@ public final class EXIFReader extends MetadataReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IIOException e) {
|
catch (IIOException e) {
|
||||||
// TODO: Issue warning without crashing...?
|
if (DEBUG) {
|
||||||
e.printStackTrace();
|
// TODO: Issue warning without crashing...?
|
||||||
|
System.err.println("Error parsing sub-IFD: " + tagId);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.tga;
|
package com.twelvemonkeys.imageio.plugins.tga;
|
||||||
|
|
||||||
interface TGA {
|
interface TGA {
|
||||||
|
byte[] MAGIC = {'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.', 0};
|
||||||
|
|
||||||
/** Fixed header size: 18.*/
|
/** Fixed header size: 18.*/
|
||||||
int HEADER_SIZE = 18;
|
int HEADER_SIZE = 18;
|
||||||
|
|
||||||
|
@@ -0,0 +1,187 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tga;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAExtensions.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class TGAExtensions {
|
||||||
|
public static final int EXT_AREA_SIZE = 495;
|
||||||
|
|
||||||
|
private String authorName;
|
||||||
|
private String authorComments;
|
||||||
|
|
||||||
|
private Calendar creationDate;
|
||||||
|
private String jobId;
|
||||||
|
|
||||||
|
private String softwareId;
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
private int backgroundColor;
|
||||||
|
private double pixelAspectRatio;
|
||||||
|
private double gamma;
|
||||||
|
|
||||||
|
private long colorCorrectionOffset;
|
||||||
|
private long postageStampOffset;
|
||||||
|
private long scanLineOffset;
|
||||||
|
|
||||||
|
private int attributeType;
|
||||||
|
|
||||||
|
private TGAExtensions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static TGAExtensions read(final ImageInputStream stream) throws IOException {
|
||||||
|
int extSize = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Should always be 495 for version 2.0, no newer version exists...
|
||||||
|
if (extSize < EXT_AREA_SIZE) {
|
||||||
|
throw new IIOException(String.format("TGA Extension Area size less than %d: %d", EXT_AREA_SIZE, extSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TGAExtensions extensions = new TGAExtensions();
|
||||||
|
extensions.authorName = readString(stream, 41);;
|
||||||
|
extensions.authorComments = readString(stream, 324);
|
||||||
|
extensions.creationDate = readDate(stream);
|
||||||
|
extensions.jobId = readString(stream, 41);
|
||||||
|
|
||||||
|
stream.skipBytes(6); // Job time, 3 shorts, hours/minutes/seconds elapsed
|
||||||
|
|
||||||
|
extensions.softwareId = readString(stream, 41);
|
||||||
|
|
||||||
|
// Software version (* 100) short + single byte ASCII (ie. 101 'b' for 1.01b)
|
||||||
|
int softwareVersion = stream.readUnsignedShort();
|
||||||
|
int softwareLetter = stream.readByte();
|
||||||
|
|
||||||
|
extensions.softwareVersion = softwareVersion != 0 && softwareLetter != ' '
|
||||||
|
? String.format("%d.%d%d", softwareVersion / 100, softwareVersion % 100, softwareLetter).trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
extensions.backgroundColor = stream.readInt(); // ARGB
|
||||||
|
|
||||||
|
extensions.pixelAspectRatio = readRational(stream);
|
||||||
|
extensions.gamma = readRational(stream);
|
||||||
|
|
||||||
|
extensions.colorCorrectionOffset = stream.readUnsignedInt();
|
||||||
|
extensions.postageStampOffset = stream.readUnsignedInt();
|
||||||
|
extensions.scanLineOffset = stream.readUnsignedInt();
|
||||||
|
|
||||||
|
// Offset 494 specifies Attribute type:
|
||||||
|
// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
|
||||||
|
// 1: undefined data in the Alpha field, can be ignored
|
||||||
|
// 2: undefined data in the Alpha field, but should be retained
|
||||||
|
// 3: useful Alpha channel data is present
|
||||||
|
// 4: pre-multiplied Alpha (see description below)
|
||||||
|
// 5 -127: RESERVED
|
||||||
|
// 128-255: Un-assigned
|
||||||
|
extensions.attributeType = stream.readUnsignedByte();
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double readRational(final ImageInputStream stream) throws IOException {
|
||||||
|
int numerator = stream.readUnsignedShort();
|
||||||
|
int denominator = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
return denominator != 0 ? numerator / (double) denominator : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Calendar readDate(final ImageInputStream stream) throws IOException {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.clear();
|
||||||
|
|
||||||
|
int month = stream.readUnsignedShort();
|
||||||
|
int date = stream.readUnsignedShort();
|
||||||
|
int year = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
int hourOfDay = stream.readUnsignedShort();
|
||||||
|
int minute = stream.readUnsignedShort();
|
||||||
|
int second = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Unused
|
||||||
|
if (month == 0 && year == 0 && date == 0 && hourOfDay == 0 && minute == 0 && second == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar.set(year, month - 1, date, hourOfDay, minute, second);
|
||||||
|
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
|
||||||
|
byte[] data = new byte[maxLength];
|
||||||
|
stream.readFully(data);
|
||||||
|
|
||||||
|
return asZeroTerminatedASCIIString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asZeroTerminatedASCIIString(final byte[] data) {
|
||||||
|
int len = data.length;
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] == 0) {
|
||||||
|
len = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(data, 0, len, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlpha() {
|
||||||
|
switch (attributeType) {
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlphaPremultiplied() {
|
||||||
|
switch (attributeType) {
|
||||||
|
case 4:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getThumbnailOffset() {
|
||||||
|
return postageStampOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorName() {
|
||||||
|
return authorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorComments() {
|
||||||
|
return authorComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Calendar getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftware() {
|
||||||
|
return softwareId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftwareVersion() {
|
||||||
|
return softwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPixelAspectRatio() {
|
||||||
|
return pixelAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBackgroundColor() {
|
||||||
|
return backgroundColor;
|
||||||
|
}
|
||||||
|
}
|
@@ -33,6 +33,7 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
|
|||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
@@ -51,6 +52,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
||||||
|
|
||||||
private TGAHeader header;
|
private TGAHeader header;
|
||||||
|
private TGAExtensions extensions;
|
||||||
|
|
||||||
protected TGAImageReader(final ImageReaderSpi provider) {
|
protected TGAImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
@@ -67,6 +70,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
header = null;
|
header = null;
|
||||||
|
extensions = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,7 +93,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||||
|
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
specifiers.add(rawType);
|
specifiers.add(rawType);
|
||||||
@@ -110,19 +114,29 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
||||||
case TGA.IMAGETYPE_MONOCHROME:
|
case TGA.IMAGETYPE_MONOCHROME:
|
||||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||||
return ImageTypeSpecifiers.createGrayscale(1, DataBuffer.TYPE_BYTE);
|
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
|
||||||
case TGA.IMAGETYPE_TRUECOLOR:
|
case TGA.IMAGETYPE_TRUECOLOR:
|
||||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
|
||||||
|
boolean hasAlpha = header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha();
|
||||||
|
boolean isAlphaPremultiplied = extensions != null && extensions.isAlphaPremultiplied();
|
||||||
|
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 16:
|
case 16:
|
||||||
|
if (hasAlpha) {
|
||||||
|
// USHORT_1555_ARGB...
|
||||||
|
return ImageTypeSpecifiers.createPacked(sRGB, 0x7C00, 0x03E0, 0x001F, 0x8000, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
|
||||||
|
}
|
||||||
|
// Default mask out alpha
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||||
case 24:
|
case 24:
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
case 32:
|
case 32:
|
||||||
// 4BYTE_BGRA...
|
// 4BYTE_BGRX...
|
||||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false);
|
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
|
||||||
|
// if hasAlpha is false
|
||||||
|
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
||||||
}
|
}
|
||||||
@@ -166,31 +180,32 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
DataInput input;
|
DataInput input;
|
||||||
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||||
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
|
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
input = imageInput;
|
input = imageInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
case 32:
|
case 32:
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
|
||||||
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
|
||||||
}
|
|
||||||
|
|
||||||
processImageProgress(100f * y / height);
|
|
||||||
|
|
||||||
if (height - 1 - y < srcRegion.y) {
|
|
||||||
break;
|
break;
|
||||||
}
|
case 16:
|
||||||
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(100f * y / height);
|
||||||
|
|
||||||
|
if (height - 1 - y < srcRegion.y) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -212,11 +227,11 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input.readFully(rowDataByte, 0, rowDataByte.length);
|
input.readFully(rowDataByte, 0, rowDataByte.length);
|
||||||
|
|
||||||
if (srcChannel.getNumBands() == 4) {
|
if (srcChannel.getNumBands() == 4 && (header.getAttributeBits() == 0 || extensions != null && !extensions.hasAlpha())) {
|
||||||
invertAlpha(rowDataByte);
|
// Remove the alpha channel (make pixels opaque) if there are no "attribute bits" (alpha bits)
|
||||||
|
removeAlpha32(rowDataByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsample horizontal
|
// Subsample horizontal
|
||||||
@@ -240,9 +255,9 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invertAlpha(final byte[] rowDataByte) {
|
private void removeAlpha32(final byte[] rowData) {
|
||||||
for (int i = 3; i < rowDataByte.length; i += 4) {
|
for (int i = 3; i < rowData.length; i += 4) {
|
||||||
rowDataByte[i] = (byte) (0xFF - rowDataByte[i]);
|
rowData[i] = (byte) 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,21 +328,154 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
private void readHeader() throws IOException {
|
private void readHeader() throws IOException {
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
// Read header
|
||||||
header = TGAHeader.read(imageInput);
|
header = TGAHeader.read(imageInput);
|
||||||
|
|
||||||
// System.err.println("header: " + header);
|
// System.err.println("header: " + header);
|
||||||
|
|
||||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||||
|
|
||||||
|
// Read footer, if 2.0 format (ends with TRUEVISION-XFILE\0)
|
||||||
|
skipToEnd(imageInput);
|
||||||
|
imageInput.seek(imageInput.getStreamPosition() - 26);
|
||||||
|
|
||||||
|
long extOffset = imageInput.readInt();
|
||||||
|
/*long devOffset = */imageInput.readInt(); // Ignored for now
|
||||||
|
|
||||||
|
byte[] magic = new byte[18];
|
||||||
|
imageInput.readFully(magic);
|
||||||
|
|
||||||
|
if (Arrays.equals(magic, TGA.MAGIC)) {
|
||||||
|
if (extOffset > 0) {
|
||||||
|
imageInput.seek(extOffset);
|
||||||
|
extensions = TGAExtensions.read(imageInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInput.seek(imageInput.getFlushedPosition());
|
imageInput.seek(imageInput.getFlushedPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
// TODO: Candidate util method
|
||||||
|
private static void skipToEnd(final ImageInputStream stream) throws IOException {
|
||||||
|
if (stream.length() > 0) {
|
||||||
|
// Seek to end of file
|
||||||
|
stream.seek(stream.length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Skip to end
|
||||||
|
long lastGood = stream.getStreamPosition();
|
||||||
|
|
||||||
|
while (stream.read() != -1) {
|
||||||
|
lastGood = stream.getStreamPosition();
|
||||||
|
stream.skipBytes(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.seek(lastGood);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (stream.read() == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Just continue reading to EOF...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbnail support
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readerSupportsThumbnails() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasThumbnails(final int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
readHeader();
|
readHeader();
|
||||||
|
|
||||||
return new TGAMetadata(header);
|
return extensions != null && extensions.getThumbnailOffset() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumThumbnails(final int imageIndex) throws IOException {
|
||||||
|
return hasThumbnails(imageIndex) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailWidth(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
Validate.isTrue(thumbnailIndex >= 0 && thumbnailIndex < getNumThumbnails(imageIndex), "thumbnailIndex >= numThumbnails");
|
||||||
|
|
||||||
|
imageInput.seek(extensions.getThumbnailOffset());
|
||||||
|
|
||||||
|
return imageInput.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailHeight(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
getThumbnailWidth(imageIndex, thumbnailIndex); // Laziness...
|
||||||
|
|
||||||
|
return imageInput.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage readThumbnail(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||||
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
|
int width = getThumbnailWidth(imageIndex, thumbnailIndex);
|
||||||
|
int height = getThumbnailHeight(imageIndex, thumbnailIndex);
|
||||||
|
|
||||||
|
// For thumbnail, always read entire image
|
||||||
|
Rectangle srcRegion = new Rectangle(width, height);
|
||||||
|
|
||||||
|
BufferedImage destination = getDestination(null, imageTypes, width, height);
|
||||||
|
WritableRaster destRaster = destination.getRaster();
|
||||||
|
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||||
|
|
||||||
|
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
|
|
||||||
|
// Thumbnail is always stored non-compressed, no need for RLE support
|
||||||
|
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
switch (header.getPixelDepth()) {
|
||||||
|
case 8:
|
||||||
|
case 24:
|
||||||
|
case 32:
|
||||||
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
processThumbnailProgress(100f * y / height);
|
||||||
|
|
||||||
|
if (height - 1 - y < srcRegion.y) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processThumbnailComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata support
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return new TGAMetadata(header, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
@@ -45,7 +45,8 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new TGAProviderInfo());
|
super(new TGAProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean canDecodeInput(final Object source) throws IOException {
|
@Override
|
||||||
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
if (!(source instanceof ImageInputStream)) {
|
if (!(source instanceof ImageInputStream)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
try {
|
try {
|
||||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
// NOTE: The TGA format does not have a magic identifier, so this is guesswork...
|
// NOTE: The original TGA format does not have a magic identifier, so this is guesswork...
|
||||||
// We'll try to match sane values, and hope no other files contains the same sequence.
|
// We'll try to match sane values, and hope no other files contains the same sequence.
|
||||||
|
|
||||||
stream.readUnsignedByte();
|
stream.readUnsignedByte();
|
||||||
@@ -88,11 +89,11 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
int colorMapStart = stream.readUnsignedShort();
|
int colorMapStart = stream.readUnsignedShort();
|
||||||
int colorMapSize = stream.readUnsignedShort();
|
int colorMapSize = stream.readUnsignedShort();
|
||||||
int colorMapDetph = stream.readUnsignedByte();
|
int colorMapDepth = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (colorMapSize == 0) {
|
if (colorMapSize == 0) {
|
||||||
// No color map, all 3 fields should be 0
|
// No color map, all 3 fields should be 0
|
||||||
if (colorMapStart!= 0 || colorMapDetph != 0) {
|
if (colorMapStart != 0 || colorMapDepth != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
if (colorMapStart >= colorMapSize) {
|
if (colorMapStart >= colorMapSize) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (colorMapDetph != 15 && colorMapDetph != 16 && colorMapDetph != 24 && colorMapDetph != 32) {
|
if (colorMapDepth != 15 && colorMapDepth != 16 && colorMapDepth != 24 && colorMapDepth != 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +135,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
// We're pretty sure by now, but there can still be false positives...
|
// We're pretty sure by now, but there can still be false positives...
|
||||||
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
|
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
|
||||||
|
// unless we are working with a local file (and the file may still be a valid original TGA without it).
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@@ -31,13 +31,17 @@ package com.twelvemonkeys.imageio.plugins.tga;
|
|||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
final class TGAMetadata extends AbstractMetadata {
|
final class TGAMetadata extends AbstractMetadata {
|
||||||
private final TGAHeader header;
|
private final TGAHeader header;
|
||||||
|
private final TGAExtensions extensions;
|
||||||
|
|
||||||
TGAMetadata(final TGAHeader header) {
|
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
|
this.extensions = extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,6 +49,8 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chroma.appendChild(csType);
|
||||||
|
|
||||||
switch (header.getImageType()) {
|
switch (header.getImageType()) {
|
||||||
case TGA.IMAGETYPE_MONOCHROME:
|
case TGA.IMAGETYPE_MONOCHROME:
|
||||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||||
@@ -62,15 +68,22 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
default:
|
default:
|
||||||
csType.setAttribute("name", "Unknown");
|
csType.setAttribute("name", "Unknown");
|
||||||
}
|
}
|
||||||
chroma.appendChild(csType);
|
|
||||||
|
|
||||||
// TODO: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||||
|
chroma.appendChild(numChannels);
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 16:
|
|
||||||
numChannels.setAttribute("value", Integer.toString(1));
|
numChannels.setAttribute("value", Integer.toString(1));
|
||||||
break;
|
break;
|
||||||
|
case 16:
|
||||||
|
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||||
|
numChannels.setAttribute("value", Integer.toString(4));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
numChannels.setAttribute("value", Integer.toString(3));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
numChannels.setAttribute("value", Integer.toString(3));
|
numChannels.setAttribute("value", Integer.toString(3));
|
||||||
break;
|
break;
|
||||||
@@ -78,11 +91,10 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
numChannels.setAttribute("value", Integer.toString(4));
|
numChannels.setAttribute("value", Integer.toString(4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
chroma.appendChild(numChannels);
|
|
||||||
|
|
||||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||||
blackIsZero.setAttribute("value", "TRUE");
|
|
||||||
chroma.appendChild(blackIsZero);
|
chroma.appendChild(blackIsZero);
|
||||||
|
blackIsZero.setAttribute("value", "TRUE");
|
||||||
|
|
||||||
// NOTE: TGA files may contain a color map, even if true color...
|
// NOTE: TGA files may contain a color map, even if true color...
|
||||||
// Not sure if this is a good idea to expose to the meta data,
|
// Not sure if this is a good idea to expose to the meta data,
|
||||||
@@ -94,16 +106,26 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||||
|
palette.appendChild(paletteEntry);
|
||||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||||
|
|
||||||
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||||
|
|
||||||
palette.appendChild(paletteEntry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||||
|
Color background = new Color(extensions.getBackgroundColor(), true);
|
||||||
|
|
||||||
|
IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
|
||||||
|
chroma.appendChild(backgroundColor);
|
||||||
|
|
||||||
|
backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
|
||||||
|
backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
|
||||||
|
backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
|
||||||
|
}
|
||||||
|
|
||||||
return chroma;
|
return chroma;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,15 +138,16 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||||
? "Uknown" : "RLE";
|
? "Uknown" : "RLE";
|
||||||
compressionTypeName.setAttribute("value", value);
|
compressionTypeName.setAttribute("value", value);
|
||||||
node.appendChild(compressionTypeName);
|
|
||||||
|
|
||||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
lossless.setAttribute("value", "TRUE");
|
|
||||||
node.appendChild(lossless);
|
node.appendChild(lossless);
|
||||||
|
lossless.setAttribute("value", "TRUE");
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
default:
|
default:
|
||||||
@@ -138,10 +161,12 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
|
||||||
node.appendChild(planarConfiguration);
|
node.appendChild(planarConfiguration);
|
||||||
|
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||||
|
|
||||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
switch (header.getImageType()) {
|
switch (header.getImageType()) {
|
||||||
case TGA.IMAGETYPE_COLORMAPPED:
|
case TGA.IMAGETYPE_COLORMAPPED:
|
||||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||||
@@ -154,13 +179,19 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.appendChild(sampleFormat);
|
|
||||||
|
|
||||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||||
|
node.appendChild(bitsPerSample);
|
||||||
|
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 16:
|
|
||||||
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
||||||
|
case 16:
|
||||||
|
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||||
|
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(3, "5"));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
||||||
@@ -170,12 +201,6 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.appendChild(bitsPerSample);
|
|
||||||
|
|
||||||
// TODO: Do we need MSB?
|
|
||||||
// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
|
||||||
// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +223,7 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
|
dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
switch (header.getOrigin()) {
|
switch (header.getOrigin()) {
|
||||||
case TGA.ORIGIN_LOWER_LEFT:
|
case TGA.ORIGIN_LOWER_LEFT:
|
||||||
@@ -214,28 +240,64 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dimension.appendChild(imageOrientation);
|
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
dimension.appendChild(pixelAspectRatio);
|
||||||
|
pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0");
|
||||||
|
|
||||||
return dimension;
|
return dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No document node
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDocumentNode() {
|
||||||
|
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||||
|
|
||||||
|
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||||
|
document.appendChild(formatVersion);
|
||||||
|
formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0");
|
||||||
|
|
||||||
|
// ImageCreationTime from extensions date
|
||||||
|
if (extensions != null && extensions.getCreationDate() != null) {
|
||||||
|
IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
|
||||||
|
document.appendChild(imageCreationTime);
|
||||||
|
|
||||||
|
Calendar date = extensions.getCreationDate();
|
||||||
|
|
||||||
|
imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
|
||||||
|
imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
|
||||||
|
imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
|
||||||
|
imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
|
||||||
|
imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
|
||||||
|
imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardTextNode() {
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
// TODO: Extra "developer area" and other stuff might go here...
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||||
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
||||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||||
|
|
||||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
|
||||||
textEntry.setAttribute("keyword", "identification");
|
|
||||||
textEntry.setAttribute("value", header.getIdentification());
|
|
||||||
text.appendChild(textEntry);
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (extensions != null) {
|
||||||
|
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : extensions.getSoftware() + " " + extensions.getSoftwareVersion());
|
||||||
|
appendTextEntry(text, "Artist", extensions.getAuthorName());
|
||||||
|
appendTextEntry(text, "UserComment", extensions.getAuthorComments());
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.hasChildNodes() ? text : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||||
|
if (value != null) {
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
parent.appendChild(textEntry);
|
||||||
|
textEntry.setAttribute("keyword", keyword);
|
||||||
|
textEntry.setAttribute("value", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No tiling
|
// No tiling
|
||||||
@@ -245,9 +307,23 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||||
|
|
||||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||||
alpha.setAttribute("value", header.getPixelDepth() == 32 ? "nonpremultiplied" : "none");
|
|
||||||
transparency.appendChild(alpha);
|
transparency.appendChild(alpha);
|
||||||
|
|
||||||
|
if (extensions != null) {
|
||||||
|
if (extensions.hasAlpha()) {
|
||||||
|
alpha.setAttribute("value", extensions.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alpha.setAttribute("value", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (header.getAttributeBits() == 8) {
|
||||||
|
alpha.setAttribute("value", "nonpremultiplied");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alpha.setAttribute("value", "none");
|
||||||
|
}
|
||||||
|
|
||||||
return transparency;
|
return transparency;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -28,24 +28,23 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author <a href="https://github.com/Schmidor">Oliver Schmidtmer</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class CCITTFaxDecoderStream extends FilterInputStream {
|
final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||||
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression",
|
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
||||||
// page 43.
|
|
||||||
|
|
||||||
private final int columns;
|
private final int columns;
|
||||||
private final byte[] decodedRow;
|
private final byte[] decodedRow;
|
||||||
@@ -62,8 +61,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private int changesReferenceRowCount;
|
private int changesReferenceRowCount;
|
||||||
private int changesCurrentRowCount;
|
private int changesCurrentRowCount;
|
||||||
|
|
||||||
private static final int EOL_CODE = 0x01; // 12 bit
|
|
||||||
|
|
||||||
private boolean optionG32D = false;
|
private boolean optionG32D = false;
|
||||||
|
|
||||||
@SuppressWarnings("unused") // Leading zeros for aligning EOL
|
@SuppressWarnings("unused") // Leading zeros for aligning EOL
|
||||||
@@ -72,29 +69,34 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private boolean optionUncompressed = false;
|
private boolean optionUncompressed = false;
|
||||||
|
|
||||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
|
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
|
||||||
final long options) {
|
final long options) {
|
||||||
super(Validate.notNull(stream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
// We know this is only used for b/w (1 bit)
|
// We know this is only used for b/w (1 bit)
|
||||||
this.decodedRow = new byte[(columns + 7) / 8];
|
this.decodedRow = new byte[(columns + 7) / 8];
|
||||||
this.type = type;
|
this.type = Validate.isTrue(
|
||||||
this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder,
|
type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
|
||||||
// "Only fill order 1 supported: %s"); //
|
type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||||
// TODO: Implement fillOrder == 2
|
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"
|
||||||
|
);
|
||||||
|
this.fillOrder = Validate.isTrue(
|
||||||
|
fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
|
||||||
|
fillOrder, "Expected fill order 1 or 2: %s"
|
||||||
|
);
|
||||||
|
|
||||||
this.changesReferenceRow = new int[columns];
|
this.changesReferenceRow = new int[columns];
|
||||||
this.changesCurrentRow = new int[columns];
|
this.changesCurrentRow = new int[columns];
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
||||||
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
||||||
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||||
@@ -107,7 +109,8 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
decodeRow();
|
decodeRow();
|
||||||
} catch (EOFException e) {
|
}
|
||||||
|
catch (EOFException e) {
|
||||||
// TODO: Rewrite to avoid throw/catch for normal flow...
|
// TODO: Rewrite to avoid throw/catch for normal flow...
|
||||||
if (decodedLength != 0) {
|
if (decodedLength != 0) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -126,16 +129,20 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
changesCurrentRowCount = 0;
|
changesCurrentRowCount = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int completeRun = 0;
|
int completeRun;
|
||||||
|
|
||||||
if (white) {
|
if (white) {
|
||||||
completeRun = decodeRun(whiteRunTree);
|
completeRun = decodeRun(whiteRunTree);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
completeRun = decodeRun(blackRunTree);
|
completeRun = decodeRun(blackRunTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
index += completeRun;
|
index += completeRun;
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
|
|
||||||
// Flip color for next run
|
// Flip color for next run
|
||||||
white = !white;
|
white = !white;
|
||||||
} while (index < columns);
|
} while (index < columns);
|
||||||
@@ -147,62 +154,79 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
changesCurrentRow = changesReferenceRow;
|
changesCurrentRow = changesReferenceRow;
|
||||||
changesReferenceRow = tmp;
|
changesReferenceRow = tmp;
|
||||||
|
|
||||||
if (changesReferenceRowCount == 0) {
|
|
||||||
changesReferenceRowCount = 3;
|
|
||||||
changesReferenceRow[0] = columns;
|
|
||||||
changesReferenceRow[1] = columns;
|
|
||||||
changesReferenceRow[2] = columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
changesCurrentRowCount = 0;
|
changesCurrentRowCount = 0;
|
||||||
|
|
||||||
mode: while (index < columns) {
|
mode: while (index < columns) {
|
||||||
// read mode
|
// read mode
|
||||||
Node n = codeTree.root;
|
Node n = codeTree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
n = n.walk(readBit());
|
n = n.walk(readBit());
|
||||||
|
|
||||||
if (n == null) {
|
if (n == null) {
|
||||||
continue mode;
|
continue mode;
|
||||||
} else if (n.isLeaf) {
|
}
|
||||||
|
else if (n.isLeaf) {
|
||||||
switch (n.value) {
|
switch (n.value) {
|
||||||
case VALUE_HMODE:
|
case VALUE_HMODE:
|
||||||
int runLength = 0;
|
int runLength;
|
||||||
runLength = decodeRun(white ? whiteRunTree : blackRunTree);
|
runLength = decodeRun(white ? whiteRunTree : blackRunTree);
|
||||||
index += runLength;
|
index += runLength;
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
|
|
||||||
runLength = decodeRun(white ? blackRunTree : whiteRunTree);
|
runLength = decodeRun(white ? blackRunTree : whiteRunTree);
|
||||||
index += runLength;
|
index += runLength;
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
break;
|
break;
|
||||||
case VALUE_PASSMODE:
|
|
||||||
index = changesReferenceRow[getNextChangingElement(index, white) + 1];
|
case VALUE_PASSMODE:
|
||||||
break;
|
int pChangingElement = getNextChangingElement(index, white) + 1;
|
||||||
default:
|
|
||||||
// Vertical mode (-3 to 3)
|
if (pChangingElement >= changesReferenceRowCount || pChangingElement == -1) {
|
||||||
index = changesReferenceRow[getNextChangingElement(index, white)] + n.value;
|
index = columns;
|
||||||
changesCurrentRow[changesCurrentRowCount] = index;
|
}
|
||||||
changesCurrentRowCount++;
|
else {
|
||||||
white = !white;
|
index = changesReferenceRow[pChangingElement];
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Vertical mode (-3 to 3)
|
||||||
|
int vChangingElement = getNextChangingElement(index, white);
|
||||||
|
|
||||||
|
if (vChangingElement >= changesReferenceRowCount || vChangingElement == -1) {
|
||||||
|
index = columns + n.value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
index = changesReferenceRow[vChangingElement] + n.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
changesCurrentRow[changesCurrentRowCount] = index;
|
||||||
|
changesCurrentRowCount++;
|
||||||
|
white = !white;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue mode;
|
continue mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getNextChangingElement(int a0, boolean white) {
|
private int getNextChangingElement(final int a0, final boolean white) {
|
||||||
int start = white ? 0 : 1;
|
int start = white ? 0 : 1;
|
||||||
|
|
||||||
for (int i = start; i < changesReferenceRowCount; i += 2) {
|
for (int i = start; i < changesReferenceRowCount; i += 2) {
|
||||||
if (a0 < changesReferenceRow[i]) {
|
if (a0 < changesReferenceRow[i] || (a0 == 0 && changesReferenceRow[i] == 0)) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeRowType2() throws IOException {
|
private void decodeRowType2() throws IOException {
|
||||||
@@ -214,20 +238,24 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
eof: while (true) {
|
eof: while (true) {
|
||||||
// read till next EOL code
|
// read till next EOL code
|
||||||
Node n = eolOnlyTree.root;
|
Node n = eolOnlyTree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Node tmp = n;
|
|
||||||
n = n.walk(readBit());
|
n = n.walk(readBit());
|
||||||
if (n == null)
|
|
||||||
|
if (n == null) {
|
||||||
continue eof;
|
continue eof;
|
||||||
|
}
|
||||||
|
|
||||||
if (n.isLeaf) {
|
if (n.isLeaf) {
|
||||||
break eof;
|
break eof;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean k = optionG32D ? readBit() : true;
|
|
||||||
if (k) {
|
if (!optionG32D || readBit()) {
|
||||||
decode1D();
|
decode1D();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
decode2D();
|
decode2D();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,23 +266,28 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
private void decodeRow() throws IOException {
|
private void decodeRow() throws IOException {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
decodeRowType2();
|
decodeRowType2();
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
decodeRowType4();
|
decodeRowType4();
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
decodeRowType6();
|
decodeRowType6();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i <= changesCurrentRowCount; i++) {
|
for (int i = 0; i <= changesCurrentRowCount; i++) {
|
||||||
int nextChange = columns;
|
int nextChange = columns;
|
||||||
|
|
||||||
if (i != changesCurrentRowCount) {
|
if (i != changesCurrentRowCount) {
|
||||||
nextChange = changesCurrentRow[i];
|
nextChange = changesCurrentRow[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextChange > columns) {
|
if (nextChange > columns) {
|
||||||
nextChange = columns;
|
nextChange = columns;
|
||||||
}
|
}
|
||||||
@@ -281,13 +314,14 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
if (index % 8 == 0) {
|
if (index % 8 == 0) {
|
||||||
decodedRow[byteIndex] = 0;
|
decodedRow[byteIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
white = !white;
|
white = !white;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index != columns) {
|
if (index != columns) {
|
||||||
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
||||||
}
|
}
|
||||||
@@ -295,43 +329,42 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
decodedLength = (index + 7) / 8;
|
decodedLength = (index + 7) / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decodeRun(Tree tree) throws IOException {
|
private int decodeRun(final Tree tree) throws IOException {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
||||||
Node n = tree.root;
|
Node n = tree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
boolean bit = readBit();
|
boolean bit = readBit();
|
||||||
n = n.walk(bit);
|
n = n.walk(bit);
|
||||||
if (n == null)
|
|
||||||
|
if (n == null) {
|
||||||
throw new IOException("Unknown code in Huffman RLE stream");
|
throw new IOException("Unknown code in Huffman RLE stream");
|
||||||
|
}
|
||||||
|
|
||||||
if (n.isLeaf) {
|
if (n.isLeaf) {
|
||||||
total += n.value;
|
total += n.value;
|
||||||
if (n.value < 64) {
|
if (n.value < 64) {
|
||||||
return total;
|
return total;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
n = tree.root;
|
n = tree.root;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetBuffer() {
|
private void resetBuffer() throws IOException {
|
||||||
for (int i = 0; i < decodedRow.length; i++) {
|
for (int i = 0; i < decodedRow.length; i++) {
|
||||||
decodedRow[i] = 0;
|
decodedRow[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (bufferPos == -1) {
|
if (bufferPos == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
readBit();
|
||||||
boolean skip = readBit();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,22 +374,29 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private boolean readBit() throws IOException {
|
private boolean readBit() throws IOException {
|
||||||
if (bufferPos < 0 || bufferPos > 7) {
|
if (bufferPos < 0 || bufferPos > 7) {
|
||||||
buffer = in.read();
|
buffer = in.read();
|
||||||
|
|
||||||
if (buffer == -1) {
|
if (buffer == -1) {
|
||||||
throw new EOFException("Unexpected end of Huffman RLE stream");
|
throw new EOFException("Unexpected end of Huffman RLE stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos = 0;
|
bufferPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSet;
|
boolean isSet;
|
||||||
|
|
||||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||||
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos++;
|
bufferPos++;
|
||||||
if (bufferPos > 7)
|
|
||||||
|
if (bufferPos > 7) {
|
||||||
bufferPos = -1;
|
bufferPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
return isSet;
|
return isSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,23 +468,25 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
throw new IOException("mark/reset not supported");
|
throw new IOException("mark/reset not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Node {
|
private static final class Node {
|
||||||
Node left;
|
Node left;
|
||||||
Node right;
|
Node right;
|
||||||
|
|
||||||
int value; // > 63 non term.
|
int value; // > 63 non term.
|
||||||
|
|
||||||
boolean canBeFill = false;
|
boolean canBeFill = false;
|
||||||
boolean isLeaf = false;
|
boolean isLeaf = false;
|
||||||
|
|
||||||
void set(boolean next, Node node) {
|
void set(final boolean next, final Node node) {
|
||||||
if (!next) {
|
if (!next) {
|
||||||
left = node;
|
left = node;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
right = node;
|
right = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Node walk(boolean next) {
|
Node walk(final boolean next) {
|
||||||
return next ? right : left;
|
return next ? right : left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,51 +496,69 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Tree {
|
private static final class Tree {
|
||||||
Node root = new Node();
|
final Node root = new Node();
|
||||||
|
|
||||||
void fill(int depth, int path, int value) throws IOException {
|
void fill(final int depth, final int path, final int value) throws IOException {
|
||||||
Node current = root;
|
Node current = root;
|
||||||
|
|
||||||
for (int i = 0; i < depth; i++) {
|
for (int i = 0; i < depth; i++) {
|
||||||
int bitPos = depth - 1 - i;
|
int bitPos = depth - 1 - i;
|
||||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||||
Node next = current.walk(isSet);
|
Node next = current.walk(isSet);
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
next = new Node();
|
next = new Node();
|
||||||
|
|
||||||
if (i == depth - 1) {
|
if (i == depth - 1) {
|
||||||
next.value = value;
|
next.value = value;
|
||||||
next.isLeaf = true;
|
next.isLeaf = true;
|
||||||
}
|
}
|
||||||
if (path == 0)
|
|
||||||
|
if (path == 0) {
|
||||||
next.canBeFill = true;
|
next.canBeFill = true;
|
||||||
|
}
|
||||||
|
|
||||||
current.set(isSet, next);
|
current.set(isSet, next);
|
||||||
} else {
|
|
||||||
if (next.isLeaf)
|
|
||||||
throw new IOException("node is leaf, no other following");
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (next.isLeaf) {
|
||||||
|
throw new IOException("node is leaf, no other following");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fill(int depth, int path, Node node) throws IOException {
|
void fill(final int depth, final int path, final Node node) throws IOException {
|
||||||
Node current = root;
|
Node current = root;
|
||||||
|
|
||||||
for (int i = 0; i < depth; i++) {
|
for (int i = 0; i < depth; i++) {
|
||||||
int bitPos = depth - 1 - i;
|
int bitPos = depth - 1 - i;
|
||||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||||
Node next = current.walk(isSet);
|
Node next = current.walk(isSet);
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
if (i == depth - 1) {
|
if (i == depth - 1) {
|
||||||
next = node;
|
next = node;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
next = new Node();
|
next = new Node();
|
||||||
}
|
}
|
||||||
if (path == 0)
|
|
||||||
|
if (path == 0) {
|
||||||
next.canBeFill = true;
|
next.canBeFill = true;
|
||||||
|
}
|
||||||
|
|
||||||
current.set(isSet, next);
|
current.set(isSet, next);
|
||||||
} else {
|
|
||||||
if (next.isLeaf)
|
|
||||||
throw new IOException("node is leaf, no other following");
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (next.isLeaf) {
|
||||||
|
throw new IOException("node is leaf, no other following");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,105 +566,148 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
static final short[][] BLACK_CODES = {
|
static final short[][] BLACK_CODES = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x3, },
|
0x3,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x4, 0x5, },
|
0x4, 0x5,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
0x4, 0x5, 0x7, },
|
0x4, 0x5, 0x7,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
0x4, 0x7, },
|
0x4, 0x7,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
0x18, },
|
0x18,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
0x17, 0x18, 0x37, 0x8, 0xf, },
|
0x17, 0x18, 0x37, 0x8, 0xf,
|
||||||
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd, },
|
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
||||||
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
||||||
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
||||||
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb, },
|
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
|
||||||
|
},
|
||||||
{ // 13 bits
|
{ // 13 bits
|
||||||
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
||||||
0x74, 0x75, 0x76, 0x77, } };
|
0x74, 0x75, 0x76, 0x77,
|
||||||
|
}
|
||||||
|
};
|
||||||
static final short[][] BLACK_RUN_LENGTHS = {
|
static final short[][] BLACK_RUN_LENGTHS = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
3, 2, },
|
3, 2,
|
||||||
|
},
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
1, 4, },
|
1, 4,
|
||||||
|
},
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
6, 5, },
|
6, 5,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
7, },
|
7,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
9, 8, },
|
9, 8,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
10, 11, 12, },
|
10, 11, 12,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
13, 14, },
|
13, 14,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
15, },
|
15,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
16, 17, 0, 18, 64, },
|
16, 17, 0, 18, 64,
|
||||||
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920, },
|
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
||||||
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
||||||
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43, },
|
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43,
|
||||||
|
},
|
||||||
{ // 13 bits
|
{ // 13 bits
|
||||||
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
||||||
1152, 1216, } };
|
1152, 1216,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final short[][] WHITE_CODES = {
|
public static final short[][] WHITE_CODES = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x7, 0x8, 0xb, 0xc, 0xe, 0xf, },
|
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, },
|
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, },
|
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc, },
|
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
||||||
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
||||||
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb, },
|
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, },
|
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x8, 0xc, 0xd, },
|
0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, } };
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final short[][] WHITE_RUN_LENGTHS = {
|
public static final short[][] WHITE_RUN_LENGTHS = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
2, 3, 4, 5, 6, 7, },
|
2, 3, 4, 5, 6, 7,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
128, 8, 9, 64, 10, 11, },
|
128, 8, 9, 64, 10, 11,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
192, 1664, 16, 17, 13, 14, 15, 1, 12, },
|
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19, },
|
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
|
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
|
||||||
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48, },
|
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
||||||
{ // 9
|
},
|
||||||
// bits
|
{ // 9 bits
|
||||||
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, },
|
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
1792, 1856, 1920, },
|
1792, 1856, 1920,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, } };
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
final static Node EOL;
|
final static Node EOL;
|
||||||
final static Node FILL;
|
final static Node FILL;
|
||||||
@@ -631,8 +734,9 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
try {
|
try {
|
||||||
eolOnlyTree.fill(12, 0, FILL);
|
eolOnlyTree.fill(12, 0, FILL);
|
||||||
eolOnlyTree.fill(12, 1, EOL);
|
eolOnlyTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blackRunTree = new Tree();
|
blackRunTree = new Tree();
|
||||||
@@ -644,9 +748,11 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
blackRunTree.fill(12, 0, FILL);
|
blackRunTree.fill(12, 0, FILL);
|
||||||
blackRunTree.fill(12, 1, EOL);
|
blackRunTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
whiteRunTree = new Tree();
|
whiteRunTree = new Tree();
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < WHITE_CODES.length; i++) {
|
for (int i = 0; i < WHITE_CODES.length; i++) {
|
||||||
@@ -654,10 +760,12 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
|
whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
whiteRunTree.fill(12, 0, FILL);
|
whiteRunTree.fill(12, 0, FILL);
|
||||||
whiteRunTree.fill(12, 1, EOL);
|
whiteRunTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
codeTree = new Tree();
|
codeTree = new Tree();
|
||||||
@@ -671,8 +779,9 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
codeTree.fill(3, 2, -1); // V_L(1)
|
codeTree.fill(3, 2, -1); // V_L(1)
|
||||||
codeTree.fill(6, 2, -2); // V_L(2)
|
codeTree.fill(6, 2, -2); // V_L(2)
|
||||||
codeTree.fill(7, 2, -3); // V_L(3)
|
codeTree.fill(7, 2, -3); // V_L(3)
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -190,7 +190,6 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
|
|
||||||
public static Decoder create(boolean oldBitReversedStream) {
|
public static Decoder create(boolean oldBitReversedStream) {
|
||||||
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
||||||
// return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWTreeDecoder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class LZWSpecDecoder extends LZWDecoder {
|
static final class LZWSpecDecoder extends LZWDecoder {
|
||||||
|
@@ -62,6 +62,8 @@ interface TIFFExtension {
|
|||||||
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
|
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
|
||||||
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
|
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
|
||||||
|
|
||||||
|
int FILL_RIGHT_TO_LEFT = 2;
|
||||||
|
|
||||||
int SAMPLEFORMAT_INT = 2;
|
int SAMPLEFORMAT_INT = 2;
|
||||||
int SAMPLEFORMAT_FP = 3;
|
int SAMPLEFORMAT_FP = 3;
|
||||||
int SAMPLEFORMAT_UNDEFINED = 4;
|
int SAMPLEFORMAT_UNDEFINED = 4;
|
||||||
|
@@ -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;
|
||||||
@@ -58,13 +61,16 @@ import javax.imageio.spi.ImageReaderSpi;
|
|||||||
import javax.imageio.spi.ServiceRegistry;
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.color.CMMException;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
import java.awt.image.*;
|
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 +88,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 +126,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 +142,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 +174,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +334,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
switch (samplesPerPixel) {
|
switch (samplesPerPixel) {
|
||||||
case 1:
|
case 1:
|
||||||
// TIFF 6.0 Spec says: 1, 4 or 8 for baseline (1 for bi-level, 4/8 for gray)
|
// TIFF 6.0 Spec says: 1, 4 or 8 for baseline (1 for bi-level, 4/8 for gray)
|
||||||
// ImageTypeSpecifier supports 1, 2, 4, 8 or 16 bits, we'll go with that for now
|
// ImageTypeSpecifier supports 1, 2, 4, 8 or 16 bits per sample, we'll support 32 bits as well.
|
||||||
|
// (Chunky or planar makes no difference for a single channel).
|
||||||
if (profile != null && profile.getColorSpaceType() != ColorSpace.TYPE_GRAY) {
|
if (profile != null && profile.getColorSpaceType() != ColorSpace.TYPE_GRAY) {
|
||||||
processWarningOccurred(String.format("Embedded ICC color profile (type %s), is incompatible with image data (GRAY/type 6). Ignoring profile.", profile.getColorSpaceType()));
|
processWarningOccurred(String.format("Embedded ICC color profile (type %s), is incompatible with image data (GRAY/type 6). Ignoring profile.", profile.getColorSpaceType()));
|
||||||
profile = null;
|
profile = null;
|
||||||
@@ -274,10 +349,46 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
|
else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
|
||||||
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0}, dataType, false, false);
|
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0}, dataType, false, false);
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
// TODO: If ExtraSamples is used, PlanarConfiguration must be taken into account also for gray data
|
|
||||||
|
|
||||||
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8 or 1/16): %d/%d", samplesPerPixel, bitsPerSample));
|
throw new IIOException(String.format("Unsupported BitsPerSample for Bi-level/Gray TIFF (expected 1, 2, 4, 8, 16 or 32): %d", bitsPerSample));
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
// Gray + alpha. We'll support:
|
||||||
|
// * 8, 16 or 32 bits per sample
|
||||||
|
// * Associated (pre-multiplied) or unassociated (non-pre-multiplied) alpha
|
||||||
|
// * Chunky (interleaved) or planar (banded) data
|
||||||
|
if (profile != null && profile.getColorSpaceType() != ColorSpace.TYPE_GRAY) {
|
||||||
|
processWarningOccurred(String.format("Embedded ICC color profile (type %s), is incompatible with image data (GRAY/type 6). Ignoring profile.", profile.getColorSpaceType()));
|
||||||
|
profile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpaces.createColorSpace(profile);
|
||||||
|
|
||||||
|
// ExtraSamples 0=unspecified, 1=associated (pre-multiplied), 2=unassociated (TODO: Support unspecified, not alpha)
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
|
if (cs == ColorSpace.getInstance(ColorSpace.CS_GRAY) && (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32)) {
|
||||||
|
switch (planarConfiguration) {
|
||||||
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
|
return ImageTypeSpecifiers.createGrayscale(bitsPerSample, dataType, extraSamples[0] == 1);
|
||||||
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
|
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
|
||||||
|
switch (planarConfiguration) {
|
||||||
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
|
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1}, dataType, true, extraSamples[0] == 1);
|
||||||
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
|
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException(String.format("Unsupported BitsPerSample for Gray + Alpha TIFF (expected 8, 16 or 32): %d", bitsPerSample));
|
||||||
|
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8, 1/16 or 1/32, or 2/8, 2/16 or 2/32): %d/%d", samplesPerPixel, bitsPerSample));
|
||||||
}
|
}
|
||||||
|
|
||||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||||
@@ -324,6 +435,11 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (bitsPerSample == 4) {
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
|
return ImageTypeSpecifier.createPacked(cs, 0xF000, 0xF00, 0xF0, 0xF, DataBuffer.TYPE_USHORT, extraSamples[0] == 1);
|
||||||
|
}
|
||||||
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
||||||
@@ -411,14 +527,32 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
case TIFFBaseline.SAMPLEFORMAT_UINT:
|
||||||
return bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
|
return bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
|
||||||
case TIFFExtension.SAMPLEFORMAT_INT:
|
case TIFFExtension.SAMPLEFORMAT_INT:
|
||||||
if (bitsPerSample == 16) {
|
switch (bitsPerSample) {
|
||||||
return DataBuffer.TYPE_SHORT;
|
case 8:
|
||||||
|
return DataBuffer.TYPE_BYTE;
|
||||||
|
case 16:
|
||||||
|
return DataBuffer.TYPE_SHORT;
|
||||||
|
case 32:
|
||||||
|
return DataBuffer.TYPE_INT;
|
||||||
}
|
}
|
||||||
throw new IIOException("Unsupported BitPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
|
|
||||||
|
throw new IIOException("Unsupported BitsPerSample for SampleFormat 2/Signed Integer (expected 8/16/32): " + bitsPerSample);
|
||||||
|
|
||||||
case TIFFExtension.SAMPLEFORMAT_FP:
|
case TIFFExtension.SAMPLEFORMAT_FP:
|
||||||
throw new IIOException("Unsupported TIFF SampleFormat: (3/Floating point)");
|
if (bitsPerSample == 32) {
|
||||||
|
return DataBuffer.TYPE_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IIOException("Unsupported BitsPerSample for SampleFormat 3/Floating Point (expected 32): " + bitsPerSample);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -585,7 +719,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
|
int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
|
||||||
int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
|
int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
|
||||||
WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
|
// WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
|
||||||
|
WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster();
|
||||||
Rectangle clip = new Rectangle(srcRegion);
|
Rectangle clip = new Rectangle(srcRegion);
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
@@ -888,7 +1023,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
|
||||||
@@ -1259,6 +1394,46 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DataBuffer.TYPE_FLOAT:
|
||||||
|
float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData();
|
||||||
|
|
||||||
|
for (int row = startRow; row < startRow + rowsInTile; row++) {
|
||||||
|
if (row >= srcRegion.y + srcRegion.height) {
|
||||||
|
break; // We're done with this tile
|
||||||
|
}
|
||||||
|
|
||||||
|
readFully(input, rowDataFloat);
|
||||||
|
|
||||||
|
if (row >= srcRegion.y) {
|
||||||
|
// normalizeBlack(interpretation, rowDataFloat);
|
||||||
|
|
||||||
|
// Subsample horizontal
|
||||||
|
if (xSub != 1) {
|
||||||
|
for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
|
||||||
|
System.arraycopy(rowDataFloat, x * xSub, rowDataFloat, x, numBands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raster.setDataElements(startCol, row - srcRegion.y, tileRowRaster);
|
||||||
|
}
|
||||||
|
// Else skip data
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Candidate util method (with off/len + possibly byte order)
|
||||||
|
private void readFully(final DataInput input, final float[] rowDataFloat) throws IOException {
|
||||||
|
if (input instanceof ImageInputStream) {
|
||||||
|
ImageInputStream imageInputStream = (ImageInputStream) input;
|
||||||
|
imageInputStream.readFully(rowDataFloat, 0, rowDataFloat.length);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int k = 0; k < rowDataFloat.length; k++) {
|
||||||
|
rowDataFloat[k] = input.readFloat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1322,7 +1497,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||||
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
||||||
case TIFFExtension.COMPRESSION_LZW:
|
case TIFFExtension.COMPRESSION_LZW:
|
||||||
return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), width * bands);
|
return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), Math.max(width * bands, 1024));
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
@@ -1394,14 +1569,24 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICC_Profile getICCProfile() {
|
private ICC_Profile getICCProfile() throws IOException {
|
||||||
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
||||||
if (entry == null) {
|
|
||||||
return null;
|
if (entry != null) {
|
||||||
|
byte[] value = (byte[]) entry.getValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// WEIRDNESS: Reading profile from InputStream is somehow more compatible
|
||||||
|
// than reading from byte array (chops off extra bytes + validates profile).
|
||||||
|
ICC_Profile profile = ICC_Profile.getInstance(new ByteArrayInputStream(value));
|
||||||
|
return ColorSpaces.validateProfile(profile);
|
||||||
|
}
|
||||||
|
catch (CMMException | IllegalArgumentException ignore) {
|
||||||
|
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + ignore.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] value = (byte[]) entry.getValue();
|
return null;
|
||||||
return ICC_Profile.getInstance(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Tiling support
|
// TODO: Tiling support
|
||||||
@@ -1500,27 +1685,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 +1724,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 +1741,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,7 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
|
|||||||
protected TIFFProviderInfo() {
|
protected TIFFProviderInfo() {
|
||||||
super(
|
super(
|
||||||
TIFFProviderInfo.class,
|
TIFFProviderInfo.class,
|
||||||
new String[] {"tiff", "TIFF"},
|
new String[] {"tiff", "TIFF", "tif", "TIF"},
|
||||||
new String[] {"tif", "tiff"},
|
new String[] {"tif", "tiff"},
|
||||||
new String[] {
|
new String[] {
|
||||||
"image/tiff", "image/x-tiff"
|
"image/tiff", "image/x-tiff"
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
@@ -80,7 +80,22 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
|
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
|
||||||
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
|
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
|
||||||
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg_no_profile.tif"), new Dimension(150, 63)), // CMYK, JPEG compressed, no ICC profile
|
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg_no_profile.tif"), new Dimension(150, 63)), // CMYK, JPEG compressed, no ICC profile
|
||||||
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)) // CMYK, JPEG compressed, with ICC profile
|
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile
|
||||||
|
new TestData(getClassLoaderResource("/tiff/grayscale-alpha.tiff"), new Dimension(248, 351)), // Gray + unassociated alpha
|
||||||
|
new TestData(getClassLoaderResource("/tiff/signed-integral-8bit.tif"), new Dimension(439, 167)), // Gray, 8 bit *signed* integral
|
||||||
|
new TestData(getClassLoaderResource("/tiff/floatingpoint-32bit.tif"), new Dimension(300, 100)), // RGB, 32 bit floating point
|
||||||
|
new TestData(getClassLoaderResource("/tiff/general-cmm-error.tif"), new Dimension(1181, 860)), // RGB, LZW compression with broken/incompatible ICC profile
|
||||||
|
new TestData(getClassLoaderResource("/tiff/lzw-rgba-padded-icc.tif"), new Dimension(19, 11)), // RGBA, LZW compression with padded ICC profile
|
||||||
|
new TestData(getClassLoaderResource("/tiff/lzw-rgba-4444.tif"), new Dimension(64, 64)), // RGBA, LZW compression with UINT 4/4/4/4 + gray 2/2
|
||||||
|
new TestData(getClassLoaderResource("/tiff/lzw-buffer-overflow.tif"), new Dimension(5, 49)), // RGBA, LZW compression, will throw IOOBE if small buffer
|
||||||
|
// CCITT
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group3_2d.tif"), new Dimension(6, 4)), // B/W, CCITT T4 2D
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group3_2d_fill.tif"), new Dimension(6, 4)), // B/W, CCITT T4 2D
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group3_2d_lsb2msb.tif"), new Dimension(6, 4)), // B/W, CCITT T4 2D, LSB
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ccitt/group4.tif"), new Dimension(6, 4)), // B/W, CCITT T6 1D
|
||||||
|
new TestData(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif"), new Dimension(2480, 3518)) // B/W, CCITT T4
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,9 +156,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
@Test
|
@Test
|
||||||
public void testReadOldStyleJPEGGrayscale() throws IOException {
|
public void testReadOldStyleJPEGGrayscale() throws IOException {
|
||||||
TestData testData = new TestData(getClassLoaderResource("/tiff/grayscale-old-style-jpeg.tiff"), new Dimension(600, 600));
|
TestData testData = new TestData(getClassLoaderResource("/tiff/grayscale-old-style-jpeg.tiff"), new Dimension(600, 600));
|
||||||
ImageInputStream stream = testData.getInputStream();
|
|
||||||
|
|
||||||
try {
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
TIFFImageReader reader = createReader();
|
TIFFImageReader reader = createReader();
|
||||||
reader.setInput(stream);
|
reader.setInput(stream);
|
||||||
BufferedImage image = reader.read(0);
|
BufferedImage image = reader.read(0);
|
||||||
@@ -151,18 +165,13 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadIncompatibleICCProfileIgnoredWithWarning() throws IOException {
|
public void testReadIncompatibleICCProfileIgnoredWithWarning() throws IOException {
|
||||||
TestData testData = new TestData(getClassLoaderResource("/tiff/rgb-with-embedded-cmyk-icc.tif"), new Dimension(1500, 1500));
|
TestData testData = new TestData(getClassLoaderResource("/tiff/rgb-with-embedded-cmyk-icc.tif"), new Dimension(1500, 1500));
|
||||||
|
|
||||||
ImageInputStream stream = testData.getInputStream();
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
|
|
||||||
try {
|
|
||||||
TIFFImageReader reader = createReader();
|
TIFFImageReader reader = createReader();
|
||||||
reader.setInput(stream);
|
reader.setInput(stream);
|
||||||
|
|
||||||
@@ -175,18 +184,13 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
||||||
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("ICC"));
|
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("ICC"));
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testColorMap8Bit() throws IOException {
|
public void testColorMap8Bit() throws IOException {
|
||||||
TestData testData = new TestData(getClassLoaderResource("/tiff/scan-lzw-8bit-colormap.tiff"), new Dimension(2550, 3300));
|
TestData testData = new TestData(getClassLoaderResource("/tiff/scan-lzw-8bit-colormap.tiff"), new Dimension(2550, 3300));
|
||||||
|
|
||||||
ImageInputStream stream = testData.getInputStream();
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
|
|
||||||
try {
|
|
||||||
TIFFImageReader reader = createReader();
|
TIFFImageReader reader = createReader();
|
||||||
reader.setInput(stream);
|
reader.setInput(stream);
|
||||||
|
|
||||||
@@ -202,8 +206,26 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
assertEquals(0xffffffff, image.getRGB(0, 0)); // The pixel at 0, 0 should be white, not black
|
assertEquals(0xffffffff, image.getRGB(0, 0)); // The pixel at 0, 0 should be white, not black
|
||||||
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("ColorMap"));
|
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("ColorMap"));
|
||||||
}
|
}
|
||||||
finally {
|
}
|
||||||
stream.close();
|
|
||||||
|
@Test
|
||||||
|
public void testBadICCProfile() throws IOException {
|
||||||
|
TestData testData = new TestData(getClassLoaderResource("/tiff/general-cmm-error.tif"), new Dimension(1181, 864));
|
||||||
|
|
||||||
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
|
TIFFImageReader reader = createReader();
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
|
||||||
|
reader.addIIOReadWarningListener(warningListener);
|
||||||
|
|
||||||
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRegion(new Rectangle(8, 8));
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(new Dimension(8, 8), new Dimension(image.getWidth(), image.getHeight()));
|
||||||
|
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("ICC profile"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-4444.tif
Normal file
BIN
imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-4444.tif
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -26,10 +26,12 @@
|
|||||||
<!-- Support -->
|
<!-- Support -->
|
||||||
<module>imageio-core</module>
|
<module>imageio-core</module>
|
||||||
<module>imageio-metadata</module>
|
<module>imageio-metadata</module>
|
||||||
|
<!--<module>imageio-stream</module>-->
|
||||||
<module>imageio-clippath</module>
|
<module>imageio-clippath</module>
|
||||||
|
|
||||||
<!-- Stand-alone readers/writers -->
|
<!-- Stand-alone readers/writers -->
|
||||||
<module>imageio-bmp</module>
|
<module>imageio-bmp</module>
|
||||||
|
<module>imageio-hdr</module>
|
||||||
<module>imageio-icns</module>
|
<module>imageio-icns</module>
|
||||||
<module>imageio-iff</module>
|
<module>imageio-iff</module>
|
||||||
<module>imageio-jpeg</module>
|
<module>imageio-jpeg</module>
|
||||||
@@ -75,14 +77,14 @@
|
|||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
@@ -137,7 +139,7 @@
|
|||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@@ -91,14 +91,14 @@
|
|||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -44,14 +44,14 @@
|
|||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@@ -72,14 +72,14 @@
|
|||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@@ -32,14 +32,14 @@
|
|||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<classifier>tests</classifier>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user