mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-18 00:00:03 -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**
|
||||
|
||||
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:
|
||||
**What is changed** Briefly describe the changes proposed in this pull request:
|
||||
|
||||
* Fixed rare exception happening in `x >= 42` case
|
||||
* Small optimization of `decompress()` method
|
||||
|
||||
+5
-3
@@ -1,9 +1,11 @@
|
||||
dist: trusty
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8 # Legacy
|
||||
- oraclejdk11 # LTS
|
||||
- oraclejdk15 # Latest
|
||||
- oraclejdk8 # LTS until Mar 2022
|
||||
- oraclejdk11 # LTS until Sep 2023
|
||||
- oraclejdk15 # out of support
|
||||
- oraclejdk16 # out of support
|
||||
- oraclejdk17 # LTS until Sep 2026
|
||||
jobs:
|
||||
include:
|
||||
# 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://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||
[](https://paypal.me/haraldk76/100)
|
||||
|
||||
## 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 purpose of this project is to provide support for formats not covered by the JRE itself.
|
||||
|
||||
Support for formats is important, both to be able to read data found
|
||||
The main goal 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
|
||||
"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.
|
||||
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
|
||||
As there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
||||
|
||||
----
|
||||
|
||||
|
||||
+6
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
@@ -123,6 +123,11 @@
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-xwd</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -34,7 +34,13 @@ import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
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}.
|
||||
@@ -70,6 +76,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
||||
try {
|
||||
@@ -80,10 +87,9 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||
}
|
||||
|
||||
Graphics2D g2d = null;
|
||||
Graphics2D g2d = dst.createGraphics();
|
||||
|
||||
try {
|
||||
g2d = dst.createGraphics();
|
||||
int interpolationType = delegate.getInterpolationType();
|
||||
|
||||
if (interpolationType > 0) {
|
||||
@@ -109,9 +115,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
return dst;
|
||||
}
|
||||
finally {
|
||||
if (g2d != null) {
|
||||
g2d.dispose();
|
||||
}
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ import java.awt.image.BufferedImage;
|
||||
*/
|
||||
public class BufferedImageIcon implements Icon {
|
||||
private final BufferedImage image;
|
||||
private int width;
|
||||
private int height;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final boolean fast;
|
||||
|
||||
public BufferedImageIcon(BufferedImage pImage) {
|
||||
@@ -81,11 +81,10 @@ public class BufferedImageIcon implements Icon {
|
||||
else {
|
||||
//System.out.println("Scaling using interpolation");
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
||||
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, xform, null);
|
||||
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
|
||||
transform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, transform, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,6 +587,7 @@ class IndexImage {
|
||||
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
||||
* This version will be removed in a later version of the API.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
||||
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
||||
}
|
||||
|
||||
+22
-16
@@ -30,17 +30,26 @@
|
||||
|
||||
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.geom.AffineTransform;
|
||||
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.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* AffineTransformOpTest.
|
||||
@@ -101,6 +110,7 @@ public class AffineTransformOpTest {
|
||||
|
||||
private final int width = 30;
|
||||
private final int height = 20;
|
||||
private final double anchor = min(width, height) / 2.0;
|
||||
|
||||
@Test
|
||||
public void testGetPoint2D() {
|
||||
@@ -128,8 +138,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
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 tmOp = new com.twelvemonkeys.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, anchor, anchor), null);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
BufferedImage image = new BufferedImage(width, height, type);
|
||||
@@ -147,8 +157,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
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 tmOp = new com.twelvemonkeys.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, anchor, anchor), null);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
BufferedImage image = spec.createBufferedImage(width, height);
|
||||
@@ -197,8 +207,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
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 tmOp = new com.twelvemonkeys.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, anchor, anchor), null);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
Raster raster = new BufferedImage(width, height, type).getRaster();
|
||||
@@ -221,8 +231,6 @@ public class AffineTransformOpTest {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
|
||||
System.err.println("type: " + type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -240,8 +248,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
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 tmOp = new com.twelvemonkeys.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, anchor, anchor), null);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
||||
@@ -264,8 +272,6 @@ public class AffineTransformOpTest {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
|
||||
System.err.println("spec: " + spec);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -65,6 +65,7 @@ import java.io.FilenameFilter;
|
||||
* @see WildcardStringParser
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class FilenameMaskFilter implements FilenameFilter {
|
||||
|
||||
// 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.DataInputStream#readLine()
|
||||
*/
|
||||
@Deprecated
|
||||
public String readLine() throws IOException {
|
||||
DataInputStream ds = new DataInputStream(in);
|
||||
return ds.readLine();
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
|
||||
package com.twelvemonkeys.xml;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.w3c.dom.DOMConfiguration;
|
||||
import org.w3c.dom.DOMImplementationList;
|
||||
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.LSSerializer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* {@code DOMImplementationLS} backed implementation.
|
||||
*
|
||||
@@ -88,17 +88,6 @@ public final class DOMSerializer {
|
||||
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
|
||||
* readability.
|
||||
@@ -169,13 +158,7 @@ public final class DOMSerializer {
|
||||
try {
|
||||
return DOMImplementationRegistry.newInstance();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,16 +30,23 @@
|
||||
|
||||
package com.twelvemonkeys.xml;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
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.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
pBuilder.append(pString.substring(pStart, pEnd));
|
||||
pBuilder.append(pString, pStart, pEnd);
|
||||
pBuilder.append(pEntity);
|
||||
return pEnd + 1;
|
||||
}
|
||||
@@ -527,8 +534,7 @@ public class XMLSerializer {
|
||||
builder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
DOMImplementation dom = builder.getDOMImplementation();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -770,6 +770,7 @@ public final class StringUtil {
|
||||
*/
|
||||
|
||||
/*public*/
|
||||
@Deprecated
|
||||
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
@@ -1464,6 +1465,7 @@ public final class StringUtil {
|
||||
*/
|
||||
|
||||
/*public*/
|
||||
@Deprecated
|
||||
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
||||
StringBuilder filteredString = new StringBuilder();
|
||||
boolean insideDemarcatedArea = false;
|
||||
|
||||
@@ -157,6 +157,7 @@ public class Time {
|
||||
* @see #parseTime(String)
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public String toString(String pFormatStr) {
|
||||
TimeFormat tf = new TimeFormat(pFormatStr);
|
||||
|
||||
@@ -174,6 +175,7 @@ public class Time {
|
||||
* @see #toString(String)
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public static Time parseTime(String pStr) {
|
||||
TimeFormat tf = TimeFormat.getInstance();
|
||||
|
||||
|
||||
+1
@@ -111,6 +111,7 @@ import java.io.PrintStream;
|
||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||
* @deprecated Will probably be removed in the near future
|
||||
*/
|
||||
@Deprecated
|
||||
public class WildcardStringParser {
|
||||
// TODO: Get rid of this class
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
|
||||
@@ -31,8 +31,9 @@
|
||||
package com.twelvemonkeys.contrib.tiff;
|
||||
|
||||
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 org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -154,7 +155,7 @@ public class TIFFUtilitiesTest {
|
||||
reader.setInput(checkTest1);
|
||||
for (int i = 0; i < 3; 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();
|
||||
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||
}
|
||||
@@ -171,7 +172,7 @@ public class TIFFUtilitiesTest {
|
||||
reader.setInput(checkTest2);
|
||||
for (int i = 0; i < 3; 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();
|
||||
Assert.assertEquals(orientation, i == 1
|
||||
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -68,13 +68,6 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>xmlgraphics-commons</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-anim</artifactId>
|
||||
@@ -98,7 +91,7 @@
|
||||
<!--
|
||||
There seems to be some weirdness in the
|
||||
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>
|
||||
<exclusion>
|
||||
|
||||
+36
-34
@@ -30,37 +30,8 @@
|
||||
|
||||
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.geom.AffineTransform;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
@@ -70,6 +41,38 @@ import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
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.
|
||||
*
|
||||
@@ -132,6 +135,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// Set ImageReadParams as hints
|
||||
// Note: The cast to Map invokes a different method that preserves
|
||||
// unset defaults, DO NOT REMOVE!
|
||||
//noinspection rawtypes
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -289,7 +293,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// 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
|
||||
// ----
|
||||
if (document != null) {
|
||||
@@ -584,9 +588,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
return dest;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
TranscoderException exception = new TranscoderException(ex.getMessage());
|
||||
exception.initCause(ex);
|
||||
throw exception;
|
||||
throw new TranscoderException(ex.getMessage(), ex);
|
||||
}
|
||||
finally {
|
||||
if (context != null) {
|
||||
|
||||
+2
-4
@@ -54,8 +54,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@@ -225,7 +223,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
assertEquals(500, image.getHeight());
|
||||
|
||||
// CSS and embedded resources all go!
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
@@ -266,7 +264,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
assertEquals(500, image.getHeight());
|
||||
|
||||
// No more warnings now that the base URI is set
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
|
||||
+34
-45
@@ -30,6 +30,22 @@
|
||||
|
||||
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.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
@@ -40,27 +56,6 @@ import java.nio.ByteOrder;
|
||||
import java.util.Collections;
|
||||
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.
|
||||
*
|
||||
@@ -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,
|
||||
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 (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);
|
||||
|
||||
return;
|
||||
@@ -498,19 +497,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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 (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);
|
||||
|
||||
return;
|
||||
@@ -530,19 +527,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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 (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);
|
||||
|
||||
return;
|
||||
@@ -557,13 +552,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
// 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.Assume.assumeNoException;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
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
|
||||
InOrder ordered = inOrder(listener);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
InOrder ordered = inOrder(listener);
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<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
|
||||
public final class AdobePathBuilder {
|
||||
|
||||
private final AdobePathReader delegate;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<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;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.util.LRUHashMap;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
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.
|
||||
@@ -83,9 +77,6 @@ public final class ColorSpaces {
|
||||
|
||||
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
|
||||
|
||||
/** 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")
|
||||
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
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||
|
||||
// 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 {
|
||||
try {
|
||||
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
|
||||
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();
|
||||
}
|
||||
// In case we didn't activate through SPI already
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
}
|
||||
|
||||
private ColorSpaces() {}
|
||||
@@ -134,7 +118,7 @@ public final class ColorSpaces {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// Fix profile before lookup/create
|
||||
profileCleaner.fixProfile(profile);
|
||||
fixProfile(profile);
|
||||
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
||||
|
||||
@@ -146,53 +130,20 @@ public final class ColorSpaces {
|
||||
return getCachedOrCreateCS(profile, profileHeader);
|
||||
}
|
||||
|
||||
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||
// Get *entire profile data*... :-/
|
||||
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)) {
|
||||
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -203,26 +154,36 @@ public final class ColorSpaces {
|
||||
Key key = new Key(profileHeader);
|
||||
|
||||
synchronized (cache) {
|
||||
ICC_ColorSpace cs = cache.get(key);
|
||||
ICC_ColorSpace cs = getCachedCS(key);
|
||||
|
||||
if (cs == null) {
|
||||
cs = new ICC_ColorSpace(profile);
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||
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.
|
||||
*
|
||||
* @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 Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
return ColorProfiles.isCS_sRGB(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
|
||||
*/
|
||||
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;
|
||||
@Deprecated
|
||||
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||
return ColorProfiles.isCS_GRAY(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
// Fix profile before validation
|
||||
profileCleaner.fixProfile(profile);
|
||||
validateColorSpace(new ICC_ColorSpace(profile));
|
||||
|
||||
return profile;
|
||||
return ColorProfiles.validateProfile(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 final byte[] data;
|
||||
|
||||
@@ -448,78 +319,4 @@ public final class ColorSpaces {
|
||||
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 ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
|
||||
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 class JPEG {
|
||||
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];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
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++) {
|
||||
// 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;
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
private final static class ITU_R_601 {
|
||||
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) {
|
||||
@@ -108,17 +154,27 @@ public final class YCbCrConverter {
|
||||
rgb[offset + 1] = clamp(green);
|
||||
}
|
||||
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
public static void convertJPEGYCbCr2RGB(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(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
rgb[offset ] = clamp(y + JPEG.Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (JPEG.Cb_G_LUT[cb] + JPEG.Cr_G_LUT[cr] >> SCALEBITS));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 provider the provider to unregister.
|
||||
* @param category the category to unregister from.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 providerClassName name of the provider class.
|
||||
|
||||
+2
@@ -1,2 +1,4 @@
|
||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||
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());
|
||||
}
|
||||
|
||||
@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
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_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());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBTrue() {
|
||||
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBFalse() {
|
||||
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)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_sRGBNull() {
|
||||
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
|
||||
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 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 static org.junit.Assert.assertArrayEquals;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
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.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
@@ -57,6 +56,7 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static org.junit.Assert.*;
|
||||
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
|
||||
InOrder ordered = inOrder(listener);
|
||||
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);
|
||||
reader.dispose();
|
||||
}
|
||||
@@ -1184,9 +1184,9 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
ordered.verify(listenerToo).imageStarted(reader, 0);
|
||||
ordered.verify(listenerThree).imageStarted(reader, 0);
|
||||
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||
|
||||
ordered.verify(listener).imageComplete(reader);
|
||||
ordered.verify(listenerToo).imageComplete(reader);
|
||||
@@ -1226,7 +1226,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@@ -1253,11 +1253,11 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
}
|
||||
|
||||
// Should not have called any methods on listener1...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
|
||||
InOrder ordered = inOrder(listenerToo);
|
||||
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);
|
||||
reader.dispose();
|
||||
}
|
||||
@@ -1281,7 +1281,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@@ -1307,8 +1307,8 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyZeroInteractions(listenerToo);
|
||||
verifyNoInteractions(listener);
|
||||
verifyNoInteractions(listenerToo);
|
||||
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).imageProgress(any(ImageReader.class), anyInt());
|
||||
doAnswer(abort).when(abortingListener).imageProgress(any(ImageReader.class), anyFloat());
|
||||
|
||||
reader.addIIOReadProgressListener(abortingListener);
|
||||
|
||||
@@ -1738,6 +1738,43 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
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")
|
||||
@Test
|
||||
public void testSetDestinationBands() {
|
||||
|
||||
+12
-13
@@ -52,7 +52,6 @@ import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@@ -79,7 +78,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
protected abstract ImageWriterSpi createProvider();
|
||||
|
||||
protected final T createWriter() throws IOException {
|
||||
return writerClass.cast(provider.createWriterInstance(null));
|
||||
return writerClass.cast(provider.createWriterInstance());
|
||||
}
|
||||
|
||||
protected abstract List<? extends RenderedImage> getTestData();
|
||||
@@ -104,7 +103,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
protected final RenderedImage getTestData(final int 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
|
||||
InOrder ordered = inOrder(listener);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -251,9 +250,9 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
ordered.verify(listenerToo).imageStarted(writer, 0);
|
||||
ordered.verify(listenerThree).imageStarted(writer, 0);
|
||||
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyFloat());
|
||||
|
||||
ordered.verify(listener).imageComplete(writer);
|
||||
ordered.verify(listenerToo).imageComplete(writer);
|
||||
@@ -290,7 +289,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -315,12 +314,12 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
InOrder ordered = inOrder(listenerToo);
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -345,7 +344,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyNoInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -371,7 +370,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
verifyZeroInteractions(listener);
|
||||
verifyZeroInteractions(listenerToo);
|
||||
verifyNoInteractions(listener);
|
||||
verifyNoInteractions(listenerToo);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
|
||||
+10
-2
@@ -38,12 +38,14 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* TGAImageReaderTest
|
||||
* HDRImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||
* @version $Id: HDRImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||
*/
|
||||
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
||||
@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
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
||||
@@ -13,7 +13,7 @@
|
||||
</description>
|
||||
|
||||
<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>
|
||||
|
||||
<build>
|
||||
@@ -33,6 +33,7 @@
|
||||
<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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
||||
@@ -13,7 +13,7 @@
|
||||
</description>
|
||||
|
||||
<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>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ final class EXIFThumbnail {
|
||||
case 6:
|
||||
// YCbCr
|
||||
for (int i = 0; i < thumbLength; i += 3) {
|
||||
YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
|
||||
YCbCrConverter.convertJPEGYCbCr2RGB(thumbData, thumbData, i);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
+13
-32
@@ -31,6 +31,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
@@ -123,7 +124,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
private int currentStreamIndex = 0;
|
||||
private final List<Long> streamOffsets = new ArrayList<>();
|
||||
|
||||
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||
super(provider);
|
||||
|
||||
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)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
else if (bogusAdobeDCT
|
||||
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|
||||
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|
||||
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|
||||
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
|
||||
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
|
||||
// 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
|
||||
|
||||
return ICC_Profile.getInstance(profileData);
|
||||
return ColorProfiles.createProfile(profileData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,11 +951,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
|
||||
int iccChunkDataSize = segment.data.length - segmentDataStart;
|
||||
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
|
||||
|
||||
return readICCProfileSafe(stream, allowBadIndexes, iccSize, iccChunkDataSize);
|
||||
return readICCProfileSafe(stream, allowBadIndexes);
|
||||
}
|
||||
else if (!segments.isEmpty()) {
|
||||
// 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];
|
||||
streams[badICC ? 0 : chunkNumber - 1] = stream;
|
||||
|
||||
int iccChunkDataSize = 0;
|
||||
int iccSize = 0;
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
Application segment = segments.get(i);
|
||||
stream = new DataInputStream(segment.data());
|
||||
@@ -1004,33 +998,20 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
int index = badICC ? i : chunkNumber - 1;
|
||||
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;
|
||||
}
|
||||
|
||||
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile, final int iccSize, final int iccChunkDataSize) throws IOException {
|
||||
if (iccSize < 0 || iccSize > iccChunkDataSize) {
|
||||
processWarningOccurred(String.format("Truncated 'ICC_PROFILE' chunk(s), size: %d. Ignoring ICC profile.", iccSize));
|
||||
return null;
|
||||
}
|
||||
|
||||
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
|
||||
try {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(stream);
|
||||
|
||||
// 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.
|
||||
// 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()));
|
||||
@@ -1169,7 +1150,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||
processThumbnailProgress(0f);
|
||||
|
||||
BufferedImage thumbnail = thumbnails.get(thumbnailIndex).read();;
|
||||
BufferedImage thumbnail = thumbnails.get(thumbnailIndex).read();
|
||||
|
||||
processThumbnailProgress(100f);
|
||||
processThumbnailComplete();
|
||||
@@ -1211,7 +1192,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
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++) {
|
||||
int offset = (x + y * width) * 4;
|
||||
// YCC -> CMY
|
||||
YCbCrConverter.convertYCbCr2RGB(data, data, offset);
|
||||
YCbCrConverter.convertJPEGYCbCr2RGB(data, data, offset);
|
||||
// Inverse K
|
||||
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.junit.Test;
|
||||
import org.mockito.internal.matchers.GreaterThan;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
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.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNoException;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
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
|
||||
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(markerSequences.getLength() - 1); // The last will be the "main" image
|
||||
assertNotNull(markerSequence);
|
||||
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
|
||||
assertThat(markerSequence.getChildNodes().getLength(), greaterThan(0));
|
||||
|
||||
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
|
||||
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 org.junit.Test;
|
||||
import org.mockito.internal.matchers.LessOrEqual;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -48,6 +47,7 @@ import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -124,7 +124,7 @@ public class JPEGSegmentImageInputStreamTest {
|
||||
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...
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ import java.io.IOException;
|
||||
*
|
||||
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.TIFFReader instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class EXIFReader extends MetadataReader {
|
||||
|
||||
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
|
||||
public final class EXIFWriter extends MetadataWriter {
|
||||
|
||||
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
|
||||
public final class Rational extends Number implements Comparable<Rational> {
|
||||
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
|
||||
public interface TIFF extends com.twelvemonkeys.imageio.metadata.tiff.TIFF {
|
||||
}
|
||||
|
||||
+3
-3
@@ -30,6 +30,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||
@@ -42,7 +43,6 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
@@ -353,8 +353,8 @@ public final class JPEGSegmentUtil {
|
||||
Directory psd = new PSDReader().read(stream);
|
||||
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
|
||||
if (iccEntry != null) {
|
||||
ICC_ColorSpace colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance((byte[]) iccEntry.getValue()));
|
||||
System.err.println("colorSpace: " + colorSpace);
|
||||
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
|
||||
System.err.println("ICC Profile: " + profile);
|
||||
}
|
||||
System.err.println("PSD: " + psd);
|
||||
System.err.println(TIFFReader.HexDump.dump(segment.data));
|
||||
|
||||
+13
-13
@@ -30,8 +30,15 @@
|
||||
|
||||
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.File;
|
||||
import java.io.IOException;
|
||||
@@ -39,15 +46,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
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;
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||
|
||||
/**
|
||||
* 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_EXIF_IFD, Collections.unmodifiableCollection(Collections.singleton(TIFF.TAG_INTEROP_IFD)));
|
||||
map.put(TIFF.TAG_SUB_IFD, Collections.singleton(TIFF.TAG_SUB_IFD));
|
||||
map.put(TIFF.TAG_EXIF_IFD, Collections.singleton(TIFF.TAG_INTEROP_IFD));
|
||||
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
private final Set<Long> parsedIFDs = new TreeSet<>();
|
||||
|
||||
private long length;
|
||||
private boolean longOffsets;
|
||||
private int offsetSize;
|
||||
private Set<Long> parsedIFDs = new TreeSet<>();
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
|
||||
+118
-29
@@ -40,7 +40,6 @@ import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
@@ -58,7 +57,24 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
|
||||
private static final int WORD_LENGTH = 2;
|
||||
private static final int LONGWORD_LENGTH = 4;
|
||||
private static final int ENTRY_LENGTH = 12;
|
||||
|
||||
// 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 {
|
||||
return write(new IFD(entries), stream);
|
||||
@@ -87,7 +103,7 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
}
|
||||
|
||||
// Offset to next IFD (EOF)
|
||||
stream.writeInt(0);
|
||||
writeOffset(stream, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -96,7 +112,12 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
// Header
|
||||
ByteOrder byteOrder = stream.getByteOrder();
|
||||
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
||||
stream.writeShort(42);
|
||||
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 {
|
||||
@@ -118,37 +139,42 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
long dataSize = computeDataSize(ordered);
|
||||
|
||||
// Offset to this IFD
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize;
|
||||
|
||||
if (!isSubIFD) {
|
||||
stream.writeInt(assertIntegerOffset(ifdOffset));
|
||||
dataOffset += LONGWORD_LENGTH;
|
||||
writeOffset(stream, ifdOffset);
|
||||
dataOffset += offsetSize;
|
||||
|
||||
// Seek to offset
|
||||
stream.seek(ifdOffset);
|
||||
}
|
||||
else {
|
||||
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
|
||||
dataOffset += directoryCountLength + ordered.size() * entryLength;
|
||||
}
|
||||
|
||||
// Write directory
|
||||
stream.writeShort(ordered.size());
|
||||
writeDirectoryCount(stream, ordered.size());
|
||||
|
||||
for (Entry entry : ordered) {
|
||||
// Write tag id
|
||||
// Write tag id, type & value count
|
||||
stream.writeShort((Integer) entry.getIdentifier());
|
||||
// Write tag type
|
||||
stream.writeShort(getType(entry));
|
||||
// Write value count
|
||||
stream.writeInt(getCount(entry));
|
||||
writeValueCount(stream, getCount(entry));
|
||||
|
||||
// Write value
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
// TODO: This could possibly be a compound directory, in which case the count should be > 1
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long streamPosition = stream.getStreamPosition();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Directory) {
|
||||
if (value instanceof CompoundDirectory) {
|
||||
// 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);
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
Directory subIFD = (Directory) value;
|
||||
writeIFD(subIFD, stream, true);
|
||||
dataOffset += computeDataSize(subIFD);
|
||||
stream.seek(streamPosition);
|
||||
@@ -161,8 +187,26 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
return ifdOffset;
|
||||
}
|
||||
|
||||
public long computeIFDSize(final Collection<Entry> directory) {
|
||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
||||
private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException {
|
||||
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) {
|
||||
@@ -175,13 +219,13 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
||||
}
|
||||
|
||||
if (length > LONGWORD_LENGTH) {
|
||||
if (length > offsetSize) {
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -229,11 +273,11 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
short type = getType(entry);
|
||||
long valueLength = getValueLength(type, getCount(entry));
|
||||
|
||||
if (valueLength <= LONGWORD_LENGTH) {
|
||||
if (valueLength <= offsetSize) {
|
||||
writeValueInline(entry.getValue(), type, stream);
|
||||
|
||||
// Pad
|
||||
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
|
||||
for (long i = valueLength; i < offsetSize; i++) {
|
||||
stream.write(0);
|
||||
}
|
||||
|
||||
@@ -248,7 +292,7 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
|
||||
private int getCount(final Entry entry) {
|
||||
Object value = entry.getValue();
|
||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
||||
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 {
|
||||
@@ -344,12 +388,28 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
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);
|
||||
|
||||
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:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -373,6 +433,7 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
case TIFF.TYPE_IFD:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
@@ -387,6 +448,13 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG8:
|
||||
case TIFF.TYPE_SLONG8:
|
||||
case TIFF.TYPE_IFD8:
|
||||
if (longOffsets) {
|
||||
stream.writeLong(((Number) value).longValue());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
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 {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
writeOffset(stream, dataOffset);
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
writeValueInline(value, type, stream);
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private int assertIntegerOffset(long offset) throws IIOException {
|
||||
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||
public void writeOffset(final ImageOutputStream output, long offset) throws IOException {
|
||||
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");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
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.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -43,13 +53,11 @@ import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* XMPReader
|
||||
@@ -67,10 +75,9 @@ public final class XMPReader extends MetadataReader {
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory factory = createDocumentBuilderFactory();
|
||||
|
||||
// TODO: Consider parsing using SAX?
|
||||
// TODO: Determine encoding and parse using a Reader...
|
||||
// TODO: Refactor scanner to return inputstream?
|
||||
@@ -79,9 +86,6 @@ public final class XMPReader extends MetadataReader {
|
||||
builder.setErrorHandler(new DefaultHandler());
|
||||
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);
|
||||
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
|
||||
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
|
||||
@@ -92,10 +96,33 @@ public final class XMPReader extends MetadataReader {
|
||||
throw new IIOException(e.getMessage(), 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) {
|
||||
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) {
|
||||
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
|
||||
Map<String, List<Entry>> subdirs = new LinkedHashMap<>();
|
||||
|
||||
for (Node desc : asIterable(pNodes)) {
|
||||
if (desc.getParentNode() != pParentNode) {
|
||||
@@ -127,7 +154,7 @@ public final class XMPReader extends MetadataReader {
|
||||
// Lookup
|
||||
List<Entry> dir = subdirs.get(node.getNamespaceURI());
|
||||
if (dir == null) {
|
||||
dir = new ArrayList<Entry>();
|
||||
dir = new ArrayList<>();
|
||||
subdirs.put(node.getNamespaceURI(), dir);
|
||||
}
|
||||
|
||||
@@ -139,7 +166,7 @@ public final class XMPReader extends MetadataReader {
|
||||
else {
|
||||
// TODO: This method contains loads of duplication an should be cleaned up...
|
||||
// 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);
|
||||
|
||||
@@ -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?
|
||||
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
|
||||
@@ -179,7 +206,7 @@ public final class XMPReader extends MetadataReader {
|
||||
|
||||
private RDFDescription parseAsResource(Node node) {
|
||||
// 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())) {
|
||||
if (child.getNodeType() != Node.ELEMENT_NODE) {
|
||||
@@ -204,7 +231,7 @@ public final class XMPReader extends MetadataReader {
|
||||
List<Entry> dir = subdirs.get(attr.getNamespaceURI());
|
||||
|
||||
if (dir == null) {
|
||||
dir = new ArrayList<Entry>();
|
||||
dir = new ArrayList<>();
|
||||
subdirs.put(attr.getNamespaceURI(), dir);
|
||||
}
|
||||
|
||||
@@ -216,7 +243,7 @@ public final class XMPReader extends MetadataReader {
|
||||
for (Node child : asIterable(node.getChildNodes())) {
|
||||
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
|
||||
Map<String, Object> alternatives = new LinkedHashMap<String, Object>();
|
||||
Map<String, Object> alternatives = new LinkedHashMap<>();
|
||||
for (Node alternative : asIterable(child.getChildNodes())) {
|
||||
if (XMP.NS_RDF.equals(alternative.getNamespaceURI()) && "li".equals(alternative.getLocalName())) {
|
||||
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()))) {
|
||||
// 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?)
|
||||
List<Object> seq = new ArrayList<Object>();
|
||||
List<Object> seq = new ArrayList<>();
|
||||
|
||||
for (Node sequence : asIterable(child.getChildNodes())) {
|
||||
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.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -84,7 +85,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -132,7 +133,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -167,7 +168,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -204,7 +205,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -247,9 +248,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
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
|
||||
@@ -266,9 +268,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
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 {
|
||||
|
||||
+83
-12
@@ -30,26 +30,32 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
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.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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
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
|
||||
@@ -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/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>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pcx</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||
|
||||
+11
-4
@@ -104,8 +104,15 @@ public final class PCXImageReader extends ImageReaderBase {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||
|
||||
// TODO: Implement
|
||||
if (rawType.getSampleModel() instanceof BandedSampleModel) {
|
||||
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);
|
||||
|
||||
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
|
||||
return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false);
|
||||
case 24:
|
||||
// Some sources says this is possible...
|
||||
// Some sources say this is possible...
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
case 32:
|
||||
// Some sources says this is possible...
|
||||
// Some sources say this is possible...
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
default:
|
||||
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/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)
|
||||
// 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_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)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pdf</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pict</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<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
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-psd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
||||
|
||||
+2
-1
@@ -30,6 +30,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -55,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
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
|
||||
}
|
||||
|
||||
// Finally add the raw type
|
||||
// Finally, add the raw type
|
||||
types.add(rawType);
|
||||
|
||||
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 last modified by $Author: haraldk$
|
||||
* @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.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<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>
|
||||
*/
|
||||
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)),
|
||||
// 4 channel, CMYK, 16 bit samples
|
||||
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)
|
||||
new TestData(getClassLoaderResource("/psb/test_original.psb"), new Dimension(710, 512)),
|
||||
// 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("/psb/rgb-multichannel-no-transparency.psb"), new Dimension(100, 100)),
|
||||
// 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+
|
||||
);
|
||||
}
|
||||
|
||||
@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
|
||||
protected List<String> getFormatNames() {
|
||||
return Collections.singletonList("psd");
|
||||
@@ -472,7 +507,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
|
||||
public void testMultiChannelNoTransparencyPSB() throws IOException {
|
||||
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"))) {
|
||||
imageReader.setInput(stream);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-reference</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
|
||||
+25
-3
@@ -59,7 +59,7 @@ public final class SGIImageReader extends ImageReaderBase {
|
||||
|
||||
private SGIHeader header;
|
||||
|
||||
protected SGIImageReader(final ImageReaderSpi provider) {
|
||||
SGIImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@@ -88,9 +88,31 @@ public final class SGIImageReader extends ImageReaderBase {
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||
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);
|
||||
|
||||
return specifiers.iterator();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tga</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||
|
||||
+110
-47
@@ -96,10 +96,18 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||
|
||||
// TODO: Implement
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -116,7 +124,14 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
||||
case TGA.IMAGETYPE_MONOCHROME:
|
||||
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_RLE:
|
||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
@@ -135,12 +150,14 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
case 24:
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
case 32:
|
||||
// 4BYTE_BGRX...
|
||||
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
|
||||
// if hasAlpha is false
|
||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
|
||||
// NOTE: We'll read using little endian byte order, thus the file layout is BGRA/BGRx
|
||||
if (hasAlpha) {
|
||||
return ImageTypeSpecifier.createFromBufferedImageType(isAlphaPremultiplied ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
|
||||
default:
|
||||
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
||||
throw new IIOException("Unknown pixel depth for true color: " + header.getPixelDepth());
|
||||
}
|
||||
default:
|
||||
throw new IIOException("Unknown image type: " + header.getImageType());
|
||||
@@ -187,20 +204,26 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
input = imageInput;
|
||||
}
|
||||
|
||||
int pixelDepth = header.getPixelDepth();
|
||||
boolean flipped = isOriginLowerLeft(header.getOrigin());
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getPixelDepth()) {
|
||||
switch (pixelDepth) {
|
||||
case 8:
|
||||
case 24:
|
||||
case 32:
|
||||
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;
|
||||
case 16:
|
||||
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;
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||
throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
@@ -220,10 +243,26 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
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 {
|
||||
// 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 (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);
|
||||
|
||||
return;
|
||||
@@ -244,19 +283,7 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
switch (origin) {
|
||||
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);
|
||||
}
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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 (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);
|
||||
|
||||
return;
|
||||
@@ -283,19 +314,32 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
switch (origin) {
|
||||
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);
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
|
||||
private void readRowInt(final DataInput input, int height, Rectangle srcRegion, boolean flip, int xSub, int ySub,
|
||||
int[] rowDataInt, 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 (y % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||
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
|
||||
@@ -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) {
|
||||
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
|
||||
&& xSub == 1
|
||||
@@ -446,20 +503,26 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
// Thumbnail is always stored non-compressed, no need for RLE support
|
||||
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
||||
|
||||
int pixelDepth = header.getPixelDepth();
|
||||
boolean flipped = isOriginLowerLeft(header.getOrigin());
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getPixelDepth()) {
|
||||
switch (pixelDepth) {
|
||||
case 8:
|
||||
case 24:
|
||||
case 32:
|
||||
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;
|
||||
case 16:
|
||||
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;
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||
throw new AssertionError("Unsupported pixel depth: " + pixelDepth);
|
||||
}
|
||||
|
||||
processThumbnailProgress(100f * y / height);
|
||||
|
||||
+5
-2
@@ -193,10 +193,13 @@ final class TGAMetadata extends AbstractMetadata {
|
||||
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
||||
bitsPerSample.setAttribute("value", createListValue(1, "8"));
|
||||
break;
|
||||
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");
|
||||
}
|
||||
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_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>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-thumbsdb</artifactId>
|
||||
<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>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
||||
@@ -13,7 +13,7 @@
|
||||
</description>
|
||||
|
||||
<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>
|
||||
|
||||
<build>
|
||||
|
||||
+12
-11
@@ -28,25 +28,26 @@
|
||||
* 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 com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
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.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
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.
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.0-SNAPSHOT</version>
|
||||
<version>3.8.0</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<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",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"},
|
||||
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 optionByteAligned;
|
||||
|
||||
// Need to take fill order into account (?) (use flip table?)
|
||||
private final int fillOrder;
|
||||
private final int type;
|
||||
|
||||
private int decodedLength;
|
||||
@@ -81,12 +79,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
* @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},
|
||||
* {@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 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) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
@@ -95,10 +91,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
type == TIFFExtension.COMPRESSION_CCITT_T4 ||
|
||||
type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||
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)
|
||||
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 type the type of stream, must be one of {@code COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE},
|
||||
* {@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.
|
||||
*/
|
||||
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) {
|
||||
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 {
|
||||
// Discover possible incorrect type, revert to RLE
|
||||
if (type == TIFFExtension.COMPRESSION_CCITT_T4 && in.markSupported()) {
|
||||
byte[] streamData = new byte[32];
|
||||
static int findCompressionType(final int encodedType, final InputStream stream) throws IOException {
|
||||
// Discover possible incorrect compression type, revert to RLE if no EOLs found
|
||||
if (encodedType == TIFFExtension.COMPRESSION_CCITT_T4 && stream.markSupported()) {
|
||||
int limit = 512;
|
||||
|
||||
try {
|
||||
in.mark(streamData.length);
|
||||
stream.mark(limit);
|
||||
int first = stream.read();
|
||||
int second = stream.read();
|
||||
|
||||
int offset = 0;
|
||||
while (offset < streamData.length) {
|
||||
int read = in.read(streamData, offset, streamData.length - offset);
|
||||
if (read <= 0) {
|
||||
break;
|
||||
if (second == -1) {
|
||||
// stream to short
|
||||
return encodedType;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
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));
|
||||
b = (short) ((b << 1) + ((streamByte >> (7 - (i % 8))) & 0x01));
|
||||
|
||||
if ((b & 0xFFF) == 1) {
|
||||
// found EOL
|
||||
return TIFFExtension.COMPRESSION_CCITT_T4;
|
||||
}
|
||||
}
|
||||
|
||||
// no EOL till limit
|
||||
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
|
||||
}
|
||||
finally {
|
||||
stream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
return encodedType;
|
||||
}
|
||||
|
||||
private void fetch() throws IOException {
|
||||
@@ -197,13 +198,17 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
try {
|
||||
decodeRow();
|
||||
}
|
||||
catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Mask the AIOOBE as an IOException
|
||||
throw new IOException("Malformed CCITT stream", e);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// TODO: Rewrite to avoid throw/catch for normal flow...
|
||||
if (decodedLength != 0) {
|
||||
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
|
||||
decodedLength = -1;
|
||||
}
|
||||
@@ -468,7 +473,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
int bufferPos = -1;
|
||||
|
||||
private boolean readBit() throws IOException {
|
||||
if (bufferPos < 0 || bufferPos > 7) {
|
||||
if (bufferPos > 7 || bufferPos < 0) {
|
||||
buffer = in.read();
|
||||
|
||||
if (buffer == -1) {
|
||||
@@ -478,21 +483,10 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
bufferPos = 0;
|
||||
}
|
||||
|
||||
boolean isSet;
|
||||
|
||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
||||
}
|
||||
else {
|
||||
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
||||
}
|
||||
|
||||
boolean isSet = (buffer & 0x80) != 0;
|
||||
buffer <<= 1;
|
||||
bufferPos++;
|
||||
|
||||
if (bufferPos > 7) {
|
||||
bufferPos = -1;
|
||||
}
|
||||
|
||||
return isSet;
|
||||
}
|
||||
|
||||
|
||||
+40
-21
@@ -30,6 +30,22 @@
|
||||
|
||||
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.metadata.Directory;
|
||||
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.TIFFEntry;
|
||||
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.
|
||||
@@ -84,7 +88,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
* or {@link #mergeTree(String, Node)} methods.
|
||||
*/
|
||||
public TIFFImageMetadata(final Directory ifd) {
|
||||
super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
|
||||
super(true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFImageMetadataFormat.class.getName(), null, null);
|
||||
this.ifd = Validate.notNull(ifd, "IFD");
|
||||
this.original = ifd;
|
||||
}
|
||||
@@ -96,7 +100,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
* {@link #setFromTree(String, Node)}
|
||||
* or {@link #mergeTree(String, Node)} methods.
|
||||
*/
|
||||
public TIFFImageMetadata(final Collection<Entry> entries) {
|
||||
public TIFFImageMetadata(final Collection<? extends Entry> entries) {
|
||||
this(new IFD(entries));
|
||||
}
|
||||
|
||||
@@ -192,7 +196,12 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
elementNode.setAttribute("value", String.valueOf((Short) value & 0xFFFF));
|
||||
}
|
||||
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 {
|
||||
elementNode.setAttribute("value", String.valueOf(value));
|
||||
@@ -353,8 +362,7 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
// Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
|
||||
Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||
int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
|
||||
int photometricValue = getPhotometricInterpretationWithFallback(); // No default for this tag!
|
||||
int numChannelsValue = getSamplesPerPixelWithFallback();
|
||||
|
||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||
@@ -445,6 +453,13 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
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() {
|
||||
// SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure
|
||||
Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||
@@ -455,15 +470,19 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
: bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1;
|
||||
}
|
||||
|
||||
private int getCompression() {
|
||||
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
||||
return compressionTag == null
|
||||
? TIFFBaseline.COMPRESSION_NONE
|
||||
: getValueAsInt(compressionTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
|
||||
|
||||
Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
|
||||
int compressionValue = compressionTag == null
|
||||
? TIFFBaseline.COMPRESSION_NONE
|
||||
: getValueAsInt(compressionTag);
|
||||
int compressionValue = getCompression();
|
||||
|
||||
// Naming is identical to JAI ImageIO metadata as far as possible
|
||||
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.color.CIELabColorConverter;
|
||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
@@ -164,6 +165,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
private CompoundDirectory IFDs;
|
||||
private Directory currentIFD;
|
||||
private int overrideCCITTCompression = -1;
|
||||
|
||||
TIFFImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
@@ -173,6 +175,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
protected void resetMembers() {
|
||||
IFDs = null;
|
||||
currentIFD = null;
|
||||
overrideCCITTCompression = -1;
|
||||
}
|
||||
|
||||
private void readMetadata() throws IOException {
|
||||
@@ -377,6 +380,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
readMetadata();
|
||||
checkBounds(imageIndex);
|
||||
currentIFD = IFDs.getDirectory(imageIndex);
|
||||
overrideCCITTCompression = -1; // Reset override for next image
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -699,40 +703,49 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
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.
|
||||
int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
|
||||
|
||||
if (interpretation == -1) {
|
||||
int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
||||
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
|
||||
Entry extraSamplesEntry = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
|
||||
int extraSamples = extraSamplesEntry == null ? 0 : extraSamplesEntry.valueCount();
|
||||
Entry extraSamples = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
switch (photometricInterpretation) {
|
||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||
@@ -1040,9 +1053,10 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
// General uncompressed/compressed reading
|
||||
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
|
||||
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, TIFFBaseline.FILL_LEFT_TO_RIGHT);
|
||||
int bitsPerSample = getBitsPerSample();
|
||||
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;
|
||||
|
||||
for (int y = 0; y < tilesDown; y++) {
|
||||
@@ -1059,7 +1073,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
DataInput input;
|
||||
if (!needsAdapter) {
|
||||
// No need for transformation, fast forward
|
||||
// No need for transformation, fast-forward
|
||||
input = imageInput;
|
||||
}
|
||||
else {
|
||||
@@ -1067,6 +1081,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
? createStreamAdapter(imageInput, stripTileByteCounts[i])
|
||||
: createStreamAdapter(imageInput);
|
||||
|
||||
adapter = createFillOrderStream(fillOrder, adapter);
|
||||
adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter);
|
||||
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))) {
|
||||
// Fast, default conversion
|
||||
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 {
|
||||
@@ -2310,26 +2326,28 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
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 {
|
||||
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1);
|
||||
|
||||
private InputStream createDecompressorStream(final int compression, final int width, final int bands, InputStream stream) throws IOException {
|
||||
switch (compression) {
|
||||
case TIFFBaseline.COMPRESSION_NONE:
|
||||
return stream;
|
||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||
return new DecoderStream(createFillOrderStream(fillOrder, stream), new PackBitsDecoder(), 256);
|
||||
return new DecoderStream(stream, new PackBitsDecoder(), 256);
|
||||
case TIFFExtension.COMPRESSION_LZW:
|
||||
// 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_DEFLATE:
|
||||
// TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
||||
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 TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
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:
|
||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||
}
|
||||
@@ -2476,24 +2494,12 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
return value;
|
||||
}
|
||||
|
||||
private ICC_Profile getICCProfile() throws IOException {
|
||||
private ICC_Profile getICCProfile() {
|
||||
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
||||
|
||||
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 {
|
||||
// WEIRDNESS: Reading profile from InputStream is somehow more compatible
|
||||
// than reading from byte array (chops off extra bytes + validates profile).
|
||||
ICC_Profile profile = ICC_Profile.getInstance(new ByteArrayInputStream(value));
|
||||
return ColorSpaces.validateProfile(profile);
|
||||
return ColorProfiles.createProfile((byte[]) entry.getValue());
|
||||
}
|
||||
catch (CMMException | IllegalArgumentException e) {
|
||||
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.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
@@ -63,6 +64,7 @@ import java.util.*;
|
||||
import java.util.zip.Deflater;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -202,26 +204,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
long streamPosition = imageOutput.getStreamPosition();
|
||||
|
||||
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;
|
||||
|
||||
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));
|
||||
|
||||
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();
|
||||
|
||||
// If we have a previous IFD, update pointer
|
||||
if (streamPosition > lastIFDPointerOffset) {
|
||||
imageOutput.seek(lastIFDPointerOffset);
|
||||
imageOutput.writeInt((int) ifdPointer);
|
||||
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||
imageOutput.seek(nextIFDPointerOffset);
|
||||
}
|
||||
|
||||
imageOutput.writeInt(0); // Update next IFD pointer later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later
|
||||
}
|
||||
else {
|
||||
imageOutput.writeInt(0); // Update current IFD pointer later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later
|
||||
}
|
||||
|
||||
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_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();
|
||||
|
||||
@@ -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
|
||||
// TODO: Refactor writeIFD to take an offset
|
||||
imageOutput.seek(lastIFDPointerOffset);
|
||||
imageOutput.writeInt((int) ifdPointer);
|
||||
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||
imageOutput.seek(nextIFDPointerOffset);
|
||||
|
||||
imageOutput.writeInt(0); // Next IFD pointer updated later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later
|
||||
}
|
||||
|
||||
return nextIFDPointerOffset;
|
||||
@@ -737,8 +739,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||
|
||||
try {
|
||||
if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
||||
outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
||||
if (Arrays.asList(inData.getMetadataFormatNames()).contains(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()) {
|
||||
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
||||
@@ -849,9 +851,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
else {
|
||||
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();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -955,11 +959,15 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
configureStreamByteOrder(streamMetadata, imageOutput);
|
||||
|
||||
writingSequence = true;
|
||||
sequenceTIFFWriter = new TIFFWriter();
|
||||
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
|
||||
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
|
||||
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||
}
|
||||
|
||||
private boolean isBigTIFF() throws IOException {
|
||||
return "bigtiff".equalsIgnoreCase(getFormatName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
if (!writingSequence) {
|
||||
@@ -1015,8 +1023,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
BufferedImage original;
|
||||
// BufferedImage original = ImageIO.read(file);
|
||||
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
|
||||
try {
|
||||
try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
|
||||
|
||||
if (!readers.hasNext()) {
|
||||
@@ -1046,9 +1053,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
original = reader.read(0, param);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
System.err.println("original: " + original);
|
||||
|
||||
@@ -1084,12 +1088,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// output.deleteOnExit();
|
||||
|
||||
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("BMP").next();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
try {
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
|
||||
writer.setOutput(stream);
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
@@ -1107,9 +1110,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
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