mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-23 00:00:05 -04:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7de8231471 | |||
| 0de9f79029 | |||
| eeb56acdde | |||
| a6862cfec8 | |||
| f8284700b4 | |||
| 38caeb22e0 | |||
| b2c5915db8 | |||
| 3911191b04 | |||
| bc328419ac | |||
| da4efe98bf | |||
| 6653f4a85d | |||
| 511a29beb9 | |||
| 5617b4323c | |||
| 16d0af357d | |||
| 74927d5396 | |||
| 7e809dd834 | |||
| 7aa95a08bc | |||
| c28963ae49 | |||
| 0327f5fc1a | |||
| 1c59057c30 | |||
| 3e1f85c4dc | |||
| 11227a68a0 | |||
| 62ba73a30e | |||
| 1f33afb5a1 | |||
| 9d3f271867 | |||
| 812e12acb0 | |||
| 060b6cf852 | |||
| e68ce7ffd1 | |||
| 778cdef69c | |||
| d46a76fca8 | |||
| 105a1ee466 | |||
| aa030f526c | |||
| 976e5d6210 | |||
| 6daca00fcd | |||
| ef05872934 | |||
| 1ddab866fd | |||
| ce997a6951 | |||
| 23bf5cb7b2 | |||
| 564778f415 | |||
| e28bf8fb44 | |||
| cf8d630d01 | |||
| 0ff7224912 | |||
| 196081a317 | |||
| ff50180d86 | |||
| 8f2c482167 | |||
| eab24890ca | |||
| cd42d81817 | |||
| ba5c667b6c | |||
| 4e9fa9442c |
@@ -1,16 +1,10 @@
|
|||||||
**What is fixed**
|
**What is fixed** Add link to the issue this PR fixes.
|
||||||
|
|
||||||
Add link to the issue this PR fixes.
|
Example: Fixes #42.
|
||||||
|
|
||||||
Fixes #42.
|
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
|
||||||
|
|
||||||
**Why is this change proposed**
|
**What is changed** Briefly describe the changes proposed in this pull request:
|
||||||
|
|
||||||
If this change does *not* fix an open issue, briefly describe the rationale for this PR.
|
|
||||||
|
|
||||||
**What is changed**
|
|
||||||
|
|
||||||
Briefly describe the changes proposed in this pull request:
|
|
||||||
|
|
||||||
* Fixed rare exception happening in `x >= 42` case
|
* Fixed rare exception happening in `x >= 42` case
|
||||||
* Small optimization of `decompress()` method
|
* Small optimization of `decompress()` method
|
||||||
|
|||||||
+5
-3
@@ -1,9 +1,11 @@
|
|||||||
dist: trusty
|
dist: trusty
|
||||||
language: java
|
language: java
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8 # Legacy
|
- oraclejdk8 # LTS until Mar 2022
|
||||||
- oraclejdk11 # LTS
|
- oraclejdk11 # LTS until Sep 2023
|
||||||
- oraclejdk15 # Latest
|
- oraclejdk15 # out of support
|
||||||
|
- oraclejdk16 # out of support
|
||||||
|
- oraclejdk17 # LTS until Sep 2026
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
# Extra job, testing legacy CMM option
|
# Extra job, testing legacy CMM option
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
[](https://travis-ci.org/haraldk/TwelveMonkeys)
|
[](https://travis-ci.com/github/haraldk/TwelveMonkeys)
|
||||||
[](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
|
[](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
|
||||||
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||||
[](https://paypal.me/haraldk76/100)
|
[](https://paypal.me/haraldk76/100)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
|
TwelveMonkeys ImageIO provides extended image file format support for the Java platform, through plugins for the `javax.imageio.*` package.
|
||||||
|
|
||||||
These plugins extend the number of image file formats supported in Java, using the `javax.imageio.*` package.
|
The main goal of this project is to provide support for formats not covered by the JRE itself.
|
||||||
The main purpose of this project is to provide support for formats not covered by the JRE itself.
|
Support for these formats is important, to be able to read data found
|
||||||
|
|
||||||
Support for formats is important, both to be able to read data found
|
|
||||||
"in the wild", as well as to maintain access to data in legacy formats.
|
"in the wild", as well as to maintain access to data in legacy formats.
|
||||||
Because there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
As there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
||||||
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.twelvemonkeys.bom</groupId>
|
<groupId>com.twelvemonkeys.bom</groupId>
|
||||||
@@ -123,6 +123,11 @@
|
|||||||
<artifactId>imageio-tiff</artifactId>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-webp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-xwd</artifactId>
|
<artifactId>imageio-xwd</artifactId>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-image</artifactId>
|
<artifactId>common-image</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ import java.awt.*;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.BufferedImageOp;
|
||||||
|
import java.awt.image.ColorModel;
|
||||||
|
import java.awt.image.ImagingOpException;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.RasterOp;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
||||||
@@ -70,6 +76,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
||||||
try {
|
try {
|
||||||
@@ -80,10 +87,9 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics2D g2d = null;
|
Graphics2D g2d = dst.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g2d = dst.createGraphics();
|
|
||||||
int interpolationType = delegate.getInterpolationType();
|
int interpolationType = delegate.getInterpolationType();
|
||||||
|
|
||||||
if (interpolationType > 0) {
|
if (interpolationType > 0) {
|
||||||
@@ -109,9 +115,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (g2d != null) {
|
g2d.dispose();
|
||||||
g2d.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ import java.awt.image.BufferedImage;
|
|||||||
*/
|
*/
|
||||||
public class BufferedImageIcon implements Icon {
|
public class BufferedImageIcon implements Icon {
|
||||||
private final BufferedImage image;
|
private final BufferedImage image;
|
||||||
private int width;
|
private final int width;
|
||||||
private int height;
|
private final int height;
|
||||||
private final boolean fast;
|
private final boolean fast;
|
||||||
|
|
||||||
public BufferedImageIcon(BufferedImage pImage) {
|
public BufferedImageIcon(BufferedImage pImage) {
|
||||||
@@ -81,11 +81,10 @@ public class BufferedImageIcon implements Icon {
|
|||||||
else {
|
else {
|
||||||
//System.out.println("Scaling using interpolation");
|
//System.out.println("Scaling using interpolation");
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
|
||||||
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
transform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
g2.drawImage(image, transform, null);
|
||||||
g2.drawImage(image, xform, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,6 +587,7 @@ class IndexImage {
|
|||||||
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
||||||
* This version will be removed in a later version of the API.
|
* This version will be removed in a later version of the API.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
||||||
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-16
@@ -30,17 +30,26 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import org.junit.Test;
|
import static java.lang.Math.min;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.BufferedImageOp;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.ImagingOpException;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.RasterOp;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AffineTransformOpTest.
|
* AffineTransformOpTest.
|
||||||
@@ -101,6 +110,7 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
private final int width = 30;
|
private final int width = 30;
|
||||||
private final int height = 20;
|
private final int height = 20;
|
||||||
|
private final double anchor = min(width, height) / 2.0;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetPoint2D() {
|
public void testGetPoint2D() {
|
||||||
@@ -128,8 +138,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateBIStandard() {
|
public void testFilterRotateBIStandard() {
|
||||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (Integer type : TYPES) {
|
for (Integer type : TYPES) {
|
||||||
BufferedImage image = new BufferedImage(width, height, type);
|
BufferedImage image = new BufferedImage(width, height, type);
|
||||||
@@ -147,8 +157,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateBICustom() {
|
public void testFilterRotateBICustom() {
|
||||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (ImageTypeSpecifier spec : SPECS) {
|
for (ImageTypeSpecifier spec : SPECS) {
|
||||||
BufferedImage image = spec.createBufferedImage(width, height);
|
BufferedImage image = spec.createBufferedImage(width, height);
|
||||||
@@ -197,8 +207,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateRasterStandard() {
|
public void testFilterRotateRasterStandard() {
|
||||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (Integer type : TYPES) {
|
for (Integer type : TYPES) {
|
||||||
Raster raster = new BufferedImage(width, height, type).getRaster();
|
Raster raster = new BufferedImage(width, height, type).getRaster();
|
||||||
@@ -221,8 +231,6 @@ public class AffineTransformOpTest {
|
|||||||
fail("No result!");
|
fail("No result!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
|
|
||||||
System.err.println("type: " + type);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,8 +248,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateRasterCustom() {
|
public void testFilterRotateRasterCustom() {
|
||||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (ImageTypeSpecifier spec : SPECS) {
|
for (ImageTypeSpecifier spec : SPECS) {
|
||||||
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
||||||
@@ -264,8 +272,6 @@ public class AffineTransformOpTest {
|
|||||||
fail("No result!");
|
fail("No result!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
|
|
||||||
System.err.println("spec: " + spec);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import java.io.FilenameFilter;
|
|||||||
* @see WildcardStringParser
|
* @see WildcardStringParser
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class FilenameMaskFilter implements FilenameFilter {
|
public class FilenameMaskFilter implements FilenameFilter {
|
||||||
|
|
||||||
// TODO: Rewrite to use regexp, or create new class
|
// TODO: Rewrite to use regexp, or create new class
|
||||||
|
|||||||
@@ -442,6 +442,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
* @see java.io.BufferedReader#readLine()
|
* @see java.io.BufferedReader#readLine()
|
||||||
* @see java.io.DataInputStream#readLine()
|
* @see java.io.DataInputStream#readLine()
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String readLine() throws IOException {
|
public String readLine() throws IOException {
|
||||||
DataInputStream ds = new DataInputStream(in);
|
DataInputStream ds = new DataInputStream(in);
|
||||||
return ds.readLine();
|
return ds.readLine();
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.xml;
|
package com.twelvemonkeys.xml;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
import org.w3c.dom.DOMConfiguration;
|
import org.w3c.dom.DOMConfiguration;
|
||||||
import org.w3c.dom.DOMImplementationList;
|
import org.w3c.dom.DOMImplementationList;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
@@ -38,9 +41,6 @@ import org.w3c.dom.ls.DOMImplementationLS;
|
|||||||
import org.w3c.dom.ls.LSOutput;
|
import org.w3c.dom.ls.LSOutput;
|
||||||
import org.w3c.dom.ls.LSSerializer;
|
import org.w3c.dom.ls.LSSerializer;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code DOMImplementationLS} backed implementation.
|
* {@code DOMImplementationLS} backed implementation.
|
||||||
*
|
*
|
||||||
@@ -88,17 +88,6 @@ public final class DOMSerializer {
|
|||||||
output.setCharacterStream(pStream);
|
output.setCharacterStream(pStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: Is it useful?
|
|
||||||
public void setNewLine(final String pNewLine) {
|
|
||||||
serializer.setNewLine(pNewLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNewLine() {
|
|
||||||
return serializer.getNewLine();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies wether the serializer should use indentation and optimize for
|
* Specifies wether the serializer should use indentation and optimize for
|
||||||
* readability.
|
* readability.
|
||||||
@@ -169,13 +158,7 @@ public final class DOMSerializer {
|
|||||||
try {
|
try {
|
||||||
return DOMImplementationRegistry.newInstance();
|
return DOMImplementationRegistry.newInstance();
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException e) {
|
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
catch (InstantiationException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
catch (IllegalAccessException e) {
|
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,23 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.xml;
|
package com.twelvemonkeys.xml;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import java.io.ByteArrayInputStream;
|
||||||
import org.w3c.dom.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
import org.xml.sax.SAXException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import java.io.*;
|
|
||||||
import java.nio.charset.Charset;
|
import org.w3c.dom.*;
|
||||||
import java.util.Date;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XMLSerializer
|
* XMLSerializer
|
||||||
@@ -290,7 +297,7 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
||||||
pBuilder.append(pString.substring(pStart, pEnd));
|
pBuilder.append(pString, pStart, pEnd);
|
||||||
pBuilder.append(pEntity);
|
pBuilder.append(pEntity);
|
||||||
return pEnd + 1;
|
return pEnd + 1;
|
||||||
}
|
}
|
||||||
@@ -527,8 +534,7 @@ public class XMLSerializer {
|
|||||||
builder = factory.newDocumentBuilder();
|
builder = factory.newDocumentBuilder();
|
||||||
}
|
}
|
||||||
catch (ParserConfigurationException e) {
|
catch (ParserConfigurationException e) {
|
||||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
throw new IOException(e);
|
||||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMImplementation dom = builder.getDOMImplementation();
|
DOMImplementation dom = builder.getDOMImplementation();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|||||||
@@ -770,6 +770,7 @@ public final class StringUtil {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*public*/
|
/*public*/
|
||||||
|
@Deprecated
|
||||||
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
@@ -1464,6 +1465,7 @@ public final class StringUtil {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*public*/
|
/*public*/
|
||||||
|
@Deprecated
|
||||||
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
||||||
StringBuilder filteredString = new StringBuilder();
|
StringBuilder filteredString = new StringBuilder();
|
||||||
boolean insideDemarcatedArea = false;
|
boolean insideDemarcatedArea = false;
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ public class Time {
|
|||||||
* @see #parseTime(String)
|
* @see #parseTime(String)
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String toString(String pFormatStr) {
|
public String toString(String pFormatStr) {
|
||||||
TimeFormat tf = new TimeFormat(pFormatStr);
|
TimeFormat tf = new TimeFormat(pFormatStr);
|
||||||
|
|
||||||
@@ -174,6 +175,7 @@ public class Time {
|
|||||||
* @see #toString(String)
|
* @see #toString(String)
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static Time parseTime(String pStr) {
|
public static Time parseTime(String pStr) {
|
||||||
TimeFormat tf = TimeFormat.getInstance();
|
TimeFormat tf = TimeFormat.getInstance();
|
||||||
|
|
||||||
|
|||||||
+1
@@ -111,6 +111,7 @@ import java.io.PrintStream;
|
|||||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||||
* @deprecated Will probably be removed in the near future
|
* @deprecated Will probably be removed in the near future
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class WildcardStringParser {
|
public class WildcardStringParser {
|
||||||
// TODO: Get rid of this class
|
// TODO: Get rid of this class
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||||
<artifactId>contrib</artifactId>
|
<artifactId>contrib</artifactId>
|
||||||
|
|||||||
@@ -31,8 +31,9 @@
|
|||||||
package com.twelvemonkeys.contrib.tiff;
|
package com.twelvemonkeys.contrib.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
||||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat;
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@@ -154,7 +155,7 @@ public class TIFFUtilitiesTest {
|
|||||||
reader.setInput(checkTest1);
|
reader.setInput(checkTest1);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Node metaData = reader.getImageMetadata(i)
|
Node metaData = reader.getImageMetadata(i)
|
||||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||||
}
|
}
|
||||||
@@ -171,7 +172,7 @@ public class TIFFUtilitiesTest {
|
|||||||
reader.setInput(checkTest2);
|
reader.setInput(checkTest2);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Node metaData = reader.getImageMetadata(i)
|
Node metaData = reader.getImageMetadata(i)
|
||||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
Assert.assertEquals(orientation, i == 1
|
Assert.assertEquals(orientation, i == 1
|
||||||
? TIFFExtension.ORIENTATION_BOTRIGHT
|
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-batik</artifactId>
|
<artifactId>imageio-batik</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||||
@@ -68,13 +68,6 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.xmlgraphics</groupId>
|
|
||||||
<artifactId>xmlgraphics-commons</artifactId>
|
|
||||||
<version>2.2</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.xmlgraphics</groupId>
|
<groupId>org.apache.xmlgraphics</groupId>
|
||||||
<artifactId>batik-anim</artifactId>
|
<artifactId>batik-anim</artifactId>
|
||||||
@@ -98,7 +91,7 @@
|
|||||||
<!--
|
<!--
|
||||||
There seems to be some weirdness in the
|
There seems to be some weirdness in the
|
||||||
Batik/FOP poms (Batik depends on FOP 0.20-5) that screws things up,
|
Batik/FOP poms (Batik depends on FOP 0.20-5) that screws things up,
|
||||||
making everything end up depending on Batik 1.5, not 1.6
|
making everything end up depending on Batik 1.5, not the specified version
|
||||||
-->
|
-->
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
|
|||||||
+36
-34
@@ -30,37 +30,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.svg;
|
package com.twelvemonkeys.imageio.plugins.svg;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
|
||||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
|
||||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
|
||||||
import org.apache.batik.bridge.*;
|
|
||||||
import org.apache.batik.css.parser.CSSLexicalUnit;
|
|
||||||
import org.apache.batik.dom.util.DOMUtilities;
|
|
||||||
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
|
||||||
import org.apache.batik.gvt.CanvasGraphicsNode;
|
|
||||||
import org.apache.batik.gvt.GraphicsNode;
|
|
||||||
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
|
|
||||||
import org.apache.batik.gvt.renderer.ImageRenderer;
|
|
||||||
import org.apache.batik.gvt.renderer.ImageRendererFactory;
|
|
||||||
import org.apache.batik.transcoder.*;
|
|
||||||
import org.apache.batik.transcoder.image.ImageTranscoder;
|
|
||||||
import org.apache.batik.util.ParsedURL;
|
|
||||||
import org.apache.batik.util.SVGConstants;
|
|
||||||
import org.apache.batik.xml.LexicalUnits;
|
|
||||||
import org.w3c.dom.DOMImplementation;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.svg.SVGSVGElement;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.ImageReadParam;
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Dimension2D;
|
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -70,6 +41,38 @@ import java.util.Collections;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
|
||||||
|
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||||
|
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||||
|
import org.apache.batik.bridge.*;
|
||||||
|
import org.apache.batik.dom.util.DOMUtilities;
|
||||||
|
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
||||||
|
import org.apache.batik.gvt.CanvasGraphicsNode;
|
||||||
|
import org.apache.batik.gvt.GraphicsNode;
|
||||||
|
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
|
||||||
|
import org.apache.batik.gvt.renderer.ImageRenderer;
|
||||||
|
import org.apache.batik.gvt.renderer.ImageRendererFactory;
|
||||||
|
import org.apache.batik.transcoder.SVGAbstractTranscoder;
|
||||||
|
import org.apache.batik.transcoder.TranscoderException;
|
||||||
|
import org.apache.batik.transcoder.TranscoderInput;
|
||||||
|
import org.apache.batik.transcoder.TranscoderOutput;
|
||||||
|
import org.apache.batik.transcoder.TranscodingHints;
|
||||||
|
import org.apache.batik.transcoder.image.ImageTranscoder;
|
||||||
|
import org.apache.batik.util.ParsedURL;
|
||||||
|
import org.apache.batik.util.SVGConstants;
|
||||||
|
import org.w3c.dom.DOMImplementation;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.svg.SVGSVGElement;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image reader for SVG document fragments.
|
* Image reader for SVG document fragments.
|
||||||
*
|
*
|
||||||
@@ -132,6 +135,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
// Set ImageReadParams as hints
|
// Set ImageReadParams as hints
|
||||||
// Note: The cast to Map invokes a different method that preserves
|
// Note: The cast to Map invokes a different method that preserves
|
||||||
// unset defaults, DO NOT REMOVE!
|
// unset defaults, DO NOT REMOVE!
|
||||||
|
//noinspection rawtypes
|
||||||
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +264,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
||||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +293,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is cheating... We don't fully transcode after all
|
// This is cheating... We don't fully transcode after all
|
||||||
protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException {
|
protected void transcode(Document document, final String uri, final TranscoderOutput output) {
|
||||||
// Sets up root, curTxf & curAoi
|
// Sets up root, curTxf & curAoi
|
||||||
// ----
|
// ----
|
||||||
if (document != null) {
|
if (document != null) {
|
||||||
@@ -584,9 +588,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
TranscoderException exception = new TranscoderException(ex.getMessage());
|
throw new TranscoderException(ex.getMessage(), ex);
|
||||||
exception.initCause(ex);
|
|
||||||
throw exception;
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
|
|||||||
+2
-4
@@ -54,8 +54,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,7 +223,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
assertEquals(500, image.getHeight());
|
assertEquals(500, image.getHeight());
|
||||||
|
|
||||||
// CSS and embedded resources all go!
|
// CSS and embedded resources all go!
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
@@ -266,7 +264,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
assertEquals(500, image.getHeight());
|
assertEquals(500, image.getHeight());
|
||||||
|
|
||||||
// No more warnings now that the base URI is set
|
// No more warnings now that the base URI is set
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-bmp</artifactId>
|
<artifactId>imageio-bmp</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||||
|
|||||||
+34
-45
@@ -30,6 +30,22 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
|
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||||
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
|
import javax.imageio.*;
|
||||||
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
@@ -40,27 +56,6 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageReadParam;
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.event.IIOReadUpdateListener;
|
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
|
||||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
|
||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageReader for Microsoft Windows Bitmap (BMP) format.
|
* ImageReader for Microsoft Windows Bitmap (BMP) format.
|
||||||
*
|
*
|
||||||
@@ -482,8 +477,12 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataByte.length);
|
input.skipBytes(rowDataByte.length);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -498,19 +497,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
|
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -530,19 +527,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataInt.length * 4);
|
input.skipBytes(rowDataInt.length * 4);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -557,13 +552,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Candidate util method
|
// TODO: Candidate util method
|
||||||
|
|||||||
+4
-4
@@ -32,8 +32,8 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assume.assumeNoException;
|
import static org.junit.Assume.assumeNoException;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
@@ -295,7 +295,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(reader, 0);
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(reader, 0);
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-clippath</artifactId>
|
<artifactId>imageio-clippath</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||||
|
|||||||
+1
@@ -39,6 +39,7 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class AdobePathBuilder {
|
public final class AdobePathBuilder {
|
||||||
|
|
||||||
private final AdobePathReader delegate;
|
private final AdobePathReader delegate;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||||
|
|||||||
+563
@@ -0,0 +1,563 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.color;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
import com.twelvemonkeys.lang.Platform;
|
||||||
|
import com.twelvemonkeys.lang.SystemUtil;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class for working with ICC color profiles.
|
||||||
|
* <p>
|
||||||
|
* Standard ICC color profiles are read from system-specific locations
|
||||||
|
* for known operating systems.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Color profiles may be configured by placing a property-file
|
||||||
|
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||||
|
* on the classpath, specifying the full path to the profiles.
|
||||||
|
* ICC color profiles are probably already present on your system, or
|
||||||
|
* can be downloaded from
|
||||||
|
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||||
|
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
|
||||||
|
* * </p>
|
||||||
|
* <p>
|
||||||
|
* Example property file:
|
||||||
|
* </p>
|
||||||
|
* <pre>
|
||||||
|
* # icc_profiles.properties
|
||||||
|
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
|
||||||
|
* GENERIC_CMYK=/path/to/Generic CMYK.icc
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class ColorProfiles {
|
||||||
|
/**
|
||||||
|
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
|
||||||
|
*/
|
||||||
|
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||||
|
|
||||||
|
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
|
||||||
|
static final int ICC_PROFILE_HEADER_SIZE = 128;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// In case we didn't activate through SPI already
|
||||||
|
ProfileDeferralActivator.activateProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ColorProfiles() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||||
|
// Get *entire profile data*... :-/
|
||||||
|
return getProfileHeaderWithProfileId(profile.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] getProfileHeaderWithProfileId(byte[] data) {
|
||||||
|
// ICC profile header is the first 128 bytes
|
||||||
|
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
|
||||||
|
|
||||||
|
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
|
||||||
|
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||||
|
// - KCMS keeps the values in the file...
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||||
|
// + Clear out rendering intent, as this may be updated by application
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||||
|
|
||||||
|
// Clear out any existing MD5, as it is no longer correct
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||||
|
|
||||||
|
// Generate new MD5 and store in header
|
||||||
|
byte[] md5 = computeMD5(header, data);
|
||||||
|
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] computeMD5(byte[] header, byte[] data) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||||
|
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
|
||||||
|
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
|
||||||
|
return digest.digest();
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||||
|
*
|
||||||
|
* @param profile the ICC profile to test. May not be {@code null}.
|
||||||
|
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @see java.awt.color.ColorSpace#CS_sRGB
|
||||||
|
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||||
|
*/
|
||||||
|
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is equal to the default GRAY profile.
|
||||||
|
*
|
||||||
|
* @param profile the ICC profile to test. May not be {@code null}.
|
||||||
|
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @see java.awt.color.ColorSpace#CS_GRAY
|
||||||
|
*/
|
||||||
|
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||||
|
* <p>
|
||||||
|
* <em>
|
||||||
|
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||||
|
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||||
|
* </em>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param profile the ICC color profile. May not be {@code null}.
|
||||||
|
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
*/
|
||||||
|
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||||
|
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||||
|
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||||
|
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||||
|
// (that is 1 single bit of difference right there.. ;-)
|
||||||
|
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||||
|
|
||||||
|
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||||
|
// except the getRenderingIntent method is package protected in java.awt.color
|
||||||
|
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||||
|
|
||||||
|
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||||
|
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is valid.
|
||||||
|
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||||
|
* <p>
|
||||||
|
* <em>
|
||||||
|
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||||
|
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||||
|
* </em>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param profile the ICC color profile. May not be {@code null}.
|
||||||
|
* @return {@code profile} if valid.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||||
|
*/
|
||||||
|
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||||
|
// Fix profile before validation
|
||||||
|
profileCleaner.fixProfile(profile);
|
||||||
|
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given input stream, as-is, with no validation.
|
||||||
|
*
|
||||||
|
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
|
||||||
|
*
|
||||||
|
* @param input the input stream to read from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the stream.
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the stream does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(InputStream)
|
||||||
|
* @see #readProfile(InputStream)
|
||||||
|
*/
|
||||||
|
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
return ICC_Profile.getInstance(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given input stream, with extra validation.
|
||||||
|
*
|
||||||
|
* If a matching profile already exists in cache, the cached instance is returned.
|
||||||
|
*
|
||||||
|
* @param input the input stream to read from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the stream.
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the stream does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(InputStream)
|
||||||
|
*/
|
||||||
|
public static ICC_Profile readProfile(final InputStream input) throws IOException {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
DataInputStream dataInput = new DataInputStream(input);
|
||||||
|
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
|
||||||
|
try {
|
||||||
|
dataInput.readFully(header);
|
||||||
|
|
||||||
|
int size = validateHeaderAndGetSize(header);
|
||||||
|
byte[] data = Arrays.copyOf(header, size);
|
||||||
|
dataInput.readFully(data, header.length, size - header.length);
|
||||||
|
|
||||||
|
return createProfile(data);
|
||||||
|
}
|
||||||
|
catch (EOFException e) {
|
||||||
|
throw new IllegalArgumentException("Truncated ICC Profile data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ICC Profile from the given byte array, as-is, with no validation.
|
||||||
|
*
|
||||||
|
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
|
||||||
|
* except that extraneous bytes at the end of the array is ignored.
|
||||||
|
*
|
||||||
|
* @param input the byte array to create a profile from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object created from the byte array
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the byte array does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(byte[])
|
||||||
|
* @see #createProfile(byte[])
|
||||||
|
*/
|
||||||
|
public static ICC_Profile createProfileRaw(final byte[] input) {
|
||||||
|
int size = validateHeaderAndGetSize(input);
|
||||||
|
|
||||||
|
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
|
||||||
|
// does not discard extra bytes at the end. We'll chop them off here for convenience
|
||||||
|
return ICC_Profile.getInstance(limit(input, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given byte array, with extra validation.
|
||||||
|
* Extraneous bytes at the end of the array are ignored.
|
||||||
|
*
|
||||||
|
* If a matching profile already exists in cache, the cached instance is returned.
|
||||||
|
*
|
||||||
|
* @param input the byte array to create a profile from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object created from the byte array
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the byte array does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(byte[])
|
||||||
|
*/
|
||||||
|
public static ICC_Profile createProfile(final byte[] input) {
|
||||||
|
int size = validateAndGetSize(input);
|
||||||
|
|
||||||
|
// Look up in cache before returning, these are already validated
|
||||||
|
byte[] profileHeader = getProfileHeaderWithProfileId(input);
|
||||||
|
ICC_Profile internal = getInternalProfile(profileHeader);
|
||||||
|
if (internal != null) {
|
||||||
|
return internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached.getProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
|
||||||
|
|
||||||
|
// We'll validate & cache by creating a color space and returning its profile...
|
||||||
|
// TODO: Rewrite with separate cache for profiles...
|
||||||
|
return ColorSpaces.createColorSpace(profile).getProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] limit(byte[] input, int size) {
|
||||||
|
return input.length == size ? input : Arrays.copyOf(input, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateAndGetSize(byte[] input) {
|
||||||
|
int size = validateHeaderAndGetSize(input);
|
||||||
|
|
||||||
|
if (size < 0 || size > input.length) {
|
||||||
|
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateHeaderAndGetSize(byte[] input) {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
|
||||||
|
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = intBigEndian(input, ICC_Profile.icHdrSize);
|
||||||
|
|
||||||
|
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
|
||||||
|
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
|
||||||
|
int profileCSType = getCsType(profileHeader);
|
||||||
|
|
||||||
|
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int intBigEndian(byte[] data, int index) {
|
||||||
|
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getCsType(byte[] profileHeader) {
|
||||||
|
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
|
||||||
|
|
||||||
|
switch (csSig) {
|
||||||
|
case ICC_Profile.icSigXYZData:
|
||||||
|
return ColorSpace.TYPE_XYZ;
|
||||||
|
case ICC_Profile.icSigLabData:
|
||||||
|
return ColorSpace.TYPE_Lab;
|
||||||
|
case ICC_Profile.icSigLuvData:
|
||||||
|
return ColorSpace.TYPE_Luv;
|
||||||
|
case ICC_Profile.icSigYCbCrData:
|
||||||
|
return ColorSpace.TYPE_YCbCr;
|
||||||
|
case ICC_Profile.icSigYxyData:
|
||||||
|
return ColorSpace.TYPE_Yxy;
|
||||||
|
case ICC_Profile.icSigRgbData:
|
||||||
|
return ColorSpace.TYPE_RGB;
|
||||||
|
case ICC_Profile.icSigGrayData:
|
||||||
|
return ColorSpace.TYPE_GRAY;
|
||||||
|
case ICC_Profile.icSigHsvData:
|
||||||
|
return ColorSpace.TYPE_HSV;
|
||||||
|
case ICC_Profile.icSigHlsData:
|
||||||
|
return ColorSpace.TYPE_HLS;
|
||||||
|
case ICC_Profile.icSigCmykData:
|
||||||
|
return ColorSpace.TYPE_CMYK;
|
||||||
|
// Note: There is no TYPE_* 10...
|
||||||
|
case ICC_Profile.icSigCmyData:
|
||||||
|
return ColorSpace.TYPE_CMY;
|
||||||
|
case ICC_Profile.icSigSpace2CLR:
|
||||||
|
return ColorSpace.TYPE_2CLR;
|
||||||
|
case ICC_Profile.icSigSpace3CLR:
|
||||||
|
return ColorSpace.TYPE_3CLR;
|
||||||
|
case ICC_Profile.icSigSpace4CLR:
|
||||||
|
return ColorSpace.TYPE_4CLR;
|
||||||
|
case ICC_Profile.icSigSpace5CLR:
|
||||||
|
return ColorSpace.TYPE_5CLR;
|
||||||
|
case ICC_Profile.icSigSpace6CLR:
|
||||||
|
return ColorSpace.TYPE_6CLR;
|
||||||
|
case ICC_Profile.icSigSpace7CLR:
|
||||||
|
return ColorSpace.TYPE_7CLR;
|
||||||
|
case ICC_Profile.icSigSpace8CLR:
|
||||||
|
return ColorSpace.TYPE_8CLR;
|
||||||
|
case ICC_Profile.icSigSpace9CLR:
|
||||||
|
return ColorSpace.TYPE_9CLR;
|
||||||
|
case ICC_Profile.icSigSpaceACLR:
|
||||||
|
return ColorSpace.TYPE_ACLR;
|
||||||
|
case ICC_Profile.icSigSpaceBCLR:
|
||||||
|
return ColorSpace.TYPE_BCLR;
|
||||||
|
case ICC_Profile.icSigSpaceCCLR:
|
||||||
|
return ColorSpace.TYPE_CCLR;
|
||||||
|
case ICC_Profile.icSigSpaceDCLR:
|
||||||
|
return ColorSpace.TYPE_DCLR;
|
||||||
|
case ICC_Profile.icSigSpaceECLR:
|
||||||
|
return ColorSpace.TYPE_ECLR;
|
||||||
|
case ICC_Profile.icSigSpaceFCLR:
|
||||||
|
return ColorSpace.TYPE_FCLR;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
|
||||||
|
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||||
|
|
||||||
|
if (stream != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ICC_Profile.getInstance(stream);
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||||
|
if (profilePath != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Loading profile from: " + profilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ICC_Profile.getInstance(profilePath);
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fixProfile(ICC_Profile profile) {
|
||||||
|
profileCleaner.fixProfile(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean validationAltersProfileHeader() {
|
||||||
|
return profileCleaner.validationAltersProfileHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
|
||||||
|
static class sRGB {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CIEXYZ {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PYCC {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GRAY {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LINEAR_RGB {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Profiles {
|
||||||
|
// TODO: Honour java.iccprofile.path property?
|
||||||
|
private static final Properties PROFILES = loadProfiles();
|
||||||
|
|
||||||
|
private static Properties loadProfiles() {
|
||||||
|
Properties systemDefaults;
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||||
|
System.err.printf(
|
||||||
|
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||||
|
ignore.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
systemDefaults = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create map with defaults and add user overrides if any
|
||||||
|
Properties profiles = new Properties(systemDefaults);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Properties userOverrides = SystemUtil.loadProperties(
|
||||||
|
ColorSpaces.class,
|
||||||
|
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||||
|
);
|
||||||
|
profiles.putAll(userOverrides);
|
||||||
|
}
|
||||||
|
catch (SecurityException | IOException ignore) {
|
||||||
|
// Most likely, this file won't be there
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("User ICC profiles: " + profiles);
|
||||||
|
System.out.println("System ICC profiles : " + systemDefaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getPath(final String profileName) {
|
||||||
|
return PROFILES.getProperty(profileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
-243
@@ -30,23 +30,17 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.color;
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
|
||||||
import com.twelvemonkeys.lang.Platform;
|
|
||||||
import com.twelvemonkeys.lang.SystemUtil;
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import com.twelvemonkeys.util.LRUHashMap;
|
import com.twelvemonkeys.util.LRUHashMap;
|
||||||
|
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
|
||||||
|
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class for working with ICC color profiles and color spaces.
|
* A helper class for working with ICC color profiles and color spaces.
|
||||||
@@ -83,9 +77,6 @@ public final class ColorSpaces {
|
|||||||
|
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||||
|
|
||||||
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
|
|
||||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
|
||||||
|
|
||||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||||
|
|
||||||
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
||||||
@@ -96,24 +87,17 @@ public final class ColorSpaces {
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static final int CS_GENERIC_CMYK = 5001;
|
public static final int CS_GENERIC_CMYK = 5001;
|
||||||
|
|
||||||
|
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
|
||||||
// Weak references to hold the color spaces while cached
|
// Weak references to hold the color spaces while cached
|
||||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||||
|
|
||||||
// Cache for the latest used color spaces
|
// Cache for the latest used color spaces
|
||||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
|
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
// In case we didn't activate through SPI already
|
||||||
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
|
ProfileDeferralActivator.activateProfiles();
|
||||||
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
|
|
||||||
}
|
|
||||||
catch (Throwable disasters) {
|
|
||||||
System.err.println("ICC Color Profile not properly activated due to the exception below.");
|
|
||||||
System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
|
|
||||||
|
|
||||||
disasters.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ColorSpaces() {}
|
private ColorSpaces() {}
|
||||||
@@ -134,7 +118,7 @@ public final class ColorSpaces {
|
|||||||
Validate.notNull(profile, "profile");
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
// Fix profile before lookup/create
|
// Fix profile before lookup/create
|
||||||
profileCleaner.fixProfile(profile);
|
fixProfile(profile);
|
||||||
|
|
||||||
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
||||||
|
|
||||||
@@ -146,53 +130,20 @@ public final class ColorSpaces {
|
|||||||
return getCachedOrCreateCS(profile, profileHeader);
|
return getCachedOrCreateCS(profile, profileHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||||
// Get *entire profile data*... :-/
|
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||||
byte[] data = profile.getData();
|
|
||||||
|
|
||||||
// Clear out preferred CMM, platform & creator, as these does not affect the profile in any way
|
|
||||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
|
||||||
// - KCMS keeps the values in the file...
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
|
||||||
// + Clear out rendering intent, as this may be updated by application
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
|
||||||
|
|
||||||
// Clear out any existing MD5, as it is no longer correct
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
|
||||||
|
|
||||||
// Generate new MD5 and store in header
|
|
||||||
byte[] md5 = computeMD5(data);
|
|
||||||
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
|
|
||||||
|
|
||||||
// ICC profile header is the first 128 bytes
|
|
||||||
return Arrays.copyOf(data, 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] computeMD5(byte[] data) {
|
|
||||||
try {
|
|
||||||
return MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
}
|
|
||||||
catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
|
||||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
|
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
|
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
|
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
|
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
|
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,26 +154,36 @@ public final class ColorSpaces {
|
|||||||
Key key = new Key(profileHeader);
|
Key key = new Key(profileHeader);
|
||||||
|
|
||||||
synchronized (cache) {
|
synchronized (cache) {
|
||||||
ICC_ColorSpace cs = cache.get(key);
|
ICC_ColorSpace cs = getCachedCS(key);
|
||||||
|
|
||||||
if (cs == null) {
|
if (cs == null) {
|
||||||
cs = new ICC_ColorSpace(profile);
|
cs = new ICC_ColorSpace(profile);
|
||||||
|
|
||||||
validateColorSpace(cs);
|
validateColorSpace(cs);
|
||||||
|
|
||||||
// On LCMS, validation *alters* the profile header, need to re-generate key
|
|
||||||
key = profileCleaner.validationAltersProfileHeader()
|
|
||||||
? new Key(getProfileHeaderWithProfileId(cs.getProfile()))
|
|
||||||
: key;
|
|
||||||
|
|
||||||
cache.put(key, cs);
|
cache.put(key, cs);
|
||||||
|
|
||||||
|
// On LCMS, validation *alters* the profile header, need to re-generate key
|
||||||
|
if (ColorProfiles.validationAltersProfileHeader()) {
|
||||||
|
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs;
|
return cs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateColorSpace(final ICC_ColorSpace cs) {
|
private static ICC_ColorSpace getCachedCS(Key profileKey) {
|
||||||
|
synchronized (cache) {
|
||||||
|
return cache.get(profileKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
|
||||||
|
return getCachedCS(new Key(profileHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||||
// Validate the color space, to avoid caching bad profiles/color spaces
|
// Validate the color space, to avoid caching bad profiles/color spaces
|
||||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||||
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
||||||
@@ -233,73 +194,27 @@ public final class ColorSpaces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
|
||||||
*
|
|
||||||
* @param profile the ICC profile to test. May not be {@code null}.
|
|
||||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
*
|
|
||||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||||
Validate.notNull(profile, "profile");
|
return ColorProfiles.isCS_sRGB(profile);
|
||||||
|
|
||||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
|
||||||
* <p>
|
|
||||||
* <em>
|
|
||||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
|
||||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
|
||||||
* </em>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param profile the ICC color profile. May not be {@code null}.
|
|
||||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
*/
|
*/
|
||||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
@Deprecated
|
||||||
Validate.notNull(profile, "profile");
|
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||||
|
return ColorProfiles.isCS_GRAY(profile);
|
||||||
// NOTE:
|
|
||||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
|
||||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
|
||||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
|
||||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
|
||||||
// (that is 1 single bit of difference right there.. ;-)
|
|
||||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
|
||||||
|
|
||||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
|
||||||
// except the getRenderingIntent method is package protected in java.awt.color
|
|
||||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
|
||||||
|
|
||||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
|
||||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is valid.
|
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
|
||||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
|
||||||
* <p>
|
|
||||||
* <em>
|
|
||||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
|
||||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
|
||||||
* </em>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param profile the ICC color profile. May not be {@code null}.
|
|
||||||
* @return {@code profile} if valid.
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||||
// Fix profile before validation
|
return ColorProfiles.validateProfile(profile);
|
||||||
profileCleaner.fixProfile(profile);
|
|
||||||
validateColorSpace(new ICC_ColorSpace(profile));
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -382,50 +297,6 @@ public final class ColorSpaces {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
|
|
||||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
|
||||||
|
|
||||||
if (stream != null) {
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return ICC_Profile.getInstance(stream);
|
|
||||||
}
|
|
||||||
catch (IOException ignore) {
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
FileUtil.close(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICC_Profile readProfileFromPath(final String profilePath) {
|
|
||||||
if (profilePath != null) {
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("Loading profile from: " + profilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return ICC_Profile.getInstance(profilePath);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Key {
|
private static final class Key {
|
||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
|
|
||||||
@@ -448,78 +319,4 @@ public final class ColorSpaces {
|
|||||||
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init
|
|
||||||
private static class sRGB {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CIEXYZ {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PYCC {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GRAY {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LINEAR_RGB {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Profiles {
|
|
||||||
// TODO: Honour java.iccprofile.path property?
|
|
||||||
private static final Properties PROFILES = loadProfiles();
|
|
||||||
|
|
||||||
private static Properties loadProfiles() {
|
|
||||||
Properties systemDefaults;
|
|
||||||
|
|
||||||
try {
|
|
||||||
systemDefaults = SystemUtil.loadProperties(
|
|
||||||
ColorSpaces.class,
|
|
||||||
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
System.err.printf(
|
|
||||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
|
||||||
ignore.getMessage()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
systemDefaults = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create map with defaults and add user overrides if any
|
|
||||||
Properties profiles = new Properties(systemDefaults);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Properties userOverrides = SystemUtil.loadProperties(
|
|
||||||
ColorSpaces.class,
|
|
||||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
|
||||||
);
|
|
||||||
profiles.putAll(userOverrides);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
// Most likely, this file won't be there
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("User ICC profiles: " + profiles);
|
|
||||||
System.out.println("System ICC profiles : " + systemDefaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return profiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getPath(final String profileName) {
|
|
||||||
return PROFILES.getProperty(profileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.color;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class exists to force early invocation of {@code ProfileDeferralMgr.activateProfiles()},
|
||||||
|
* in an attempt to avoid JDK-6986863 and related bugs in Java < 17.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @see <a href="https://bugs.openjdk.java.net/browse/JDK-6986863">JDK-6986863</a>
|
||||||
|
*/
|
||||||
|
final class ProfileDeferralActivator {
|
||||||
|
|
||||||
|
static {
|
||||||
|
activateProfilesInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void activateProfilesInternal() {
|
||||||
|
try {
|
||||||
|
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863 and friends.
|
||||||
|
// Relies on static initializer in ColorConvertOp to actually invoke ProfileDeferralMgr.activateProfiles()
|
||||||
|
Class.forName("java.awt.image.ColorConvertOp");
|
||||||
|
}
|
||||||
|
catch (Throwable disasters) {
|
||||||
|
System.err.println("ProfileDeferralMgr.activateProfiles() failed. ICC Color Profiles may not work properly, see stack trace below.");
|
||||||
|
System.err.println("For more information, see https://bugs.openjdk.java.net/browse/JDK-6986863");
|
||||||
|
System.err.println("Please upgrade to Java 17 or later where this bug is fixed, or ask your JRE provider to backport the fix.");
|
||||||
|
System.err.println();
|
||||||
|
System.err.println("If you can't update to Java 17, a possible workaround is to add");
|
||||||
|
System.err.println("\tClass.forName(\"java.awt.image.ColorConvertOp\");");
|
||||||
|
System.err.println("*early* in your application startup code, to force profile activation before profiles are accessed.");
|
||||||
|
System.err.println();
|
||||||
|
|
||||||
|
disasters.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void activateProfiles() {
|
||||||
|
// This method exists for other classes in the package to
|
||||||
|
// ensure this class' static initializer is run.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not a service provider, but exploits the SPI mechanism as a hook to force early profile activation.
|
||||||
|
*/
|
||||||
|
public static final class Spi extends ImageInputStreamSpi {
|
||||||
|
@Override public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||||
|
activateProfiles();
|
||||||
|
|
||||||
|
deregisterProvider(registry, this, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String getDescription(Locale locale) {
|
||||||
|
return getClass().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
-31
@@ -45,36 +45,82 @@ public final class YCbCrConverter {
|
|||||||
private final static int CENTERJSAMPLE = 128;
|
private final static int CENTERJSAMPLE = 128;
|
||||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||||
|
|
||||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
private final static class JPEG {
|
||||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes tables for YCC->RGB color space conversion.
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
*/
|
*/
|
||||||
private static void buildYCCtoRGBtable() {
|
private static void buildYCCtoRGBtable() {
|
||||||
if (ColorSpaces.DEBUG) {
|
if (ColorSpaces.DEBUG) {
|
||||||
System.err.println("Building YCC conversion table");
|
System.err.println("Building JPEG YCbCr conversion table");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||||
|
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||||
|
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||||
|
// Cr=>R value is nearest int to 1.40200 * x
|
||||||
|
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cb=>B value is nearest int to 1.77200 * x
|
||||||
|
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cr=>G value is scaled-up -0.71414 * x
|
||||||
|
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||||
|
// Cb=>G value is scaled-up -0.34414 * x
|
||||||
|
// We also add in ONE_HALF so that need not do it in inner loop
|
||||||
|
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
static {
|
||||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
buildYCCtoRGBtable();
|
||||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
|
||||||
// Cr=>R value is nearest int to 1.40200 * x
|
|
||||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
|
||||||
// Cb=>B value is nearest int to 1.77200 * x
|
|
||||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
|
||||||
// Cr=>G value is scaled-up -0.71414 * x
|
|
||||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
|
||||||
// Cb=>G value is scaled-up -0.34414 * x
|
|
||||||
// We also add in ONE_HALF so that need not do it in inner loop
|
|
||||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
private final static class ITU_R_601 {
|
||||||
buildYCCtoRGBtable();
|
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Y_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
|
*/
|
||||||
|
private static void buildYCCtoRGBtable() {
|
||||||
|
if (ColorSpaces.DEBUG) {
|
||||||
|
System.err.println("Building ITU-R REC.601 YCbCr conversion table");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||||
|
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||||
|
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||||
|
|
||||||
|
// Y'CbCr to RGB conversion, using values from BT.601 specification:
|
||||||
|
// R = 1.16438 * (Y'-16) + 1.59603 * (Cr-128)
|
||||||
|
// G = 1.16438 * (Y'-16) - 0.39176 * (Cb-128) - 0.81297 * (Cr-128)
|
||||||
|
// B = 1.16438 * (Y'-16) + 2.01723 * (Cb-128)
|
||||||
|
|
||||||
|
// Cr=>R value is nearest int to 1.59603 * x
|
||||||
|
Cr_R_LUT[i] = ((int) (1.59603 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cb=>B value is nearest int to 2.01723 * x
|
||||||
|
Cb_B_LUT[i] = ((int) (2.01723 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cr=>G value is scaled-up -0.81297 * x
|
||||||
|
Cr_G_LUT[i] = -(int) (0.81297 * (1 << SCALEBITS) + 0.5) * x;
|
||||||
|
// Cb=>G value is scaled-up -0.39176 * x
|
||||||
|
// We also add in ONE_HALF so that need not do it in inner loop
|
||||||
|
Cb_G_LUT[i] = -(int) ((0.39176) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||||
|
|
||||||
|
// Y`=>RGB
|
||||||
|
Y_LUT[i] = ((int) (1.16438 * (1 << SCALEBITS) + 0.5) * (i - 16) + ONE_HALF) >> SCALEBITS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
buildYCCtoRGBtable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
||||||
@@ -108,17 +154,27 @@ public final class YCbCrConverter {
|
|||||||
rgb[offset + 1] = clamp(green);
|
rgb[offset + 1] = clamp(green);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
public static void convertJPEGYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||||
int y = yCbCr[offset] & 0xff;
|
int y = yCbCr[offset ] & 0xff;
|
||||||
int cr = yCbCr[offset + 2] & 0xff;
|
|
||||||
int cb = yCbCr[offset + 1] & 0xff;
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
|
|
||||||
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
|
rgb[offset ] = clamp(y + JPEG.Cr_R_LUT[cr]);
|
||||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
rgb[offset + 1] = clamp(y + (JPEG.Cb_G_LUT[cb] + JPEG.Cr_G_LUT[cr] >> SCALEBITS));
|
||||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
rgb[offset + 2] = clamp(y + JPEG.Cb_B_LUT[cb]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte clamp(int val) {
|
public static void convertRec601YCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||||
|
int y = yCbCr[offset ] & 0xff;
|
||||||
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
|
|
||||||
|
rgb[offset ] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cr_R_LUT[cr]);
|
||||||
|
rgb[offset + 1] = clamp(ITU_R_601.Y_LUT[y] + (ITU_R_601.Cr_G_LUT[cr] + ITU_R_601.Cb_G_LUT[cb] >> SCALEBITS));
|
||||||
|
rgb[offset + 2] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cb_B_LUT[cb]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte clamp(final int val) {
|
||||||
return (byte) Math.max(0, Math.min(255, val));
|
return (byte) Math.max(0, Math.min(255, val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,19 +158,18 @@ public final class IIOUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
|
||||||
*
|
*
|
||||||
* @param registry the registry to unregister from.
|
* @param registry the registry to unregister from.
|
||||||
* @param provider the provider to unregister.
|
* @param provider the provider to unregister.
|
||||||
* @param category the category to unregister from.
|
* @param category the category to unregister from.
|
||||||
*/
|
*/
|
||||||
public static <T> void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class<T> category) {
|
public static <T> void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class<T> category) {
|
||||||
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
|
|
||||||
registry.deregisterServiceProvider(category.cast(provider), category);
|
registry.deregisterServiceProvider(category.cast(provider), category);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
|
||||||
*
|
*
|
||||||
* @param registry the registry to lookup from.
|
* @param registry the registry to lookup from.
|
||||||
* @param providerClassName name of the provider class.
|
* @param providerClassName name of the provider class.
|
||||||
|
|||||||
+2
@@ -1,2 +1,4 @@
|
|||||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||||
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
||||||
|
# Use SPI loading as a hook for early profile activation
|
||||||
|
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
||||||
|
|||||||
+247
@@ -0,0 +1,247 @@
|
|||||||
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class ColorProfilesTest {
|
||||||
|
@Test
|
||||||
|
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||||
|
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
ICC_Profile profile = createBrokenProfile(internal);
|
||||||
|
assertNotSame(internal, profile); // Sanity check
|
||||||
|
|
||||||
|
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
|
||||||
|
|
||||||
|
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||||
|
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||||
|
assertTrue(created.isCS_sRGB());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||||
|
byte[] data = internal.getData();
|
||||||
|
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
||||||
|
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
||||||
|
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
||||||
|
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
||||||
|
return ICC_Profile.getInstance(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsOffendingColorProfile() {
|
||||||
|
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||||
|
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_sRGBTrue() {
|
||||||
|
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_sRGBFalse() {
|
||||||
|
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||||
|
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||||
|
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||||
|
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testIsCS_sRGBNull() {
|
||||||
|
ColorProfiles.isCS_sRGB(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_GRAYTrue() {
|
||||||
|
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_GRAYFalse() {
|
||||||
|
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||||
|
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||||
|
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||||
|
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testIsCS_GRAYNull() {
|
||||||
|
ColorProfiles.isCS_GRAY(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileNull() {
|
||||||
|
ColorProfiles.createProfile(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileNull() throws IOException {
|
||||||
|
ColorProfiles.readProfile(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileRawNull() {
|
||||||
|
ColorProfiles.createProfileRaw(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileRawNull() throws IOException {
|
||||||
|
ColorProfiles.readProfileRaw(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateProfileRaw() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
|
||||||
|
assertArrayEquals(data, profileRaw.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadProfileRaw() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
assertArrayEquals(data, profileRaw.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileRawBadData() {
|
||||||
|
ColorProfiles.createProfileRaw(new byte[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileRawBadData() throws IOException {
|
||||||
|
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
|
||||||
|
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
|
||||||
|
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileBadData() {
|
||||||
|
ColorProfiles.createProfile(new byte[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileBadData() throws IOException {
|
||||||
|
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileRawTruncated() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileRawTruncated() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileTruncated() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileTruncated() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileRawTruncatedHeader() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileRawTruncatedHeader() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateProfileTruncatedHeader() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testReadProfileTruncatedHeader() throws IOException {
|
||||||
|
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||||
|
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateProfileBytesSame() throws IOException {
|
||||||
|
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
|
||||||
|
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||||
|
|
||||||
|
assertEquals(profile1, profile2);
|
||||||
|
assertSame(profile1, profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadProfileInputStreamSame() throws IOException {
|
||||||
|
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
|
||||||
|
assertEquals(profile1, profile2);
|
||||||
|
assertSame(profile1, profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadProfileDifferent() throws IOException {
|
||||||
|
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
|
||||||
|
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||||
|
|
||||||
|
assertNotSame(profile1, profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateProfileBytesSameAsCached() throws IOException {
|
||||||
|
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
|
||||||
|
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||||
|
|
||||||
|
assertEquals(cs1.getProfile(), profile2);
|
||||||
|
assertSame(cs1.getProfile(), profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadProfileInputStreamSameAsCached() throws IOException {
|
||||||
|
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
|
||||||
|
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
|
|
||||||
|
assertEquals(cs1.getProfile(), profile2);
|
||||||
|
assertSame(cs1.getProfile(), profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateProfileBytesSameAsInternal() {
|
||||||
|
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
|
||||||
|
|
||||||
|
assertEquals(profile1, profile2);
|
||||||
|
assertSame(profile1, profile2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadProfileInputStreamSameAsInternal() throws IOException {
|
||||||
|
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
|
||||||
|
|
||||||
|
assertEquals(profile1, profile2);
|
||||||
|
assertSame(profile1, profile2);
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-29
@@ -90,34 +90,6 @@ public class ColorSpacesTest {
|
|||||||
assertTrue(created.isCS_sRGB());
|
assertTrue(created.isCS_sRGB());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
|
||||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
|
||||||
ICC_Profile profile = createBrokenProfile(internal);
|
|
||||||
assertNotSame(internal, profile); // Sanity check
|
|
||||||
|
|
||||||
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
|
|
||||||
|
|
||||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
|
||||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
|
||||||
assertTrue(created.isCS_sRGB());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
|
||||||
byte[] data = internal.getData();
|
|
||||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
|
||||||
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
|
||||||
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
|
||||||
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
|
||||||
return ICC_Profile.getInstance(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIsOffendingColorProfile() {
|
|
||||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
|
||||||
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
|
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
|
||||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||||
@@ -166,11 +138,13 @@ public class ColorSpacesTest {
|
|||||||
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
|
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Test
|
@Test
|
||||||
public void testIsCS_sRGBTrue() {
|
public void testIsCS_sRGBTrue() {
|
||||||
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Test
|
@Test
|
||||||
public void testIsCS_sRGBFalse() {
|
public void testIsCS_sRGBFalse() {
|
||||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||||
@@ -179,14 +153,36 @@ public class ColorSpacesTest {
|
|||||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testIsCS_sRGBNull() {
|
public void testIsCS_sRGBNull() {
|
||||||
ColorSpaces.isCS_sRGB(null);
|
ColorSpaces.isCS_sRGB(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
public void testIsCS_GRAYTrue() {
|
||||||
|
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
public void testIsCS_GRAYFalse() {
|
||||||
|
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||||
|
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||||
|
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||||
|
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testIsCS_GRAYNull() {
|
||||||
|
ColorSpaces.isCS_GRAY(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEqualHeadersDifferentProfile() throws IOException {
|
public void testEqualHeadersDifferentProfile() throws IOException {
|
||||||
// These profiles are extracted from various JPEGs, and have the exact same profile header...
|
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different)...
|
||||||
ICC_Profile profile1 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
ICC_Profile profile1 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||||
ICC_Profile profile2 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
ICC_Profile profile2 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||||
|
|
||||||
|
|||||||
-2
@@ -39,8 +39,6 @@ import java.io.IOException;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
public class KCMSSanitizerStrategyTest {
|
public class KCMSSanitizerStrategyTest {
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
public class ProfileDeferralActivatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivateProfiles() {
|
||||||
|
// Should just run with no exceptions...
|
||||||
|
ProfileDeferralActivator.activateProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSpiRegistration() {
|
||||||
|
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
|
||||||
|
ServiceRegistry registry = mock(ServiceRegistry.class);
|
||||||
|
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
|
||||||
|
|
||||||
|
spi.onRegistration(registry, category);
|
||||||
|
|
||||||
|
verify(registry, only()).deregisterServiceProvider(spi, category);
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
-14
@@ -45,9 +45,8 @@ import javax.imageio.spi.IIORegistry;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.SampleModel;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
@@ -57,6 +56,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
@@ -1151,7 +1151,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(reader, 0);
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
@@ -1184,9 +1184,9 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
ordered.verify(listenerToo).imageStarted(reader, 0);
|
ordered.verify(listenerToo).imageStarted(reader, 0);
|
||||||
ordered.verify(listenerThree).imageStarted(reader, 0);
|
ordered.verify(listenerThree).imageStarted(reader, 0);
|
||||||
|
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
|
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
ordered.verify(listenerToo).imageComplete(reader);
|
ordered.verify(listenerToo).imageComplete(reader);
|
||||||
@@ -1226,7 +1226,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,11 +1253,11 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods on listener1...
|
// Should not have called any methods on listener1...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
|
|
||||||
InOrder ordered = inOrder(listenerToo);
|
InOrder ordered = inOrder(listenerToo);
|
||||||
ordered.verify(listenerToo).imageStarted(reader, 0);
|
ordered.verify(listenerToo).imageStarted(reader, 0);
|
||||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listenerToo).imageComplete(reader);
|
ordered.verify(listenerToo).imageComplete(reader);
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
@@ -1281,7 +1281,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1307,8 +1307,8 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
verifyZeroInteractions(listenerToo);
|
verifyNoInteractions(listenerToo);
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1333,7 +1333,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
doAnswer(abort).when(abortingListener).imageStarted(any(ImageReader.class), anyInt());
|
doAnswer(abort).when(abortingListener).imageStarted(any(ImageReader.class), anyInt());
|
||||||
doAnswer(abort).when(abortingListener).imageProgress(any(ImageReader.class), anyInt());
|
doAnswer(abort).when(abortingListener).imageProgress(any(ImageReader.class), anyFloat());
|
||||||
|
|
||||||
reader.addIIOReadProgressListener(abortingListener);
|
reader.addIIOReadProgressListener(abortingListener);
|
||||||
|
|
||||||
@@ -1738,6 +1738,43 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
|
||||||
|
// Allow subclasses to filter out test data that can't be converted to a compatible image without data loss
|
||||||
|
return getTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAffineTransformOpCompatibility() throws IOException {
|
||||||
|
// Test that the output of normal images are compatible with AffineTransformOp. Is unlikely to work on all test data
|
||||||
|
ImageReader reader = createReader();
|
||||||
|
|
||||||
|
for (TestData testData : getTestDataForAffineTransformOpCompatibility()) {
|
||||||
|
try (ImageInputStream input = testData.getInputStream()) {
|
||||||
|
reader.setInput(input);
|
||||||
|
|
||||||
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRegion(new Rectangle(min(reader.getWidth(0), 64), min(reader.getHeight(0), 64)));
|
||||||
|
|
||||||
|
BufferedImage originalImage = reader.read(0, param);
|
||||||
|
|
||||||
|
AffineTransform transform = AffineTransform.getTranslateInstance(10, 10);
|
||||||
|
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BufferedImage resultImage = op.filter(originalImage, null); // The exception happens here
|
||||||
|
assertNotNull(resultImage);
|
||||||
|
}
|
||||||
|
catch (ImagingOpException e) {
|
||||||
|
fail(e.getMessage() + ".\n\t"
|
||||||
|
+ originalImage + "\n\t"
|
||||||
|
+ testData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore("TODO: Implement")
|
@Ignore("TODO: Implement")
|
||||||
@Test
|
@Test
|
||||||
public void testSetDestinationBands() {
|
public void testSetDestinationBands() {
|
||||||
|
|||||||
+12
-13
@@ -52,7 +52,6 @@ import java.net.URL;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,7 +78,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
protected abstract ImageWriterSpi createProvider();
|
protected abstract ImageWriterSpi createProvider();
|
||||||
|
|
||||||
protected final T createWriter() throws IOException {
|
protected final T createWriter() throws IOException {
|
||||||
return writerClass.cast(provider.createWriterInstance(null));
|
return writerClass.cast(provider.createWriterInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract List<? extends RenderedImage> getTestData();
|
protected abstract List<? extends RenderedImage> getTestData();
|
||||||
@@ -104,7 +103,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final RenderedImage getTestData(final int index) {
|
protected final RenderedImage getTestData(final int index) {
|
||||||
return getTestData().get(index);
|
return getTestData().get(index);
|
||||||
}
|
}
|
||||||
@@ -219,7 +218,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(writer, 0);
|
ordered.verify(listener).imageStarted(writer, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(writer);
|
ordered.verify(listener).imageComplete(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,9 +250,9 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
ordered.verify(listenerToo).imageStarted(writer, 0);
|
ordered.verify(listenerToo).imageStarted(writer, 0);
|
||||||
ordered.verify(listenerThree).imageStarted(writer, 0);
|
ordered.verify(listenerThree).imageStarted(writer, 0);
|
||||||
|
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||||
|
|
||||||
ordered.verify(listener).imageComplete(writer);
|
ordered.verify(listener).imageComplete(writer);
|
||||||
ordered.verify(listenerToo).imageComplete(writer);
|
ordered.verify(listenerToo).imageComplete(writer);
|
||||||
@@ -290,7 +289,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -315,12 +314,12 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
|
|
||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listenerToo);
|
InOrder ordered = inOrder(listenerToo);
|
||||||
ordered.verify(listenerToo).imageStarted(writer, 0);
|
ordered.verify(listenerToo).imageStarted(writer, 0);
|
||||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||||
ordered.verify(listenerToo).imageComplete(writer);
|
ordered.verify(listenerToo).imageComplete(writer);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -345,7 +344,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -371,7 +370,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should not have called any methods...
|
// Should not have called any methods...
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
verifyZeroInteractions(listenerToo);
|
verifyNoInteractions(listenerToo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-hdr</artifactId>
|
<artifactId>imageio-hdr</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||||
|
|||||||
+10
-2
@@ -38,12 +38,14 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TGAImageReaderTest
|
* HDRImageReaderTest
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
* @version $Id: HDRImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
||||||
@Override
|
@Override
|
||||||
@@ -58,6 +60,12 @@ public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
|
||||||
|
// HDR images uses floating point buffers...
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-icns</artifactId>
|
<artifactId>imageio-icns</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-iff</artifactId>
|
<artifactId>imageio-iff</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.jpms.module.name>com.twelvemonkeys.imageio.jaiinterop</project.jpms.module.name>
|
<project.jpms.module.name>com.twelvemonkeys.imageio.jpeg.jaiinterop</project.jpms.module.name>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
<groupId>com.github.jai-imageio</groupId>
|
<groupId>com.github.jai-imageio</groupId>
|
||||||
<artifactId>jai-imageio-core</artifactId>
|
<artifactId>jai-imageio-core</artifactId>
|
||||||
<version>1.4.0</version>
|
<version>1.4.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.jpms.module.name>com.twelvemonkeys.imageio.jep262interop</project.jpms.module.name>
|
<project.jpms.module.name>com.twelvemonkeys.imageio.jpeg.jep262interop</project.jpms.module.name>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-jpeg</artifactId>
|
<artifactId>imageio-jpeg</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||||
|
|||||||
+1
-1
@@ -119,7 +119,7 @@ final class EXIFThumbnail {
|
|||||||
case 6:
|
case 6:
|
||||||
// YCbCr
|
// YCbCr
|
||||||
for (int i = 0; i < thumbLength; i += 3) {
|
for (int i = 0; i < thumbLength; i += 3) {
|
||||||
YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
|
YCbCrConverter.convertJPEGYCbCr2RGB(thumbData, thumbData, i);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
+13
-32
@@ -31,6 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
@@ -123,7 +124,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
private int currentStreamIndex = 0;
|
private int currentStreamIndex = 0;
|
||||||
private final List<Long> streamOffsets = new ArrayList<>();
|
private final List<Long> streamOffsets = new ArrayList<>();
|
||||||
|
|
||||||
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||||
super(provider);
|
super(provider);
|
||||||
|
|
||||||
this.delegate = Validate.notNull(delegate);
|
this.delegate = Validate.notNull(delegate);
|
||||||
@@ -335,7 +336,7 @@ public final 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.
|
||||||
else if (bogusAdobeDCT
|
else if (bogusAdobeDCT
|
||||||
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|
||||||
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|
||||||
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
|
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -614,7 +615,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) throws IOException {
|
||||||
// NOTE: This is probably not the right way to do it... :-P
|
// NOTE: This is probably not the right way to do it... :-P
|
||||||
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
||||||
|
|
||||||
@@ -630,7 +631,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
|
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
|
||||||
|
|
||||||
return ICC_Profile.getInstance(profileData);
|
return ColorProfiles.createProfile(profileData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -950,11 +951,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
|
return readICCProfileSafe(stream, allowBadIndexes);
|
||||||
int iccChunkDataSize = segment.data.length - segmentDataStart;
|
|
||||||
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
|
|
||||||
|
|
||||||
return readICCProfileSafe(stream, allowBadIndexes, iccSize, iccChunkDataSize);
|
|
||||||
}
|
}
|
||||||
else if (!segments.isEmpty()) {
|
else if (!segments.isEmpty()) {
|
||||||
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
|
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
|
||||||
@@ -989,9 +986,6 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
InputStream[] streams = new InputStream[count];
|
InputStream[] streams = new InputStream[count];
|
||||||
streams[badICC ? 0 : chunkNumber - 1] = stream;
|
streams[badICC ? 0 : chunkNumber - 1] = stream;
|
||||||
|
|
||||||
int iccChunkDataSize = 0;
|
|
||||||
int iccSize = 0;
|
|
||||||
|
|
||||||
for (int i = 1; i < count; i++) {
|
for (int i = 1; i < count; i++) {
|
||||||
Application segment = segments.get(i);
|
Application segment = segments.get(i);
|
||||||
stream = new DataInputStream(segment.data());
|
stream = new DataInputStream(segment.data());
|
||||||
@@ -1004,33 +998,20 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
int index = badICC ? i : chunkNumber - 1;
|
int index = badICC ? i : chunkNumber - 1;
|
||||||
streams[index] = stream;
|
streams[index] = stream;
|
||||||
|
|
||||||
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
|
|
||||||
iccChunkDataSize += segment.data.length - segmentDataStart;
|
|
||||||
if (index == 0) {
|
|
||||||
iccSize = intFromBigEndian(segment.data, segmentDataStart);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes, iccSize, iccChunkDataSize);
|
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile, final int iccSize, final int iccChunkDataSize) throws IOException {
|
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
|
||||||
if (iccSize < 0 || iccSize > iccChunkDataSize) {
|
|
||||||
processWarningOccurred(String.format("Truncated 'ICC_PROFILE' chunk(s), size: %d. Ignoring ICC profile.", iccSize));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ICC_Profile profile = ICC_Profile.getInstance(stream);
|
|
||||||
|
|
||||||
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
|
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
|
||||||
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
|
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (IOException | RuntimeException e) {
|
||||||
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
||||||
// Usual reason: Broken tools store truncated ICC profiles in a single ICC_PROFILE chunk...
|
// Usual reason: Broken tools store truncated ICC profiles in a single ICC_PROFILE chunk...
|
||||||
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk(s): %s. Ignoring ICC profile.", e.getMessage()));
|
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk(s): %s. Ignoring ICC profile.", e.getMessage()));
|
||||||
@@ -1169,7 +1150,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
processThumbnailStarted(imageIndex, thumbnailIndex);
|
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
processThumbnailProgress(0f);
|
processThumbnailProgress(0f);
|
||||||
|
|
||||||
BufferedImage thumbnail = thumbnails.get(thumbnailIndex).read();;
|
BufferedImage thumbnail = thumbnails.get(thumbnailIndex).read();
|
||||||
|
|
||||||
processThumbnailProgress(100f);
|
processThumbnailProgress(100f);
|
||||||
processThumbnailComplete();
|
processThumbnailComplete();
|
||||||
@@ -1211,7 +1192,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
YCbCrConverter.convertYCbCr2RGB(data, data, (x + y * width) * numComponents);
|
YCbCrConverter.convertJPEGYCbCr2RGB(data, data, (x + y * width) * numComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1225,7 +1206,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
int offset = (x + y * width) * 4;
|
int offset = (x + y * width) * 4;
|
||||||
// YCC -> CMY
|
// YCC -> CMY
|
||||||
YCbCrConverter.convertYCbCr2RGB(data, data, offset);
|
YCbCrConverter.convertJPEGYCbCr2RGB(data, data, offset);
|
||||||
// Inverse K
|
// Inverse K
|
||||||
data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
|
data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -36,7 +36,6 @@ import com.twelvemonkeys.lang.StringUtil;
|
|||||||
|
|
||||||
import org.hamcrest.core.IsInstanceOf;
|
import org.hamcrest.core.IsInstanceOf;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.internal.matchers.GreaterThan;
|
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@@ -66,12 +65,11 @@ import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
|
|||||||
import static org.hamcrest.CoreMatchers.allOf;
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assume.assumeNoException;
|
import static org.junit.Assume.assumeNoException;
|
||||||
import static org.junit.Assume.assumeNotNull;
|
import static org.junit.Assume.assumeNotNull;
|
||||||
import static org.mockito.AdditionalMatchers.and;
|
import static org.mockito.AdditionalMatchers.and;
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1456,7 +1454,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
|
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
|
||||||
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
|
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
|
||||||
assertNotNull(markerSequence);
|
assertNotNull(markerSequence);
|
||||||
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
|
assertThat(markerSequence.getChildNodes().getLength(), greaterThan(0));
|
||||||
|
|
||||||
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
|
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
|
||||||
for (int j = 0; j < unknowns.getLength(); j++) {
|
for (int j = 0; j < unknowns.getLength(); j++) {
|
||||||
|
|||||||
+2
-2
@@ -36,7 +36,6 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
|||||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.internal.matchers.LessOrEqual;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -48,6 +47,7 @@ import java.net.URL;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ public class JPEGSegmentImageInputStreamTest {
|
|||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertThat(length, new LessOrEqual<>(10203L)); // In no case should length increase
|
assertThat(length, lessThanOrEqualTo(10203L)); // In no case should length increase
|
||||||
|
|
||||||
assertEquals(9607L, length); // May change, if more chunks are passed to reader...
|
assertEquals(9607L, length); // May change, if more chunks are passed to reader...
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>imageio-metadata</artifactId>
|
<artifactId>imageio-metadata</artifactId>
|
||||||
|
|||||||
+1
@@ -46,6 +46,7 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFReader instead.
|
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFReader instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class EXIFReader extends MetadataReader {
|
public final class EXIFReader extends MetadataReader {
|
||||||
|
|
||||||
private final TIFFReader delegate = new TIFFReader();
|
private final TIFFReader delegate = new TIFFReader();
|
||||||
|
|||||||
+1
@@ -48,6 +48,7 @@ import java.util.Collection;
|
|||||||
*
|
*
|
||||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter instead.
|
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class EXIFWriter extends MetadataWriter {
|
public final class EXIFWriter extends MetadataWriter {
|
||||||
|
|
||||||
private final TIFFWriter delegate = new TIFFWriter();
|
private final TIFFWriter delegate = new TIFFWriter();
|
||||||
|
|||||||
+1
@@ -41,6 +41,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
*
|
*
|
||||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.Rational instead.
|
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.Rational instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class Rational extends Number implements Comparable<Rational> {
|
public final class Rational extends Number implements Comparable<Rational> {
|
||||||
private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate;
|
private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate;
|
||||||
|
|
||||||
|
|||||||
+1
@@ -39,5 +39,6 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
*
|
*
|
||||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFF instead.
|
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFF instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public interface TIFF extends com.twelvemonkeys.imageio.metadata.tiff.TIFF {
|
public interface TIFF extends com.twelvemonkeys.imageio.metadata.tiff.TIFF {
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.jpeg;
|
package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||||
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.psd.PSD;
|
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||||
@@ -42,7 +43,6 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
|||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.color.ICC_ColorSpace;
|
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
@@ -353,8 +353,8 @@ public final class JPEGSegmentUtil {
|
|||||||
Directory psd = new PSDReader().read(stream);
|
Directory psd = new PSDReader().read(stream);
|
||||||
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
|
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
|
||||||
if (iccEntry != null) {
|
if (iccEntry != null) {
|
||||||
ICC_ColorSpace colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance((byte[]) iccEntry.getValue()));
|
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
|
||||||
System.err.println("colorSpace: " + colorSpace);
|
System.err.println("ICC Profile: " + profile);
|
||||||
}
|
}
|
||||||
System.err.println("PSD: " + psd);
|
System.err.println("PSD: " + psd);
|
||||||
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
||||||
|
|||||||
+13
-13
@@ -30,8 +30,15 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||||
|
|
||||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -39,15 +46,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFReader
|
* TIFFReader
|
||||||
@@ -73,16 +72,17 @@ public final class TIFFReader extends MetadataReader {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
map.put(TIFF.TAG_SUB_IFD, Collections.unmodifiableCollection(Collections.singleton(TIFF.TAG_SUB_IFD)));
|
map.put(TIFF.TAG_SUB_IFD, Collections.singleton(TIFF.TAG_SUB_IFD));
|
||||||
map.put(TIFF.TAG_EXIF_IFD, Collections.unmodifiableCollection(Collections.singleton(TIFF.TAG_INTEROP_IFD)));
|
map.put(TIFF.TAG_EXIF_IFD, Collections.singleton(TIFF.TAG_INTEROP_IFD));
|
||||||
|
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Set<Long> parsedIFDs = new TreeSet<>();
|
||||||
|
|
||||||
private long length;
|
private long length;
|
||||||
private boolean longOffsets;
|
private boolean longOffsets;
|
||||||
private int offsetSize;
|
private int offsetSize;
|
||||||
private Set<Long> parsedIFDs = new TreeSet<>();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Directory read(final ImageInputStream input) throws IOException {
|
public Directory read(final ImageInputStream input) throws IOException {
|
||||||
|
|||||||
+118
-29
@@ -40,7 +40,6 @@ import javax.imageio.IIOException;
|
|||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -58,7 +57,24 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
|
|
||||||
private static final int WORD_LENGTH = 2;
|
private static final int WORD_LENGTH = 2;
|
||||||
private static final int LONGWORD_LENGTH = 4;
|
private static final int LONGWORD_LENGTH = 4;
|
||||||
private static final int ENTRY_LENGTH = 12;
|
|
||||||
|
// TODO: We probably want to gloss over client code writing IFDs in BigTIFF (or vice versa) somehow... Silently convert IFD -> IFD8
|
||||||
|
private final boolean longOffsets;
|
||||||
|
private final int offsetSize;
|
||||||
|
private final long entryLength;
|
||||||
|
private final int directoryCountLength;
|
||||||
|
|
||||||
|
public TIFFWriter() {
|
||||||
|
this(LONGWORD_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TIFFWriter(int offsetSize) {
|
||||||
|
this.offsetSize = Validate.isTrue(offsetSize == 4 || offsetSize == 8, offsetSize, "offsetSize must be 4 for TIFF or 8 for BigTIFF");
|
||||||
|
|
||||||
|
longOffsets = offsetSize == 8;
|
||||||
|
directoryCountLength = longOffsets ? 8 : WORD_LENGTH;
|
||||||
|
entryLength = 2 * WORD_LENGTH + 2 * offsetSize;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean write(final Collection<? extends Entry> entries, final ImageOutputStream stream) throws IOException {
|
public boolean write(final Collection<? extends Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||||
return write(new IFD(entries), stream);
|
return write(new IFD(entries), stream);
|
||||||
@@ -87,7 +103,7 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Offset to next IFD (EOF)
|
// Offset to next IFD (EOF)
|
||||||
stream.writeInt(0);
|
writeOffset(stream, 0);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -96,7 +112,12 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
// Header
|
// Header
|
||||||
ByteOrder byteOrder = stream.getByteOrder();
|
ByteOrder byteOrder = stream.getByteOrder();
|
||||||
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
||||||
stream.writeShort(42);
|
stream.writeShort(longOffsets ? TIFF.BIGTIFF_MAGIC : TIFF.TIFF_MAGIC);
|
||||||
|
|
||||||
|
if (longOffsets) {
|
||||||
|
stream.writeShort(offsetSize); // Always 8 in this case
|
||||||
|
stream.writeShort(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||||
@@ -118,37 +139,42 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
long dataSize = computeDataSize(ordered);
|
long dataSize = computeDataSize(ordered);
|
||||||
|
|
||||||
// Offset to this IFD
|
// Offset to this IFD
|
||||||
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
|
final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize;
|
||||||
|
|
||||||
if (!isSubIFD) {
|
if (!isSubIFD) {
|
||||||
stream.writeInt(assertIntegerOffset(ifdOffset));
|
writeOffset(stream, ifdOffset);
|
||||||
dataOffset += LONGWORD_LENGTH;
|
dataOffset += offsetSize;
|
||||||
|
|
||||||
// Seek to offset
|
// Seek to offset
|
||||||
stream.seek(ifdOffset);
|
stream.seek(ifdOffset);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
|
dataOffset += directoryCountLength + ordered.size() * entryLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write directory
|
// Write directory
|
||||||
stream.writeShort(ordered.size());
|
writeDirectoryCount(stream, ordered.size());
|
||||||
|
|
||||||
for (Entry entry : ordered) {
|
for (Entry entry : ordered) {
|
||||||
// Write tag id
|
// Write tag id, type & value count
|
||||||
stream.writeShort((Integer) entry.getIdentifier());
|
stream.writeShort((Integer) entry.getIdentifier());
|
||||||
// Write tag type
|
|
||||||
stream.writeShort(getType(entry));
|
stream.writeShort(getType(entry));
|
||||||
// Write value count
|
writeValueCount(stream, getCount(entry));
|
||||||
stream.writeInt(getCount(entry));
|
|
||||||
|
|
||||||
// Write value
|
// Write value
|
||||||
if (entry.getValue() instanceof Directory) {
|
Object value = entry.getValue();
|
||||||
// TODO: This could possibly be a compound directory, in which case the count should be > 1
|
if (value instanceof Directory) {
|
||||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
if (value instanceof CompoundDirectory) {
|
||||||
long streamPosition = stream.getStreamPosition();
|
// Can't have both nested and linked IFDs
|
||||||
|
throw new AssertionError("SubIFD cannot contain linked IFDs");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't write offset here, we need to write value, as both LONG/IFD and LONG8/IFD8 is allowed
|
||||||
|
// TODO: Or possibly gloss over, by always writing IFD8 for BigTIFF?
|
||||||
|
long streamPosition = stream.getStreamPosition() + offsetSize;
|
||||||
|
writeValueInline(dataOffset, getType(entry), stream);
|
||||||
stream.seek(dataOffset);
|
stream.seek(dataOffset);
|
||||||
Directory subIFD = (Directory) entry.getValue();
|
Directory subIFD = (Directory) value;
|
||||||
writeIFD(subIFD, stream, true);
|
writeIFD(subIFD, stream, true);
|
||||||
dataOffset += computeDataSize(subIFD);
|
dataOffset += computeDataSize(subIFD);
|
||||||
stream.seek(streamPosition);
|
stream.seek(streamPosition);
|
||||||
@@ -161,8 +187,26 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
return ifdOffset;
|
return ifdOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long computeIFDSize(final Collection<Entry> directory) {
|
private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException {
|
||||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
if (longOffsets) {
|
||||||
|
stream.writeLong(count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.writeShort(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeValueCount(ImageOutputStream stream, int count) throws IOException {
|
||||||
|
if (longOffsets) {
|
||||||
|
stream.writeLong(count);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stream.writeInt(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long computeIFDSize(final Collection<? extends Entry> directory) {
|
||||||
|
return directoryCountLength + computeDataSize(new IFD(directory)) + directory.size() * entryLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long computeDataSize(final Directory directory) {
|
private long computeDataSize(final Directory directory) {
|
||||||
@@ -175,13 +219,13 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > LONGWORD_LENGTH) {
|
if (length > offsetSize) {
|
||||||
dataSize += length;
|
dataSize += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.getValue() instanceof Directory) {
|
if (entry.getValue() instanceof Directory) {
|
||||||
Directory subIFD = (Directory) entry.getValue();
|
Directory subIFD = (Directory) entry.getValue();
|
||||||
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
|
long subIFDSize = directoryCountLength + computeDataSize(subIFD) + subIFD.size() * entryLength;
|
||||||
dataSize += subIFDSize;
|
dataSize += subIFDSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,11 +273,11 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
short type = getType(entry);
|
short type = getType(entry);
|
||||||
long valueLength = getValueLength(type, getCount(entry));
|
long valueLength = getValueLength(type, getCount(entry));
|
||||||
|
|
||||||
if (valueLength <= LONGWORD_LENGTH) {
|
if (valueLength <= offsetSize) {
|
||||||
writeValueInline(entry.getValue(), type, stream);
|
writeValueInline(entry.getValue(), type, stream);
|
||||||
|
|
||||||
// Pad
|
// Pad
|
||||||
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
|
for (long i = valueLength; i < offsetSize; i++) {
|
||||||
stream.write(0);
|
stream.write(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +292,7 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
|
|
||||||
private int getCount(final Entry entry) {
|
private int getCount(final Entry entry) {
|
||||||
Object value = entry.getValue();
|
Object value = entry.getValue();
|
||||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
return value instanceof String ? ((String) value).getBytes(StandardCharsets.UTF_8).length + 1 : entry.valueCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||||
@@ -344,12 +388,28 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
doubles = (double[]) value;
|
doubles = (double[]) value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
throw new IllegalArgumentException("Unsupported type for TIFF DOUBLE: " + value.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.writeDoubles(doubles, 0, doubles.length);
|
stream.writeDoubles(doubles, 0, doubles.length);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case TIFF.TYPE_LONG8:
|
||||||
|
case TIFF.TYPE_SLONG8:
|
||||||
|
if (longOffsets) {
|
||||||
|
long[] longs;
|
||||||
|
|
||||||
|
if (value instanceof long[]) {
|
||||||
|
longs = (long[]) value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IllegalArgumentException("Unsupported type for TIFF LONG8: " + value.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.writeLongs(longs, 0, longs.length);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||||
@@ -373,6 +433,7 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
break;
|
break;
|
||||||
case TIFF.TYPE_LONG:
|
case TIFF.TYPE_LONG:
|
||||||
case TIFF.TYPE_SLONG:
|
case TIFF.TYPE_SLONG:
|
||||||
|
case TIFF.TYPE_IFD:
|
||||||
stream.writeInt(((Number) value).intValue());
|
stream.writeInt(((Number) value).intValue());
|
||||||
break;
|
break;
|
||||||
case TIFF.TYPE_RATIONAL:
|
case TIFF.TYPE_RATIONAL:
|
||||||
@@ -387,6 +448,13 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
case TIFF.TYPE_DOUBLE:
|
case TIFF.TYPE_DOUBLE:
|
||||||
stream.writeDouble(((Number) value).doubleValue());
|
stream.writeDouble(((Number) value).doubleValue());
|
||||||
break;
|
break;
|
||||||
|
case TIFF.TYPE_LONG8:
|
||||||
|
case TIFF.TYPE_SLONG8:
|
||||||
|
case TIFF.TYPE_IFD8:
|
||||||
|
if (longOffsets) {
|
||||||
|
stream.writeLong(((Number) value).longValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||||
@@ -395,18 +463,39 @@ public final class TIFFWriter extends MetadataWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
writeOffset(stream, dataOffset);
|
||||||
long position = stream.getStreamPosition();
|
long position = stream.getStreamPosition();
|
||||||
stream.seek(dataOffset);
|
stream.seek(dataOffset);
|
||||||
writeValueInline(value, type, stream);
|
writeValueInline(value, type, stream);
|
||||||
stream.seek(position);
|
stream.seek(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int assertIntegerOffset(long offset) throws IIOException {
|
public void writeOffset(final ImageOutputStream output, long offset) throws IOException {
|
||||||
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
if (longOffsets) {
|
||||||
|
output.writeLong(assertLongOffset(offset));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output.writeInt(assertIntegerOffset(offset)); // Treated as unsigned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int offsetSize() {
|
||||||
|
return offsetSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int assertIntegerOffset(final long offset) throws IIOException {
|
||||||
|
if (offset < 0 || offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||||
throw new IIOException("Integer overflow for TIFF stream");
|
throw new IIOException("Integer overflow for TIFF stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) offset;
|
return (int) offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long assertLongOffset(final long offset) throws IIOException {
|
||||||
|
if (offset < 0) {
|
||||||
|
throw new IIOException("Long overflow for BigTIFF stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-27
@@ -30,11 +30,21 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import java.io.IOException;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import java.util.ArrayList;
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
import java.util.Collections;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import java.util.Iterator;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import javax.xml.XMLConstants;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.NamedNodeMap;
|
import org.w3c.dom.NamedNodeMap;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@@ -43,13 +53,11 @@ import org.xml.sax.InputSource;
|
|||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XMPReader
|
* XMPReader
|
||||||
@@ -67,10 +75,9 @@ public final class XMPReader extends MetadataReader {
|
|||||||
public Directory read(final ImageInputStream input) throws IOException {
|
public Directory read(final ImageInputStream input) throws IOException {
|
||||||
Validate.notNull(input, "input");
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
||||||
factory.setNamespaceAware(true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
DocumentBuilderFactory factory = createDocumentBuilderFactory();
|
||||||
|
|
||||||
// TODO: Consider parsing using SAX?
|
// TODO: Consider parsing using SAX?
|
||||||
// TODO: Determine encoding and parse using a Reader...
|
// TODO: Determine encoding and parse using a Reader...
|
||||||
// TODO: Refactor scanner to return inputstream?
|
// TODO: Refactor scanner to return inputstream?
|
||||||
@@ -79,9 +86,6 @@ public final class XMPReader extends MetadataReader {
|
|||||||
builder.setErrorHandler(new DefaultHandler());
|
builder.setErrorHandler(new DefaultHandler());
|
||||||
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
|
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
|
||||||
|
|
||||||
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
|
|
||||||
// serializer.serialize(document);
|
|
||||||
|
|
||||||
String toolkit = getToolkit(document);
|
String toolkit = getToolkit(document);
|
||||||
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
|
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
|
||||||
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
|
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
|
||||||
@@ -92,10 +96,33 @@ public final class XMPReader extends MetadataReader {
|
|||||||
throw new IIOException(e.getMessage(), e);
|
throw new IIOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
catch (ParserConfigurationException e) {
|
catch (ParserConfigurationException e) {
|
||||||
throw new RuntimeException(e); // TODO: Or IOException?
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
factory.setNamespaceAware(true);
|
||||||
|
|
||||||
|
// Security: Disable XInclude & expanding entity references ("bombs"), not needed for XMP
|
||||||
|
factory.setXIncludeAware(false);
|
||||||
|
factory.setExpandEntityReferences(false);
|
||||||
|
|
||||||
|
// Security: Enable "secure processing", to prevent DoS attacks
|
||||||
|
factory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||||
|
|
||||||
|
// Security: Remove possibility to access external DTDs or Schema, not needed for XMP
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
|
||||||
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
|
||||||
|
|
||||||
|
// Security: Disable loading of external DTD and entities, not needed for XMP
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||||
|
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
private String getToolkit(Document document) {
|
private String getToolkit(Document document) {
|
||||||
NodeList xmpmeta = document.getElementsByTagNameNS(XMP.NS_X, "xmpmeta");
|
NodeList xmpmeta = document.getElementsByTagNameNS(XMP.NS_X, "xmpmeta");
|
||||||
|
|
||||||
@@ -109,7 +136,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes, String toolkit) {
|
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes, String toolkit) {
|
||||||
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
|
Map<String, List<Entry>> subdirs = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (Node desc : asIterable(pNodes)) {
|
for (Node desc : asIterable(pNodes)) {
|
||||||
if (desc.getParentNode() != pParentNode) {
|
if (desc.getParentNode() != pParentNode) {
|
||||||
@@ -127,7 +154,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
// Lookup
|
// Lookup
|
||||||
List<Entry> dir = subdirs.get(node.getNamespaceURI());
|
List<Entry> dir = subdirs.get(node.getNamespaceURI());
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
dir = new ArrayList<Entry>();
|
dir = new ArrayList<>();
|
||||||
subdirs.put(node.getNamespaceURI(), dir);
|
subdirs.put(node.getNamespaceURI(), dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +166,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
else {
|
else {
|
||||||
// TODO: This method contains loads of duplication an should be cleaned up...
|
// TODO: This method contains loads of duplication an should be cleaned up...
|
||||||
// Support attribute short-hand syntax
|
// Support attribute short-hand syntax
|
||||||
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<String, List<Entry>>();
|
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<>();
|
||||||
|
|
||||||
parseAttributesForKnownElements(subsubdirs, node);
|
parseAttributesForKnownElements(subsubdirs, node);
|
||||||
|
|
||||||
@@ -161,7 +188,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Directory> entries = new ArrayList<Directory>(subdirs.size());
|
List<Directory> entries = new ArrayList<>(subdirs.size());
|
||||||
|
|
||||||
// TODO: Should we still allow asking for a subdirectory by item id?
|
// TODO: Should we still allow asking for a subdirectory by item id?
|
||||||
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
|
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
|
||||||
@@ -179,7 +206,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
|
|
||||||
private RDFDescription parseAsResource(Node node) {
|
private RDFDescription parseAsResource(Node node) {
|
||||||
// See: http://www.w3.org/TR/REC-rdf-syntax/#section-Syntax-parsetype-resource
|
// See: http://www.w3.org/TR/REC-rdf-syntax/#section-Syntax-parsetype-resource
|
||||||
List<Entry> entries = new ArrayList<Entry>();
|
List<Entry> entries = new ArrayList<>();
|
||||||
|
|
||||||
for (Node child : asIterable(node.getChildNodes())) {
|
for (Node child : asIterable(node.getChildNodes())) {
|
||||||
if (child.getNodeType() != Node.ELEMENT_NODE) {
|
if (child.getNodeType() != Node.ELEMENT_NODE) {
|
||||||
@@ -204,7 +231,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
List<Entry> dir = subdirs.get(attr.getNamespaceURI());
|
List<Entry> dir = subdirs.get(attr.getNamespaceURI());
|
||||||
|
|
||||||
if (dir == null) {
|
if (dir == null) {
|
||||||
dir = new ArrayList<Entry>();
|
dir = new ArrayList<>();
|
||||||
subdirs.put(attr.getNamespaceURI(), dir);
|
subdirs.put(attr.getNamespaceURI(), dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +243,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
for (Node child : asIterable(node.getChildNodes())) {
|
for (Node child : asIterable(node.getChildNodes())) {
|
||||||
if (XMP.NS_RDF.equals(child.getNamespaceURI()) && "Alt".equals(child.getLocalName())) {
|
if (XMP.NS_RDF.equals(child.getNamespaceURI()) && "Alt".equals(child.getLocalName())) {
|
||||||
// Support for <rdf:Alt><rdf:li> -> return a Map<String, Object> keyed on xml:lang
|
// Support for <rdf:Alt><rdf:li> -> return a Map<String, Object> keyed on xml:lang
|
||||||
Map<String, Object> alternatives = new LinkedHashMap<String, Object>();
|
Map<String, Object> alternatives = new LinkedHashMap<>();
|
||||||
for (Node alternative : asIterable(child.getChildNodes())) {
|
for (Node alternative : asIterable(child.getChildNodes())) {
|
||||||
if (XMP.NS_RDF.equals(alternative.getNamespaceURI()) && "li".equals(alternative.getLocalName())) {
|
if (XMP.NS_RDF.equals(alternative.getNamespaceURI()) && "li".equals(alternative.getLocalName())) {
|
||||||
NamedNodeMap attributes = alternative.getAttributes();
|
NamedNodeMap attributes = alternative.getAttributes();
|
||||||
@@ -230,7 +257,7 @@ public final class XMPReader extends MetadataReader {
|
|||||||
else if (XMP.NS_RDF.equals(child.getNamespaceURI()) && ("Seq".equals(child.getLocalName()) || "Bag".equals(child.getLocalName()))) {
|
else if (XMP.NS_RDF.equals(child.getNamespaceURI()) && ("Seq".equals(child.getLocalName()) || "Bag".equals(child.getLocalName()))) {
|
||||||
// Support for <rdf:Seq><rdf:li> -> return array
|
// Support for <rdf:Seq><rdf:li> -> return array
|
||||||
// Support for <rdf:Bag><rdf:li> -> return array/unordered collection (how can a serialized collection not have order?)
|
// Support for <rdf:Bag><rdf:li> -> return array/unordered collection (how can a serialized collection not have order?)
|
||||||
List<Object> seq = new ArrayList<Object>();
|
List<Object> seq = new ArrayList<>();
|
||||||
|
|
||||||
for (Node sequence : asIterable(child.getChildNodes())) {
|
for (Node sequence : asIterable(child.getChildNodes())) {
|
||||||
if (XMP.NS_RDF.equals(sequence.getNamespaceURI()) && "li".equals(sequence.getLocalName())) {
|
if (XMP.NS_RDF.equals(sequence.getNamespaceURI()) && "li".equals(sequence.getLocalName())) {
|
||||||
|
|||||||
+345
@@ -0,0 +1,345 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of the copyright holder 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 HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.*;
|
||||||
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFFWriterTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class BigTIFFWriterTest extends MetadataWriterAbstractTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected InputStream getData() throws IOException {
|
||||||
|
// TODO: Replace with BigTIFF resource
|
||||||
|
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TIFFReader createReader() {
|
||||||
|
return new TIFFReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected TIFFWriter createWriter() {
|
||||||
|
return new TIFFWriter(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteReadSimple() throws IOException {
|
||||||
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
createWriter().write(directory, imageStream);
|
||||||
|
imageStream.flush();
|
||||||
|
|
||||||
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
|
|
||||||
|
byte[] data = output.toByteArray();
|
||||||
|
|
||||||
|
assertEquals(164, data.length);
|
||||||
|
assertEquals('M', data[0]);
|
||||||
|
assertEquals('M', data[1]);
|
||||||
|
assertEquals(0, data[2]);
|
||||||
|
assertEquals(43, data[3]);
|
||||||
|
|
||||||
|
Directory read = createReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
|
assertNotNull(read);
|
||||||
|
assertEquals(5, read.size());
|
||||||
|
|
||||||
|
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
|
||||||
|
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
|
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||||
|
assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||||
|
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
|
||||||
|
assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
|
||||||
|
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
|
||||||
|
assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
|
||||||
|
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
|
||||||
|
assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteMotorola() throws IOException {
|
||||||
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||||
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
|
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||||
|
|
||||||
|
createWriter().write(directory, imageStream);
|
||||||
|
imageStream.flush();
|
||||||
|
|
||||||
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
|
|
||||||
|
byte[] data = output.toByteArray();
|
||||||
|
|
||||||
|
assertEquals(94, data.length);
|
||||||
|
assertEquals('M', data[0]);
|
||||||
|
assertEquals('M', data[1]);
|
||||||
|
assertEquals(0, data[2]);
|
||||||
|
assertEquals(43, data[3]);
|
||||||
|
|
||||||
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
|
assertNotNull(read);
|
||||||
|
assertEquals(2, read.size());
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
|
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||||
|
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteIntel() throws IOException {
|
||||||
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||||
|
Directory directory = new AbstractDirectory(entries) {};
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
|
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||||
|
|
||||||
|
createWriter().write(directory, imageStream);
|
||||||
|
imageStream.flush();
|
||||||
|
|
||||||
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
|
|
||||||
|
byte[] data = output.toByteArray();
|
||||||
|
|
||||||
|
assertEquals(94, data.length);
|
||||||
|
assertEquals('I', data[0]);
|
||||||
|
assertEquals('I', data[1]);
|
||||||
|
assertEquals(43, data[2]);
|
||||||
|
assertEquals(0, data[3]);
|
||||||
|
|
||||||
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||||
|
|
||||||
|
assertNotNull(read);
|
||||||
|
assertEquals(2, read.size());
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||||
|
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||||
|
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||||
|
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNestingIFD8Long8() throws IOException {
|
||||||
|
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||||
|
|
||||||
|
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
|
||||||
|
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||||
|
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||||
|
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
|
||||||
|
|
||||||
|
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
|
createWriter().write(directory, imageStream);
|
||||||
|
imageStream.flush();
|
||||||
|
|
||||||
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
|
|
||||||
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
|
|
||||||
|
assertNotNull(read);
|
||||||
|
assertEquals(1, read.size());
|
||||||
|
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNestingIFDLong() throws IOException {
|
||||||
|
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||||
|
|
||||||
|
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
|
||||||
|
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||||
|
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||||
|
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
|
||||||
|
|
||||||
|
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
|
createWriter().write(directory, imageStream);
|
||||||
|
imageStream.flush();
|
||||||
|
|
||||||
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
|
|
||||||
|
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
|
|
||||||
|
assertNotNull(read);
|
||||||
|
assertEquals(1, read.size());
|
||||||
|
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWriteRead() throws IOException {
|
||||||
|
Directory original = createReader().read(getDataAsIIS());
|
||||||
|
|
||||||
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
||||||
|
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
|
try {
|
||||||
|
createWriter().write(original, imageOutput);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
imageOutput.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
|
|
||||||
|
assertEquals(original, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComputeIFDSize() throws IOException {
|
||||||
|
ArrayList<Entry> entries = new ArrayList<>();
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||||
|
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||||
|
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||||
|
|
||||||
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
|
writer.writeIFD(entries, stream);
|
||||||
|
|
||||||
|
assertEquals(140, writer.computeIFDSize(entries));
|
||||||
|
assertEquals(148, stream.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComputeIFDSizeNestedIFD8Long8() throws IOException {
|
||||||
|
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||||
|
|
||||||
|
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
|
||||||
|
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||||
|
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||||
|
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
|
||||||
|
|
||||||
|
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||||
|
|
||||||
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
|
writer.writeIFD(entries, stream);
|
||||||
|
|
||||||
|
assertEquals(162, writer.computeIFDSize(entries));
|
||||||
|
assertEquals(170, stream.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComputeIFDSizeNestedIFDLong() throws IOException {
|
||||||
|
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||||
|
|
||||||
|
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
|
||||||
|
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||||
|
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||||
|
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
|
||||||
|
|
||||||
|
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||||
|
|
||||||
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
|
writer.writeIFD(entries, stream);
|
||||||
|
|
||||||
|
assertEquals(162, writer.computeIFDSize(entries)); // 162 = 5 * (8 + 20) + 22
|
||||||
|
assertEquals(170, stream.getStreamPosition()); // 170 = 8 + 5 * (8 + 20) + 22
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
streamPos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
streamPos += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Method read not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Method read not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-8
@@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.metadata.tiff;
|
|||||||
import com.twelvemonkeys.imageio.metadata.*;
|
import com.twelvemonkeys.imageio.metadata.*;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -84,7 +85,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
|
|
||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
new TIFFWriter().write(directory, imageStream);
|
createWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@@ -132,7 +133,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
|
|
||||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||||
|
|
||||||
new TIFFWriter().write(directory, imageStream);
|
createWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@@ -167,7 +168,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
|
|
||||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||||
|
|
||||||
new TIFFWriter().write(directory, imageStream);
|
createWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@@ -204,7 +205,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||||
|
|
||||||
new TIFFWriter().write(directory, imageStream);
|
createWriter().write(directory, imageStream);
|
||||||
imageStream.flush();
|
imageStream.flush();
|
||||||
|
|
||||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||||
@@ -247,9 +248,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
TIFFWriter writer = createWriter();
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
ImageOutputStream stream = new NullImageOutputStream();
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
writer.write(new IFD(entries), stream);
|
writer.writeIFD(entries, stream);
|
||||||
|
|
||||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
assertEquals(94, writer.computeIFDSize(entries));
|
||||||
|
assertEquals(98, stream.getStreamPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -266,9 +268,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
|||||||
TIFFWriter writer = createWriter();
|
TIFFWriter writer = createWriter();
|
||||||
|
|
||||||
ImageOutputStream stream = new NullImageOutputStream();
|
ImageOutputStream stream = new NullImageOutputStream();
|
||||||
writer.write(new IFD(entries), stream);
|
writer.writeIFD(entries, stream);
|
||||||
|
|
||||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
assertEquals(92, writer.computeIFDSize(entries)); // 92 = 5 * (2 + 12) + 22
|
||||||
|
assertEquals(96, stream.getStreamPosition()); // 96 = 4 + 5 * (2 + 12) + 22
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||||
|
|||||||
+83
-12
@@ -30,26 +30,32 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import static org.junit.Assert.assertEquals;
|
||||||
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import javax.imageio.ImageIO;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XMPReaderTest
|
* XMPReaderTest
|
||||||
@@ -483,4 +489,69 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
|
|||||||
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/PixelYDimension"), hasValue("550"));
|
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/PixelYDimension"), hasValue("550"));
|
||||||
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA"));
|
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 1500L)
|
||||||
|
public void testNoExternalRequest() throws Exception {
|
||||||
|
// TODO: Use dynamic port?
|
||||||
|
try (HTTPServer server = new HTTPServer(7777)) {
|
||||||
|
try {
|
||||||
|
createReader().read(getResourceAsIIS("/xmp/xmp-jpeg-xxe.xml"));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
if (ioe.getMessage().contains("501")) {
|
||||||
|
throw new AssertionError("Reading should not cause external requests", ioe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other exception is a bug (but might happen if the parser does not support certain features)
|
||||||
|
throw ioe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HTTPServer implements AutoCloseable {
|
||||||
|
private final ServerSocket server;
|
||||||
|
private final Thread thread;
|
||||||
|
|
||||||
|
HTTPServer(int port) throws IOException {
|
||||||
|
server = new ServerSocket(port, 1);
|
||||||
|
thread = new Thread(new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
|
serve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serve() {
|
||||||
|
try {
|
||||||
|
Socket client = server.accept();
|
||||||
|
|
||||||
|
// Get the input stream, don't care about the request
|
||||||
|
try (InputStream inputStream = client.getInputStream()) {
|
||||||
|
while (inputStream.available() > 0) {
|
||||||
|
if (inputStream.read() == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Answer with 501, this will cause the client to throw IOException
|
||||||
|
try (OutputStream outputStream = client.getOutputStream()) {
|
||||||
|
outputStream.write("HTTP/1.0 501 Not Implemented\r\n\r\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
if (server.isClosed() && e instanceof SocketException) {
|
||||||
|
// Socket closed due to server close, all good
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void close() throws Exception {
|
||||||
|
server.close();
|
||||||
|
thread.join(); // It's advised against throwing InterruptedException here, but this is not production code...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><!DOCTYPE root [<!ENTITY % ext SYSTEM 'http://localhost:7777/xxx'> %ext;]>
|
||||||
|
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.16'>
|
||||||
|
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
|
||||||
|
|
||||||
|
<rdf:Description rdf:about=''
|
||||||
|
xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'>
|
||||||
|
<xmpMM:InstanceID>xmp.iid:7EDC21BF-371B-4189-90AF-C83A54A6A190</xmpMM:InstanceID>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:RDF>
|
||||||
|
</x:xmpmeta>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<?xpacket end='w'?>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-pcx</artifactId>
|
<artifactId>imageio-pcx</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||||
|
|||||||
+11
-4
@@ -104,8 +104,15 @@ public final class PCXImageReader extends ImageReaderBase {
|
|||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||||
|
if (rawType.getSampleModel() instanceof BandedSampleModel) {
|
||||||
// TODO: Implement
|
if (rawType.getNumBands() == 3) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||||
|
}
|
||||||
|
else if (rawType.getNumBands() == 4) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE));
|
||||||
|
}
|
||||||
|
}
|
||||||
specifiers.add(rawType);
|
specifiers.add(rawType);
|
||||||
|
|
||||||
return specifiers.iterator();
|
return specifiers.iterator();
|
||||||
@@ -142,10 +149,10 @@ public final class PCXImageReader extends ImageReaderBase {
|
|||||||
// PCX RGB has channels for 24 bit RGB, will be validated by ImageTypeSpecifier
|
// PCX RGB has channels for 24 bit RGB, will be validated by ImageTypeSpecifier
|
||||||
return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false);
|
return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false);
|
||||||
case 24:
|
case 24:
|
||||||
// Some sources says this is possible...
|
// Some sources say this is possible...
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
case 32:
|
case 32:
|
||||||
// Some sources says this is possible...
|
// Some sources say this is possible...
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel());
|
throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel());
|
||||||
|
|||||||
+1
-1
@@ -78,7 +78,7 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest<PCXImageReader>
|
|||||||
new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel)
|
new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel)
|
||||||
new TestData(getClassLoaderResource("/pcx/MARBLES.PCX"), new Dimension(1419, 1001)), // RLE encoded RGB
|
new TestData(getClassLoaderResource("/pcx/MARBLES.PCX"), new Dimension(1419, 1001)), // RLE encoded RGB
|
||||||
new TestData(getClassLoaderResource("/pcx/no-palette-monochrome.pcx"), new Dimension(128, 152)), // RLE encoded monochrome (1 bps/1 channel)
|
new TestData(getClassLoaderResource("/pcx/no-palette-monochrome.pcx"), new Dimension(128, 152)), // RLE encoded monochrome (1 bps/1 channel)
|
||||||
// See cga-pcx.txt, however, the text seems to be in error, the bits can not not as described
|
// See cga-pcx.txt, however, the text seems to be in error, I don't see how the bits can be as described
|
||||||
new TestData(getClassLoaderResource("/pcx/CGA_BW.PCX"), new Dimension(640, 200)), // RLE encoded indexed (CGA mode)
|
new TestData(getClassLoaderResource("/pcx/CGA_BW.PCX"), new Dimension(640, 200)), // RLE encoded indexed (CGA mode)
|
||||||
new TestData(getClassLoaderResource("/pcx/CGA_FSD.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
|
new TestData(getClassLoaderResource("/pcx/CGA_FSD.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
|
||||||
new TestData(getClassLoaderResource("/pcx/CGA_RGBI.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
|
new TestData(getClassLoaderResource("/pcx/CGA_RGBI.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-pdf</artifactId>
|
<artifactId>imageio-pdf</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-pict</artifactId>
|
<artifactId>imageio-pict</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-pnm</artifactId>
|
<artifactId>imageio-pnm</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
||||||
|
|||||||
+14
@@ -61,6 +61,20 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW)
|
||||||
|
new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)), // P3 (PPM PLAIN)
|
||||||
|
new TestData(getClassLoaderResource("/pbm/j.pbm"), new Dimension(6, 10)), // P1 (PBM PLAIN)
|
||||||
|
new TestData(getClassLoaderResource("/pgm/feep.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN)
|
||||||
|
new TestData(getClassLoaderResource("/pgm/feep16.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN, 16 bits/sample)
|
||||||
|
new TestData(getClassLoaderResource("/pgm/house.l.pgm"), new Dimension(367, 241)), // P5 (PGM RAW)
|
||||||
|
new TestData(getClassLoaderResource("/ppm/lighthouse_rgb48.ppm"), new Dimension(768, 512)) // P6 (PPM RAW, 16 bits/sample)
|
||||||
|
// "/pfm/memorial.pfm" uses floating point
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-psd</artifactId>
|
<artifactId>imageio-psd</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
||||||
|
|||||||
+2
-1
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
@@ -55,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
|
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
|
||||||
profile = ICC_Profile.getInstance(stream);
|
profile = ColorProfiles.readProfileRaw(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -331,7 +331,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
|||||||
// Just stick to the raw type
|
// Just stick to the raw type
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally add the raw type
|
// Finally, add the raw type
|
||||||
types.add(rawType);
|
types.add(rawType);
|
||||||
|
|
||||||
return types.iterator();
|
return types.iterator();
|
||||||
|
|||||||
+2
-2
@@ -59,8 +59,8 @@ import java.util.Collections;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
||||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification<a>
|
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
|
||||||
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
|
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
|
||||||
*/
|
*/
|
||||||
public final class PSDImageWriter extends ImageWriterBase {
|
public final class PSDImageWriter extends ImageWriterBase {
|
||||||
|
|
||||||
|
|||||||
+39
-4
@@ -86,8 +86,6 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
|
|||||||
new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)),
|
new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)),
|
||||||
// 4 channel, CMYK, 16 bit samples
|
// 4 channel, CMYK, 16 bit samples
|
||||||
new TestData(getClassLoaderResource("/psd/cmyk_16bits.psd"), new Dimension(1000, 275)),
|
new TestData(getClassLoaderResource("/psd/cmyk_16bits.psd"), new Dimension(1000, 275)),
|
||||||
// 3 channel, RGB, 32 bit samples
|
|
||||||
new TestData(getClassLoaderResource("/psd/32bit5x5.psd"), new Dimension(5, 5)),
|
|
||||||
// 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB)
|
// 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB)
|
||||||
new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)),
|
new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)),
|
||||||
// From http://telegraphics.com.au/svn/psdparse/trunk/psd/
|
// From http://telegraphics.com.au/svn/psdparse/trunk/psd/
|
||||||
@@ -104,11 +102,48 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
|
|||||||
new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)),
|
new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)),
|
||||||
new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)),
|
new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)),
|
||||||
// CMYK, uncompressed + contains some uncommon MeSa (instead of 8BIM) resource blocks
|
// CMYK, uncompressed + contains some uncommon MeSa (instead of 8BIM) resource blocks
|
||||||
new TestData(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"), new Dimension(400, 191))
|
new TestData(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"), new Dimension(400, 191)),
|
||||||
|
// 3 channel, RGB, 32 bit samples
|
||||||
|
new TestData(getClassLoaderResource("/psd/32bit5x5.psd"), new Dimension(5, 5))
|
||||||
// TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
|
// TODO: Need more recent ZIP compressed PSD files from CS2/CS3+
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
|
||||||
|
return Arrays.asList(
|
||||||
|
// 5 channel, RGB
|
||||||
|
new TestData(getClassLoaderResource("/psd/photoshopping.psd"), new Dimension(300, 225)),
|
||||||
|
// 1 channel, gray, 8 bit samples
|
||||||
|
new TestData(getClassLoaderResource("/psd/buttons.psd"), new Dimension(20, 20)),
|
||||||
|
// 3 channel RGB, "no composite layer"
|
||||||
|
new TestData(getClassLoaderResource("/psd/jugware-icon.psd"), new Dimension(128, 128)),
|
||||||
|
// 3 channel RGB, old data, no layer info/mask
|
||||||
|
new TestData(getClassLoaderResource("/psd/MARBLES.PSD"), new Dimension(1419, 1001)),
|
||||||
|
// 1 channel, indexed color
|
||||||
|
new TestData(getClassLoaderResource("/psd/coral_fish.psd"), new Dimension(800, 800)),
|
||||||
|
// 1 channel, bitmap, 1 bit samples
|
||||||
|
new TestData(getClassLoaderResource("/psd/test_bitmap.psd"), new Dimension(710, 512)),
|
||||||
|
// 1 channel, gray, 16 bit samples
|
||||||
|
new TestData(getClassLoaderResource("/psd/test_gray16.psd"), new Dimension(710, 512)),
|
||||||
|
// 3 channel, RGB, 8 bit samples ("Large Document Format" aka PSB)
|
||||||
|
new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)),
|
||||||
|
// From http://telegraphics.com.au/svn/psdparse/trunk/psd/
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq.psd"), new Dimension(341, 512)),
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq_ind.psd"), new Dimension(341, 512)),
|
||||||
|
// Contains a shorter than normal PrintFlags chunk
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq-2.5.psd"), new Dimension(341, 512)),
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq-3.0.psd"), new Dimension(341, 512)),
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq-5.5.psd"), new Dimension(341, 512)),
|
||||||
|
new TestData(getClassLoaderResource("/psd/adobehq-7.0.psd"), new Dimension(341, 512)),
|
||||||
|
// From https://github.com/kmike/psd-tools/tree/master/tests/psd_files
|
||||||
|
new TestData(getClassLoaderResource("/psd/masks2.psd"), new Dimension(640, 1136)),
|
||||||
|
// RGB, multiple alpha channels, no transparency
|
||||||
|
new TestData(getClassLoaderResource("/psd/rgb-multichannel-no-transparency.psd"), new Dimension(100, 100)),
|
||||||
|
new TestData(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("psd");
|
return Collections.singletonList("psd");
|
||||||
@@ -472,7 +507,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
|
|||||||
public void testMultiChannelNoTransparencyPSB() throws IOException {
|
public void testMultiChannelNoTransparencyPSB() throws IOException {
|
||||||
PSDImageReader imageReader = createReader();
|
PSDImageReader imageReader = createReader();
|
||||||
|
|
||||||
// The following PSB is RGB, has 4 channels (1 alpha/auxillary channel), but should be treated as opaque
|
// The following PSB is RGB, has 4 channels (1 alpha/auxiliary channel), but should be treated as opaque
|
||||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"))) {
|
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psb/rgb-multichannel-no-transparency.psb"))) {
|
||||||
imageReader.setInput(stream);
|
imageReader.setInput(stream);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-reference</artifactId>
|
<artifactId>imageio-reference</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-sgi</artifactId>
|
<artifactId>imageio-sgi</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||||
|
|||||||
+25
-3
@@ -59,7 +59,7 @@ public final class SGIImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private SGIHeader header;
|
private SGIHeader header;
|
||||||
|
|
||||||
protected SGIImageReader(final ImageReaderSpi provider) {
|
SGIImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,9 +88,31 @@ public final class SGIImageReader 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<>();
|
||||||
|
|
||||||
|
int channels = header.getChannels();
|
||||||
|
|
||||||
|
switch (header.getBytesPerPixel()) {
|
||||||
|
case 1:
|
||||||
|
if (channels == 1) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
|
||||||
|
}
|
||||||
|
else if (channels == 3) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||||
|
}
|
||||||
|
else if (channels == 4) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (channels == 1) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement
|
|
||||||
specifiers.add(rawType);
|
specifiers.add(rawType);
|
||||||
|
|
||||||
return specifiers.iterator();
|
return specifiers.iterator();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-tga</artifactId>
|
<artifactId>imageio-tga</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||||
|
|||||||
+110
-47
@@ -96,10 +96,18 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||||
|
|
||||||
// TODO: Implement
|
|
||||||
specifiers.add(rawType);
|
specifiers.add(rawType);
|
||||||
|
|
||||||
|
if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_RGB) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||||
|
}
|
||||||
|
else if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_ARGB) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
|
||||||
|
}
|
||||||
|
else if (rawType.getBufferedImageType() == BufferedImage.TYPE_INT_ARGB_PRE) {
|
||||||
|
specifiers.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||||
|
}
|
||||||
|
|
||||||
return specifiers.iterator();
|
return specifiers.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +124,14 @@ 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(8, DataBuffer.TYPE_BYTE);
|
switch (header.getPixelDepth()) {
|
||||||
|
case 8:
|
||||||
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
case 16:
|
||||||
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY);
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown pixel depth for monochrome: " + header.getPixelDepth());
|
||||||
|
}
|
||||||
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);
|
||||||
@@ -135,12 +150,14 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
case 24:
|
case 24:
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
case 32:
|
case 32:
|
||||||
// 4BYTE_BGRX...
|
// NOTE: We'll read using little endian byte order, thus the file layout is BGRA/BGRx
|
||||||
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
|
if (hasAlpha) {
|
||||||
// if hasAlpha is false
|
return ImageTypeSpecifier.createFromBufferedImageType(isAlphaPremultiplied ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB);
|
||||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
|
}
|
||||||
|
|
||||||
|
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
throw new IIOException("Unknown pixel depth for true color: " + header.getPixelDepth());
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown image type: " + header.getImageType());
|
throw new IIOException("Unknown image type: " + header.getImageType());
|
||||||
@@ -187,20 +204,26 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
input = imageInput;
|
input = imageInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pixelDepth = header.getPixelDepth();
|
||||||
|
boolean flipped = isOriginLowerLeft(header.getOrigin());
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getPixelDepth()) {
|
switch (pixelDepth) {
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
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, flipped, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
readRowUShort(input, height, srcRegion, flipped, xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
int[] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowInt(input, height, srcRegion, flipped, xSub, ySub, rowDataInt, destRaster, clippedRow, y);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100f * y / height);
|
processImageProgress(100f * y / height);
|
||||||
@@ -220,10 +243,26 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowByte(final DataInput input, int height, Rectangle srcRegion, int origin, int xSub, int ySub,
|
private boolean isOriginLowerLeft(final int origin) throws IIOException {
|
||||||
|
switch (origin) {
|
||||||
|
case TGA.ORIGIN_LOWER_LEFT:
|
||||||
|
return true;
|
||||||
|
case TGA.ORIGIN_UPPER_LEFT:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
// Other orientations are not supported
|
||||||
|
throw new IIOException("Unsupported origin: " + origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readRowByte(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub,
|
||||||
byte[] rowDataByte, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
byte[] rowDataByte, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = flip ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataByte.length);
|
input.skipBytes(rowDataByte.length);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -244,19 +283,7 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (origin) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
case TGA.ORIGIN_LOWER_LEFT:
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
break;
|
|
||||||
case TGA.ORIGIN_UPPER_LEFT:
|
|
||||||
dstY = y / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unsupported origin: " + origin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeAlpha32(final byte[] rowData) {
|
private void removeAlpha32(final byte[] rowData) {
|
||||||
@@ -265,10 +292,14 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowUShort(final DataInput input, int height, Rectangle srcRegion, int origin, int xSub, int ySub,
|
private void readRowUShort(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub,
|
||||||
short[] rowDataUShort, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
short[] rowDataUShort, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = flip ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataUShort.length * 2);
|
input.skipBytes(rowDataUShort.length * 2);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -283,19 +314,32 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (origin) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
case TGA.ORIGIN_LOWER_LEFT:
|
}
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
private void readRowInt(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub,
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
int[] rowDataInt, WritableRaster destChannel, Raster srcChannel, int y) throws IOException {
|
||||||
break;
|
// Flip into position?
|
||||||
case TGA.ORIGIN_UPPER_LEFT:
|
int srcY = flip ? height - 1 - y : y;
|
||||||
dstY = y / ySub;
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
break;
|
// If subsampled or outside source region, skip entire row
|
||||||
default:
|
if (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
throw new IIOException("Unsupported origin: " + origin);
|
input.skipBytes(rowDataInt.length * 4);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readFully(input, rowDataInt);
|
||||||
|
|
||||||
|
// Subsample horizontal
|
||||||
|
if (xSub != 1) {
|
||||||
|
for (int x = srcRegion.x / xSub; x < ((srcRegion.x + srcRegion.width) / xSub); x++) {
|
||||||
|
rowDataInt[x] = rowDataInt[x * xSub];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Candidate util method
|
// TODO: Candidate util method
|
||||||
@@ -311,6 +355,19 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Candidate util method
|
||||||
|
private static void readFully(final DataInput input, final int[] ints) throws IOException {
|
||||||
|
if (input instanceof ImageInputStream) {
|
||||||
|
// Optimization for ImageInputStreams, read all in one go
|
||||||
|
((ImageInputStream) input).readFully(ints, 0, ints.length);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int i = 0; i < ints.length; i++) {
|
||||||
|
ints[i] = input.readInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
|
private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
|
||||||
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
|
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
|
||||||
&& xSub == 1
|
&& xSub == 1
|
||||||
@@ -446,20 +503,26 @@ final class TGAImageReader extends ImageReaderBase {
|
|||||||
// Thumbnail is always stored non-compressed, no need for RLE support
|
// Thumbnail is always stored non-compressed, no need for RLE support
|
||||||
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
||||||
|
|
||||||
|
int pixelDepth = header.getPixelDepth();
|
||||||
|
boolean flipped = isOriginLowerLeft(header.getOrigin());
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getPixelDepth()) {
|
switch (pixelDepth) {
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
case 32:
|
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y);
|
readRowByte(imageInput, height, srcRegion, flipped, 1, 1, rowDataByte, destRaster, rowRaster, y);
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y);
|
readRowUShort(imageInput, height, srcRegion, flipped, 1, 1, rowDataUShort, destRaster, rowRaster, y);
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
int[] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowInt(imageInput, height, srcRegion, flipped, 1, 1, rowDataInt, destRaster, rowRaster, y);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
processThumbnailProgress(100f * y / height);
|
processThumbnailProgress(100f * y / height);
|
||||||
|
|||||||
+5
-2
@@ -193,10 +193,13 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
bitsPerSample.setAttribute("value", createListValue(1, "8"));
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
if (header.getImageType() == TGA.IMAGETYPE_MONOCHROME || header.getImageType() == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||||
|
bitsPerSample.setAttribute("value", "16");
|
||||||
|
}
|
||||||
|
else if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||||
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
+4
-1
@@ -91,7 +91,10 @@ public class TGAImageReaderTest extends ImageReaderAbstractTest<TGAImageReader>
|
|||||||
new TestData(getClassLoaderResource("/tga/XING_T24.TGA"), new Dimension(240, 164)), // Uncompressed 24 bit BGR top/down
|
new TestData(getClassLoaderResource("/tga/XING_T24.TGA"), new Dimension(240, 164)), // Uncompressed 24 bit BGR top/down
|
||||||
new TestData(getClassLoaderResource("/tga/XING_T32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRA top/down
|
new TestData(getClassLoaderResource("/tga/XING_T32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRA top/down
|
||||||
|
|
||||||
new TestData(getClassLoaderResource("/tga/autodesk-3dsmax-extsize494.tga"), new Dimension(440, 200)) // RLE compressed 32 bit BGRA bottom/up
|
new TestData(getClassLoaderResource("/tga/autodesk-3dsmax-extsize494.tga"), new Dimension(440, 200)), // RLE compressed 32 bit BGRA bottom/up
|
||||||
|
|
||||||
|
new TestData(getClassLoaderResource("/tga/monochrome16_top_left.tga"), new Dimension(64, 64)), // Uncompressed 16 bit monochrome
|
||||||
|
new TestData(getClassLoaderResource("/tga/monochrome16_top_left_rle.tga"), new Dimension(64, 64)) // RLE compressed 16 bit monochrome
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-thumbsdb</artifactId>
|
<artifactId>imageio-thumbsdb</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?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.8.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>imageio-tiff-jai-interop</artifactId>
|
||||||
|
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
|
||||||
|
<description>
|
||||||
|
Test TIFF plugin and JAI TIFF plugin Metadata interoperability
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.imageio.tiff.jaiinterop</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.jai-imageio</groupId>
|
||||||
|
<artifactId>jai-imageio-core</artifactId>
|
||||||
|
<version>1.4.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<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>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-metadata</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-tiff</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.tiff.jaiinterop;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests our TIFFImageMetadata works with JAI TIFFImageWriter.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TIFFImageReaderJDKJPEGInteroperabilityTest.java,v 1.0 08.05.12 15:25 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class TIFFImageMetadataJAInteroperabilityTest {
|
||||||
|
private static final String JAI_TIFF_PROVIDER_CLASS_NAME = "com.github.jaiimageio.impl.plugins.tiff.TIFFImageWriterSpi";
|
||||||
|
|
||||||
|
private ImageWriter createImageWriter() {
|
||||||
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("TIFF");
|
||||||
|
|
||||||
|
while (writers.hasNext()) {
|
||||||
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
|
if (JAI_TIFF_PROVIDER_CLASS_NAME.equals(writer.getOriginatingProvider().getClass().getName())) {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError("Expected Spi not found (dependency issue?): " + JAI_TIFF_PROVIDER_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRationalNeedsDenominator() {
|
||||||
|
// Set the resolution to 200 dpi
|
||||||
|
IIOMetadata ourMetadata = new TIFFImageMetadata(Arrays.asList(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, 2), // Unit DPI (default)
|
||||||
|
new TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(200)),
|
||||||
|
new TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(200))));
|
||||||
|
|
||||||
|
ImageTypeSpecifier type = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
ImageWriter writer = createImageWriter();
|
||||||
|
IIOMetadata converted = writer.convertImageMetadata(ourMetadata, type, null);
|
||||||
|
|
||||||
|
assertNotNull(converted);
|
||||||
|
|
||||||
|
// Make sure we have x/y resolution in converted metadata
|
||||||
|
IIOMetadataNode standardTree = (IIOMetadataNode) converted.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||||
|
String horizontalPixelSize = ((IIOMetadataNode) standardTree.getElementsByTagName("HorizontalPixelSize").item(0)).getAttribute("value");
|
||||||
|
String verticalPixelSize = ((IIOMetadataNode) standardTree.getElementsByTagName("VerticalPixelSize").item(0)).getAttribute("value");
|
||||||
|
|
||||||
|
// For some reason this is *pixel size* in *mm*...
|
||||||
|
String expected = String.valueOf(2.54 / 200 * 10);
|
||||||
|
assertEquals(expected, horizontalPixelSize);
|
||||||
|
assertEquals(expected, verticalPixelSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.jpms.module.name>com.twelvemonkeys.imageio.jdkinterop</project.jpms.module.name>
|
<project.jpms.module.name>com.twelvemonkeys.imageio.tiff.jdkinterop</project.jpms.module.name>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
+12
-11
@@ -28,25 +28,26 @@
|
|||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.jpeg.jdkinterop;
|
package com.twelvemonkeys.imageio.plugins.tiff.jdkinterop;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader;
|
import static org.junit.Assert.fail;
|
||||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi;
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
|
||||||
|
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests our TIFFImageReader delegating to the JDK JPEGImageReader.
|
* Tests our TIFFImageReader delegating to the JDK JPEGImageReader.
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0-SNAPSHOT</version>
|
<version>3.8.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-tiff</artifactId>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BigTIFFImageWriterSpi
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BigTIFFImageWriterSpi.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class BigTIFFImageWriterSpi extends ImageWriterSpiBase {
|
||||||
|
// TODO: Implement canEncodeImage better
|
||||||
|
|
||||||
|
public BigTIFFImageWriterSpi() {
|
||||||
|
super(new BigTIFFProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canEncodeImage(final ImageTypeSpecifier type) {
|
||||||
|
// TODO: Test bit depths compatibility
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TIFFImageWriter createWriterInstance(final Object extension) {
|
||||||
|
return new TIFFImageWriter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
|
return "BigTIFF image writer";
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -50,10 +50,10 @@ final class BigTIFFProviderInfo extends ReaderWriterProviderInfo {
|
|||||||
},
|
},
|
||||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
|
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
|
||||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
|
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
|
||||||
null,
|
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
|
||||||
null,
|
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"},
|
||||||
false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
|
false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
|
||||||
true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
|
true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-52
@@ -58,8 +58,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private final boolean optionUncompressed;
|
private final boolean optionUncompressed;
|
||||||
private final boolean optionByteAligned;
|
private final boolean optionByteAligned;
|
||||||
|
|
||||||
// Need to take fill order into account (?) (use flip table?)
|
|
||||||
private final int fillOrder;
|
|
||||||
private final int type;
|
private final int type;
|
||||||
|
|
||||||
private int decodedLength;
|
private int decodedLength;
|
||||||
@@ -81,12 +79,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
* @param columns the number of columns in the stream.
|
* @param columns the number of columns in the stream.
|
||||||
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||||
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
||||||
* @param fillOrder fillOrder, must be {@code FILL_LEFT_TO_RIGHT} or
|
|
||||||
* {@code FILL_RIGHT_TO_LEFT}.
|
|
||||||
* @param options CCITT T.4 or T.6 options.
|
* @param options CCITT T.4 or T.6 options.
|
||||||
* @param byteAligned enable byte alignment used in PDF files (EncodedByteAlign).
|
* @param byteAligned enable byte alignment used in PDF files (EncodedByteAlign).
|
||||||
*/
|
*/
|
||||||
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 long options, final boolean byteAligned) {
|
final long options, final boolean byteAligned) {
|
||||||
super(Validate.notNull(stream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
@@ -95,10 +91,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
|
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
|
||||||
type == TIFFExtension.COMPRESSION_CCITT_T6,
|
type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||||
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s");
|
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"
|
|
||||||
);
|
|
||||||
|
|
||||||
// We know this is only used for b/w (1 bit)
|
// We know this is only used for b/w (1 bit)
|
||||||
decodedRow = new byte[(columns + 7) / 8];
|
decodedRow = new byte[(columns + 7) / 8];
|
||||||
@@ -140,54 +132,63 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
* @param columns the number of columns in the stream.
|
* @param columns the number of columns in the stream.
|
||||||
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
* @param type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||||
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
* {@code COMPRESSION_CCITT_T4} or {@code COMPRESSION_CCITT_T6}.
|
||||||
* @param fillOrder fillOrder, must be {@code FILL_LEFT_TO_RIGHT} or
|
|
||||||
* {@code FILL_RIGHT_TO_LEFT}.
|
|
||||||
* @param options CCITT T.4 or T.6 options.
|
* @param options CCITT T.4 or T.6 options.
|
||||||
*/
|
*/
|
||||||
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 long options) {
|
final long options) {
|
||||||
this(stream, columns, type, fillOrder, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
this(stream, columns, type, options, type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int findCompressionType(final int type, final InputStream in) throws IOException {
|
static int findCompressionType(final int encodedType, final InputStream stream) throws IOException {
|
||||||
// Discover possible incorrect type, revert to RLE
|
// Discover possible incorrect compression type, revert to RLE if no EOLs found
|
||||||
if (type == TIFFExtension.COMPRESSION_CCITT_T4 && in.markSupported()) {
|
if (encodedType == TIFFExtension.COMPRESSION_CCITT_T4 && stream.markSupported()) {
|
||||||
byte[] streamData = new byte[32];
|
int limit = 512;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
in.mark(streamData.length);
|
stream.mark(limit);
|
||||||
|
int first = stream.read();
|
||||||
|
int second = stream.read();
|
||||||
|
|
||||||
int offset = 0;
|
if (second == -1) {
|
||||||
while (offset < streamData.length) {
|
// stream to short
|
||||||
int read = in.read(streamData, offset, streamData.length - offset);
|
return encodedType;
|
||||||
if (read <= 0) {
|
}
|
||||||
break;
|
else if (first == 0 && (((byte) second) >> 4 == 1 || ((byte) second) == 1)) {
|
||||||
|
// correct, starts with EOL or byte aligned EOL
|
||||||
|
return encodedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
short b = (short) (((((byte) first) << 8) + ((byte) second)) >> 4);
|
||||||
|
int limitBits = limit * 8;
|
||||||
|
int read = second;
|
||||||
|
byte streamByte = (byte) read;
|
||||||
|
|
||||||
|
for (int i = 12; i < limitBits; i++) {
|
||||||
|
if (i % 8 == 0) {
|
||||||
|
read = stream.read();
|
||||||
|
if (read == -1) {
|
||||||
|
// no EOL before stream end
|
||||||
|
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += read;
|
b = (short) ((b << 1) + ((streamByte >> (7 - (i % 8))) & 0x01));
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
in.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamData[0] != 0 || (streamData[1] >> 4 != 1 && streamData[1] != 1)) {
|
|
||||||
// Leading EOL (0b000000000001) not found, search further and try RLE if not found
|
|
||||||
int numBits = streamData.length * 8;
|
|
||||||
short b = (short) (((streamData[0] << 8) + streamData[1]) >> 4);
|
|
||||||
for (int i = 12; i < numBits; i++) {
|
|
||||||
b = (short) ((b << 1) + ((streamData[(i / 8)] >> (7 - (i % 8))) & 0x01));
|
|
||||||
|
|
||||||
if ((b & 0xFFF) == 1) {
|
if ((b & 0xFFF) == 1) {
|
||||||
|
// found EOL
|
||||||
return TIFFExtension.COMPRESSION_CCITT_T4;
|
return TIFFExtension.COMPRESSION_CCITT_T4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// no EOL till limit
|
||||||
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
|
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
stream.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return type;
|
return encodedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetch() throws IOException {
|
private void fetch() throws IOException {
|
||||||
@@ -197,13 +198,17 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
try {
|
try {
|
||||||
decodeRow();
|
decodeRow();
|
||||||
}
|
}
|
||||||
|
catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
// Mask the AIOOBE as an IOException
|
||||||
|
throw new IOException("Malformed CCITT stream", e);
|
||||||
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..otherwise, just let client code try to read past the
|
// ...otherwise, just let client code try to read past the
|
||||||
// end of stream
|
// end of stream
|
||||||
decodedLength = -1;
|
decodedLength = -1;
|
||||||
}
|
}
|
||||||
@@ -468,7 +473,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
int bufferPos = -1;
|
int bufferPos = -1;
|
||||||
|
|
||||||
private boolean readBit() throws IOException {
|
private boolean readBit() throws IOException {
|
||||||
if (bufferPos < 0 || bufferPos > 7) {
|
if (bufferPos > 7 || bufferPos < 0) {
|
||||||
buffer = in.read();
|
buffer = in.read();
|
||||||
|
|
||||||
if (buffer == -1) {
|
if (buffer == -1) {
|
||||||
@@ -478,21 +483,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
bufferPos = 0;
|
bufferPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSet;
|
boolean isSet = (buffer & 0x80) != 0;
|
||||||
|
buffer <<= 1;
|
||||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
|
||||||
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufferPos++;
|
bufferPos++;
|
||||||
|
|
||||||
if (bufferPos > 7) {
|
|
||||||
bufferPos = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSet;
|
return isSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-21
@@ -30,6 +30,22 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader.guessPhotometricInterpretation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
@@ -38,18 +54,6 @@ import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
|||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageMetadata.
|
* TIFFImageMetadata.
|
||||||
@@ -84,7 +88,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
* or {@link #mergeTree(String, Node)} methods.
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
*/
|
*/
|
||||||
public TIFFImageMetadata(final Directory ifd) {
|
public TIFFImageMetadata(final Directory ifd) {
|
||||||
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
super(true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFImageMetadataFormat.class.getName(), null, null);
|
||||||
this.ifd = Validate.notNull(ifd, "IFD");
|
this.ifd = Validate.notNull(ifd, "IFD");
|
||||||
this.original = ifd;
|
this.original = ifd;
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
* {@link #setFromTree(String, Node)}
|
* {@link #setFromTree(String, Node)}
|
||||||
* or {@link #mergeTree(String, Node)} methods.
|
* or {@link #mergeTree(String, Node)} methods.
|
||||||
*/
|
*/
|
||||||
public TIFFImageMetadata(final Collection<Entry> entries) {
|
public TIFFImageMetadata(final Collection<? extends Entry> entries) {
|
||||||
this(new IFD(entries));
|
this(new IFD(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +196,12 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
elementNode.setAttribute("value", String.valueOf((Short) value & 0xFFFF));
|
elementNode.setAttribute("value", String.valueOf((Short) value & 0xFFFF));
|
||||||
}
|
}
|
||||||
else if (unsigned && value instanceof Integer) {
|
else if (unsigned && value instanceof Integer) {
|
||||||
elementNode.setAttribute("value", String.valueOf((Integer) value & 0xFFFFFFFFl));
|
elementNode.setAttribute("value", String.valueOf((Integer) value & 0xFFFFFFFFL));
|
||||||
|
}
|
||||||
|
else if (value instanceof Rational) {
|
||||||
|
// For compatibility with JAI format, we need denominator
|
||||||
|
String rational = String.valueOf(value);
|
||||||
|
elementNode.setAttribute("value", rational.indexOf('/') < 0 && !"NaN".equals(rational) ? rational + "/1" : rational);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
elementNode.setAttribute("value", String.valueOf(value));
|
elementNode.setAttribute("value", String.valueOf(value));
|
||||||
@@ -353,8 +362,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
||||||
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
int photometricValue = getPhotometricInterpretationWithFallback(); // No default for this tag!
|
||||||
int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
|
|
||||||
int numChannelsValue = getSamplesPerPixelWithFallback();
|
int numChannelsValue = getSamplesPerPixelWithFallback();
|
||||||
|
|
||||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||||
@@ -445,6 +453,13 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
return chroma;
|
return chroma;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getPhotometricInterpretationWithFallback() {
|
||||||
|
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
|
|
||||||
|
return photometricTag != null ? getValueAsInt(photometricTag)
|
||||||
|
: guessPhotometricInterpretation(getCompression(), getSamplesPerPixelWithFallback(), ifd.getEntryById(TIFF.TAG_EXTRA_SAMPLES), ifd.getEntryById(TIFF.TAG_COLOR_MAP));
|
||||||
|
}
|
||||||
|
|
||||||
private int getSamplesPerPixelWithFallback() {
|
private int getSamplesPerPixelWithFallback() {
|
||||||
// SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure
|
// SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure
|
||||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
@@ -455,15 +470,19 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
|||||||
: bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1;
|
: bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getCompression() {
|
||||||
|
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
||||||
|
return compressionTag == null
|
||||||
|
? TIFFBaseline.COMPRESSION_NONE
|
||||||
|
: getValueAsInt(compressionTag);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardCompressionNode() {
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
||||||
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
|
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
|
||||||
|
|
||||||
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
int compressionValue = getCompression();
|
||||||
int compressionValue = compressionTag == null
|
|
||||||
? TIFFBaseline.COMPRESSION_NONE
|
|
||||||
: 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) {
|
||||||
|
|||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016, 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 of the copyright holder 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 HOLDER 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.tiff;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TIFFImageMetadataFormat.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: TIFFImageMetadataFormat.java,v 1.0 17/04/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class TIFFImageMetadataFormat extends IIOMetadataFormatImpl {
|
||||||
|
private static final TIFFImageMetadataFormat INSTANCE = new TIFFImageMetadataFormat();
|
||||||
|
|
||||||
|
// We'll reuse the metadata formats defined for JAI
|
||||||
|
public static final String SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_image_1.0";
|
||||||
|
|
||||||
|
public TIFFImageMetadataFormat() {
|
||||||
|
super(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
|
||||||
|
|
||||||
|
// TODO: Implement!
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TIFFImageMetadataFormat getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-49
@@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
|
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
|
||||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
|
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
|
||||||
|
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
@@ -164,6 +165,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private CompoundDirectory IFDs;
|
private CompoundDirectory IFDs;
|
||||||
private Directory currentIFD;
|
private Directory currentIFD;
|
||||||
|
private int overrideCCITTCompression = -1;
|
||||||
|
|
||||||
TIFFImageReader(final ImageReaderSpi provider) {
|
TIFFImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
@@ -173,6 +175,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
IFDs = null;
|
IFDs = null;
|
||||||
currentIFD = null;
|
currentIFD = null;
|
||||||
|
overrideCCITTCompression = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readMetadata() throws IOException {
|
private void readMetadata() throws IOException {
|
||||||
@@ -377,6 +380,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
readMetadata();
|
readMetadata();
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
currentIFD = IFDs.getDirectory(imageIndex);
|
currentIFD = IFDs.getDirectory(imageIndex);
|
||||||
|
overrideCCITTCompression = -1; // Reset override for next image
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -699,40 +703,49 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
private int getPhotometricInterpretationWithFallback() throws IIOException {
|
private int getPhotometricInterpretationWithFallback() throws IIOException {
|
||||||
// PhotometricInterpretation is a required tag, but as it can be guessed this does a fallback that is similar to JAI ImageIO.
|
// PhotometricInterpretation is a required tag, but as it can be guessed this does a fallback that is similar to JAI ImageIO.
|
||||||
int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
|
int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
|
||||||
|
|
||||||
if (interpretation == -1) {
|
if (interpretation == -1) {
|
||||||
int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
||||||
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
|
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
|
||||||
Entry extraSamplesEntry = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
|
Entry extraSamples = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
|
||||||
int extraSamples = extraSamplesEntry == null ? 0 : extraSamplesEntry.valueCount();
|
Entry colorMap = currentIFD.getEntryById(TIFF.TAG_COLOR_MAP);
|
||||||
|
|
||||||
|
interpretation = guessPhotometricInterpretation(compression, samplesPerPixel, extraSamples, colorMap);
|
||||||
|
|
||||||
if (compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE
|
|
||||||
|| compression == TIFFExtension.COMPRESSION_CCITT_T4
|
|
||||||
|| compression == TIFFExtension.COMPRESSION_CCITT_T6) {
|
|
||||||
interpretation = TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
|
|
||||||
}
|
|
||||||
else if (currentIFD.getEntryById(TIFF.TAG_COLOR_MAP) != null) {
|
|
||||||
interpretation = TIFFBaseline.PHOTOMETRIC_PALETTE;
|
|
||||||
}
|
|
||||||
else if ((samplesPerPixel - extraSamples) == 3) {
|
|
||||||
if (compression == TIFFExtension.COMPRESSION_JPEG
|
|
||||||
|| compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
|
|
||||||
interpretation = TIFFExtension.PHOTOMETRIC_YCBCR;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
interpretation = TIFFBaseline.PHOTOMETRIC_RGB;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ((samplesPerPixel - extraSamples) == 4) {
|
|
||||||
interpretation = TIFFExtension.PHOTOMETRIC_SEPARATED;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
interpretation = TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
|
|
||||||
}
|
|
||||||
processWarningOccurred("Missing PhotometricInterpretation, determining fallback: " + interpretation);
|
processWarningOccurred("Missing PhotometricInterpretation, determining fallback: " + interpretation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return interpretation;
|
return interpretation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int guessPhotometricInterpretation(int compression, int samplesPerPixel, Entry extraSamples, Entry colorMap) {
|
||||||
|
int extraSamplesCount = extraSamples == null ? 0 : extraSamples.valueCount();
|
||||||
|
|
||||||
|
if (compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE
|
||||||
|
|| compression == TIFFExtension.COMPRESSION_CCITT_T4
|
||||||
|
|| compression == TIFFExtension.COMPRESSION_CCITT_T6) {
|
||||||
|
return TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
|
||||||
|
}
|
||||||
|
else if (colorMap != null) {
|
||||||
|
return TIFFBaseline.PHOTOMETRIC_PALETTE;
|
||||||
|
}
|
||||||
|
else if ((samplesPerPixel - extraSamplesCount) == 3) {
|
||||||
|
if (compression == TIFFExtension.COMPRESSION_JPEG
|
||||||
|
|| compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
|
||||||
|
return TIFFExtension.PHOTOMETRIC_YCBCR;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return TIFFBaseline.PHOTOMETRIC_RGB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((samplesPerPixel - extraSamplesCount) == 4) {
|
||||||
|
return TIFFExtension.PHOTOMETRIC_SEPARATED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int getOpaqueSamplesPerPixel(final int photometricInterpretation) throws IIOException {
|
private int getOpaqueSamplesPerPixel(final int photometricInterpretation) throws IIOException {
|
||||||
switch (photometricInterpretation) {
|
switch (photometricInterpretation) {
|
||||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||||
@@ -1040,9 +1053,10 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// General uncompressed/compressed reading
|
// General uncompressed/compressed reading
|
||||||
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
|
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
|
||||||
|
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, TIFFBaseline.FILL_LEFT_TO_RIGHT);
|
||||||
int bitsPerSample = getBitsPerSample();
|
int bitsPerSample = getBitsPerSample();
|
||||||
boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0 || bitsPerSample == 6;
|
boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0 || bitsPerSample == 6;
|
||||||
boolean needsAdapter = compression != TIFFBaseline.COMPRESSION_NONE
|
boolean needsAdapter = compression != TIFFBaseline.COMPRESSION_NONE || fillOrder != TIFFBaseline.FILL_LEFT_TO_RIGHT
|
||||||
|| interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || needsBitPadding;
|
|| interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || needsBitPadding;
|
||||||
|
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
for (int y = 0; y < tilesDown; y++) {
|
||||||
@@ -1059,7 +1073,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
DataInput input;
|
DataInput input;
|
||||||
if (!needsAdapter) {
|
if (!needsAdapter) {
|
||||||
// No need for transformation, fast forward
|
// No need for transformation, fast-forward
|
||||||
input = imageInput;
|
input = imageInput;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1067,6 +1081,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
? createStreamAdapter(imageInput, stripTileByteCounts[i])
|
? createStreamAdapter(imageInput, stripTileByteCounts[i])
|
||||||
: createStreamAdapter(imageInput);
|
: createStreamAdapter(imageInput);
|
||||||
|
|
||||||
|
adapter = createFillOrderStream(fillOrder, adapter);
|
||||||
adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter);
|
adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter);
|
||||||
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, bitsPerSample, adapter, imageInput.getByteOrder());
|
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, bitsPerSample, adapter, imageInput.getByteOrder());
|
||||||
|
|
||||||
@@ -2136,7 +2151,8 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
&& (referenceBW == null || Arrays.equals(referenceBW, REFERENCE_BLACK_WHITE_YCC_DEFAULT))) {
|
&& (referenceBW == null || Arrays.equals(referenceBW, REFERENCE_BLACK_WHITE_YCC_DEFAULT))) {
|
||||||
// Fast, default conversion
|
// Fast, default conversion
|
||||||
for (int i = 0; i < data.length; i += 3) {
|
for (int i = 0; i < data.length; i += 3) {
|
||||||
YCbCrConverter.convertYCbCr2RGB(data, data, i);
|
// TODO: The default is likely neither JPEG or rec 601, as the reference B/W doesn't match...
|
||||||
|
YCbCrConverter.convertJPEGYCbCr2RGB(data, data, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -2310,26 +2326,28 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
return (short) Math.max(0, Math.min(0xffff, val));
|
return (short) Math.max(0, Math.min(0xffff, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream createDecompressorStream(final int compression, final int width, final int bands, final InputStream stream) throws IOException {
|
private InputStream createDecompressorStream(final int compression, final int width, final int bands, InputStream stream) throws IOException {
|
||||||
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1);
|
|
||||||
|
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case TIFFBaseline.COMPRESSION_NONE:
|
case TIFFBaseline.COMPRESSION_NONE:
|
||||||
return stream;
|
return stream;
|
||||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||||
return new DecoderStream(createFillOrderStream(fillOrder, stream), new PackBitsDecoder(), 256);
|
return new DecoderStream(stream, new PackBitsDecoder(), 256);
|
||||||
case TIFFExtension.COMPRESSION_LZW:
|
case TIFFExtension.COMPRESSION_LZW:
|
||||||
// NOTE: Needs large buffer for compatibility with certain encoders
|
// NOTE: Needs large buffer for compatibility with certain encoders
|
||||||
return new DecoderStream(createFillOrderStream(fillOrder, stream), LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), Math.max(width * bands, 4096));
|
return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), Math.max(width * bands, 4096));
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
// TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
// TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
||||||
case TIFFCustom.COMPRESSION_PIXTIFF_ZIP:
|
case TIFFCustom.COMPRESSION_PIXTIFF_ZIP:
|
||||||
return new InflaterInputStream(createFillOrderStream(fillOrder, stream), new Inflater(), 1024);
|
return new InflaterInputStream(stream, new Inflater(), 1024);
|
||||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
return new CCITTFaxDecoderStream(stream, width, findCCITTType(compression, stream), fillOrder, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
// TODO: Find a better way to test for incorrect CCITT type ONCE per IFD
|
||||||
|
if (overrideCCITTCompression == -1) {
|
||||||
|
overrideCCITTCompression = findCCITTType(compression, stream);
|
||||||
|
}
|
||||||
|
return new CCITTFaxDecoderStream(stream, width, overrideCCITTCompression, getCCITTOptions(compression), compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||||
}
|
}
|
||||||
@@ -2476,24 +2494,12 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICC_Profile getICCProfile() throws IOException {
|
private ICC_Profile getICCProfile() {
|
||||||
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
||||||
|
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
byte[] value = (byte[]) entry.getValue();
|
|
||||||
|
|
||||||
// Validate ICC profile size vs actual value size
|
|
||||||
int size = (value[0] & 0xff) << 24 | (value[1] & 0xff) << 16 | (value[2] & 0xff) << 8 | (value[3] & 0xff);
|
|
||||||
if (size < 0 || size > value.length) {
|
|
||||||
processWarningOccurred("Ignoring truncated ICC profile: Bad ICC profile size (" + size + ")");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// WEIRDNESS: Reading profile from InputStream is somehow more compatible
|
return ColorProfiles.createProfile((byte[]) entry.getValue());
|
||||||
// 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 e) {
|
catch (CMMException | IllegalArgumentException e) {
|
||||||
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
|
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
|
||||||
|
|||||||
+24
-24
@@ -32,6 +32,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.color.ColorProfiles;
|
||||||
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.tiff.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
@@ -63,6 +64,7 @@ import java.util.*;
|
|||||||
import java.util.zip.Deflater;
|
import java.util.zip.Deflater;
|
||||||
import java.util.zip.DeflaterOutputStream;
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadata.configureStreamByteOrder;
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadata.configureStreamByteOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,26 +204,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
long streamPosition = imageOutput.getStreamPosition();
|
long streamPosition = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
||||||
long stripOffset = streamPosition + 4 + ifdSize + 4;
|
long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize();
|
||||||
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
|
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
|
||||||
|
|
||||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||||
|
|
||||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
// If we have a previous IFD, update pointer
|
// If we have a previous IFD, update pointer
|
||||||
if (streamPosition > lastIFDPointerOffset) {
|
if (streamPosition > lastIFDPointerOffset) {
|
||||||
imageOutput.seek(lastIFDPointerOffset);
|
imageOutput.seek(lastIFDPointerOffset);
|
||||||
imageOutput.writeInt((int) ifdPointer);
|
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||||
imageOutput.seek(nextIFDPointerOffset);
|
imageOutput.seek(nextIFDPointerOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
imageOutput.writeInt(0); // Update next IFD pointer later
|
tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
imageOutput.writeInt(0); // Update current IFD pointer later
|
tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later
|
||||||
}
|
}
|
||||||
|
|
||||||
long stripOffset = imageOutput.getStreamPosition();
|
long stripOffset = imageOutput.getStreamPosition();
|
||||||
@@ -260,7 +262,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||||
|
|
||||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||||
|
|
||||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||||
|
|
||||||
@@ -268,10 +270,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset
|
// However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset
|
||||||
// TODO: Refactor writeIFD to take an offset
|
// TODO: Refactor writeIFD to take an offset
|
||||||
imageOutput.seek(lastIFDPointerOffset);
|
imageOutput.seek(lastIFDPointerOffset);
|
||||||
imageOutput.writeInt((int) ifdPointer);
|
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||||
imageOutput.seek(nextIFDPointerOffset);
|
imageOutput.seek(nextIFDPointerOffset);
|
||||||
|
|
||||||
imageOutput.writeInt(0); // Next IFD pointer updated later
|
tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextIFDPointerOffset;
|
return nextIFDPointerOffset;
|
||||||
@@ -737,8 +739,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
if (Arrays.asList(inData.getMetadataFormatNames()).contains(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
||||||
outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
outData.setFromTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
||||||
}
|
}
|
||||||
else if (inData.isStandardMetadataFormatSupported()) {
|
else if (inData.isStandardMetadataFormatSupported()) {
|
||||||
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
||||||
@@ -849,9 +851,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
else {
|
else {
|
||||||
entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numBands));
|
entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numBands));
|
||||||
|
|
||||||
// Note: Assuming sRGB to be the default RGB interpretation
|
// Embed ICC profile if we have one that:
|
||||||
|
// * is not sRGB (assuming sRGB to be the default RGB interpretation), and
|
||||||
|
// * is not gray scale (assuming photometric either BlackIsZero or WhiteIsZero)
|
||||||
ColorSpace colorSpace = colorModel.getColorSpace();
|
ColorSpace colorSpace = colorModel.getColorSpace();
|
||||||
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB()) {
|
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorProfiles.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
|
||||||
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
|
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -955,11 +959,15 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
configureStreamByteOrder(streamMetadata, imageOutput);
|
configureStreamByteOrder(streamMetadata, imageOutput);
|
||||||
|
|
||||||
writingSequence = true;
|
writingSequence = true;
|
||||||
sequenceTIFFWriter = new TIFFWriter();
|
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
|
||||||
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
|
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
|
||||||
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isBigTIFF() throws IOException {
|
||||||
|
return "bigtiff".equalsIgnoreCase(getFormatName());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
|
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||||
if (!writingSequence) {
|
if (!writingSequence) {
|
||||||
@@ -1015,8 +1023,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
BufferedImage original;
|
BufferedImage original;
|
||||||
// BufferedImage original = ImageIO.read(file);
|
// BufferedImage original = ImageIO.read(file);
|
||||||
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
|
try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) {
|
||||||
try {
|
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
|
||||||
|
|
||||||
if (!readers.hasNext()) {
|
if (!readers.hasNext()) {
|
||||||
@@ -1046,9 +1053,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
original = reader.read(0, param);
|
original = reader.read(0, param);
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("original: " + original);
|
System.err.println("original: " + original);
|
||||||
|
|
||||||
@@ -1084,12 +1088,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
// output.deleteOnExit();
|
// output.deleteOnExit();
|
||||||
|
|
||||||
System.err.println("output: " + output);
|
System.err.println("output: " + output);
|
||||||
TIFFImageWriter writer = new TIFFImageWriter(null);
|
TIFFImageWriter writer = new TIFFImageWriter(new TIFFImageWriterSpi());
|
||||||
// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
|
// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
|
||||||
// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
|
// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
|
||||||
ImageOutputStream stream = ImageIO.createImageOutputStream(output);
|
|
||||||
|
|
||||||
try {
|
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
|
||||||
writer.setOutput(stream);
|
writer.setOutput(stream);
|
||||||
|
|
||||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
@@ -1107,9 +1110,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
writer.write(null, new IIOImage(image, null, null), param);
|
writer.write(null, new IIOImage(image, null, null), param);
|
||||||
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
stream.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("output.length: " + output.length());
|
System.err.println("output.length: " + output.length());
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user