Compare commits

...

39 Commits

Author SHA1 Message Date
snyk-bot b794d05035 fix: upgrade com.twelvemonkeys.common:common-image from 3.1-SNAPSHOT to 3.7.0
Snyk has created this PR to upgrade com.twelvemonkeys.common:common-image from 3.1-SNAPSHOT to 3.7.0.

See this package in Maven Repository:
https://mvnrepository.com/artifact/com.twelvemonkeys.common/common-image/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/b53d0a96-fb95-4707-a6b4-5e37b1b75566?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-03 20:34:07 +00:00
Harald Kuhr 511a29beb9 Minor AffineTransformOp clean-up + removed test output. 2021-10-29 11:05:24 +02:00
Gauthier 5617b4323c Make tests pass on JDK 16 and 17 (#635)
* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis
2021-10-26 18:38:33 +02:00
Harald Kuhr 16d0af357d #629: Fixed build 2021-10-15 16:04:53 +02:00
Harald Kuhr 74927d5396 #629: Preliminary WebP animation (ANIM/ANMF) support 2021-10-15 15:08:39 +02:00
Harald Kuhr 7e809dd834 Readme improvements 2021-10-15 14:48:39 +02:00
Harald Kuhr 7aa95a08bc Fixed Travis link + bonus project summary updates 2021-10-15 09:41:47 +02:00
Harald Kuhr c28963ae49 Minor servlet clean-up. 2021-09-21 14:23:59 +02:00
Harald Kuhr 0327f5fc1a Servlet deprecation 2021-09-21 14:22:16 +02:00
Harald Kuhr 1c59057c30 #628: Stabilized build + better dependency scopes and module names in interop modules 2021-09-17 20:32:25 +02:00
Harald Kuhr 3e1f85c4dc #626 Clean up + fill order support for all compression types 2021-09-17 19:37:28 +02:00
Harald Kuhr 11227a68a0 #628 TIFF metadata fix, now always outputs denominator for rationals.
+ Bonus: Added JAI TIFF interop module with test and other minor fixes.
2021-09-17 16:34:38 +02:00
Oliver Schmidtmer 62ba73a30e #626: Handle fillOrder in TIFFImageReader, not in CCITTFaxDecoderStream (#627) 2021-09-17 16:16:30 +02:00
Harald Kuhr 1f33afb5a1 Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading. 2021-09-16 22:56:13 +02:00
Harald Kuhr 9d3f271867 #626 TIFF CCITT detection only once per IFD 2021-09-16 22:25:06 +02:00
Harald Kuhr 812e12acb0 #623: TGAImageReader, PCXImageReader and SGIImageReader now return more standard image types as default, for better AffineTransformOp compatibility. Added tests.
Bonus: Subsampling fix for TGAImageReader and BMPImageReader.
2021-09-07 09:29:13 +02:00
Harald Kuhr 060b6cf852 #624: Added metadata support for 16 bit USHORT gray. 2021-09-07 09:24:27 +02:00
Koen De Groote e68ce7ffd1 Certain pixeldepth-16 TGA files fail to process, classcast exception (#624)
* Added fixed for monochrome tga16 bit. Uncertain if that description is complete.
Test files added. Without the changed code, the tests fail.

* Fix suggested by HaraldK

Co-authored-by: Koen De Groote <koen.degroote@limecraft.com>
2021-09-03 16:19:05 +02:00
Harald Kuhr 778cdef69c Fix typo in TIFFImageMetadataFormat mk II. 2021-08-31 22:34:47 +02:00
Harald Kuhr d46a76fca8 Fix typo in TIFFImageMetadataFormat. 2021-08-31 22:26:32 +02:00
Harald Kuhr 105a1ee466 #621 Don't add ICC profile for default gray images 2021-08-31 22:16:08 +02:00
Harald Kuhr aa030f526c #617 BigTIFF write clean-up. 2021-08-31 20:24:42 +02:00
Harald Kuhr 976e5d6210 #619: Fix WebP Y'CbCr->RGB conversion (now uses rec 601) 2021-08-26 16:47:51 +02:00
Harald Kuhr 6daca00fcd Minor clean-up. 2021-08-09 21:24:32 +02:00
Harald Kuhr ef05872934 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java
2021-08-09 21:22:46 +02:00
Harald Kuhr 1ddab866fd #617 BigTIFF write support. 2021-08-09 21:11:40 +02:00
Harald Kuhr ce997a6951 Some more minor clean-up. 2021-08-06 10:10:34 +02:00
Harald Kuhr 23bf5cb7b2 Minor clean-up. 2021-08-06 09:59:07 +02:00
Harald Kuhr 564778f415 #616: Remove dependency on old xmlgraphics-commons (no longer needed) 2021-08-04 11:25:58 +02:00
Harald Kuhr e28bf8fb44 Fix WebP ICC handling for images with alpha. 2021-07-12 10:01:16 +02:00
Harald Kuhr cf8d630d01 Add WebP to BOM. 2021-07-12 09:38:20 +02:00
Harald Kuhr 0ff7224912 Switch build from travis.ci.org to com 2021-06-29 16:56:58 +02:00
Koen De Groote 196081a317 Documentation cleanup (#612)
* Added the `@Deprecated` tag to instances where is was mentioned in documentation, but not for the actual code itself.

Changed one documentation link pointing at a non-existing item.

* As per PR suggestion.
2021-06-29 13:06:07 +02:00
Harald Kuhr ff50180d86 #609 Fixed ICC Profile handling in WebP. 2021-06-03 21:31:54 +02:00
Harald Kuhr 8f2c482167 Minor code clean-up. 2021-06-03 18:21:00 +02:00
Harald Kuhr eab24890ca Merge pull request #608 from Schmidor/ccitt_eol_search
#579 Deeper EOL search in the CCITT stream
2021-05-31 10:40:20 +02:00
Oliver Schmidtmer cd42d81817 Invert EOF check 2021-05-28 14:38:44 +02:00
Oliver Schmidtmer ba5c667b6c #579 Deeper EOL search in the CCITT stream 2021-05-27 22:11:13 +02:00
Harald Kuhr 4e9fa9442c PR template update. 2021-05-13 15:15:09 +02:00
157 changed files with 2506 additions and 933 deletions
@@ -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
View File
@@ -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
+5 -8
View File
@@ -1,19 +1,16 @@
[![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys)
[![Build Status](https://travis-ci.com/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.com/github/haraldk/TwelveMonkeys)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio/badge.svg?color=slateblue)](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys)
[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](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.
----
+5
View File
@@ -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>
@@ -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);
}
@@ -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;
}
}
@@ -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();
@@ -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();
@@ -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
@@ -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
+1 -8
View File
@@ -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>
@@ -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) {
@@ -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();
@@ -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
@@ -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);
}
@@ -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;
@@ -239,6 +239,7 @@ public final class ColorSpaces {
* @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) {
@@ -247,6 +248,21 @@ public final class ColorSpaces {
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>
@@ -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));
}
}
@@ -184,6 +184,24 @@ public class ColorSpacesTest {
ColorSpaces.isCS_sRGB(null);
}
@Test
public void testIsCS_GRAYTrue() {
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
}
@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)));
}
@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...
@@ -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 {
@@ -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() {
@@ -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);
}
}
@@ -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");
+2 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
@@ -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:
@@ -123,7 +123,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);
@@ -1169,7 +1169,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 +1211,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 +1225,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);
}
@@ -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++) {
@@ -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...
}
@@ -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();
@@ -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();
@@ -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;
@@ -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 {
}
@@ -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 {
@@ -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;
}
}
@@ -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");
}
}
}
@@ -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 {
@@ -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());
@@ -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)
@@ -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(
@@ -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();
@@ -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);
@@ -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();
@@ -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);
@@ -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 {
@@ -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
);
}
+57
View File
@@ -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-SNAPSHOT</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>
@@ -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);
}
}
+1 -1
View File
@@ -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>
@@ -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.
@@ -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";
}
}
@@ -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
);
}
}
@@ -30,14 +30,14 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.lang.Validate;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import com.twelvemonkeys.lang.Validate;
/**
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
*
@@ -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 {
@@ -203,7 +204,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
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 +469,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 +479,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;
}
@@ -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) {
@@ -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;
}
}
@@ -30,6 +30,45 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static java.util.Arrays.asList;
import java.awt.*;
import java.awt.color.CMMException;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
@@ -60,35 +99,6 @@ import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.CMMException;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static java.util.Arrays.asList;
/**
* ImageReader implementation for Aldus/Adobe Tagged Image File Format (TIFF).
* <p>
@@ -164,6 +174,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 +184,7 @@ public final class TIFFImageReader extends ImageReaderBase {
protected void resetMembers() {
IFDs = null;
currentIFD = null;
overrideCCITTCompression = -1;
}
private void readMetadata() throws IOException {
@@ -377,6 +389,7 @@ public final class TIFFImageReader extends ImageReaderBase {
readMetadata();
checkBounds(imageIndex);
currentIFD = IFDs.getDirectory(imageIndex);
overrideCCITTCompression = -1; // Reset override for next image
}
@Override
@@ -699,40 +712,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 +1062,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 +1082,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 +1090,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 +2160,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 +2335,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);
}
@@ -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.ColorSpaces;
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() && !ColorSpaces.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());
@@ -39,18 +39,17 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: TIFFMedataFormat.java,v 1.0 17/04/15 harald.kuhr Exp$
* @deprecated Use {@link TIFFImageMetadataFormat} instead.
*/
@Deprecated
public final class TIFFMedataFormat extends IIOMetadataFormatImpl {
// TODO: Fix typo in class name + rename to TIFFImageMetadataFormat
// TODO: Delete class in next version
private static final TIFFMedataFormat INSTANCE = new TIFFMedataFormat();
// 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 static final String SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME = TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
public TIFFMedataFormat() {
super(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
// TODO: Implement!
}
@Override
@@ -53,7 +53,7 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi"},
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
);
}
}
@@ -1 +1,2 @@
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi
@@ -0,0 +1,117 @@
/*
* 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.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
import static org.junit.Assert.assertArrayEquals;
/**
* BigTIFFImageWriterTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BigTIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$
*/
public class BigTIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new BigTIFFImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY)
);
}
@Test
public void roundrtip() throws IOException {
TIFFImageWriter writer = createWriter();
ImageReader reader = ImageIO.getImageReader(writer);
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/bigtiff/BigTIFF.tif"))) {
reader.setInput(input);
BufferedImage image = reader.read(0);
ByteArrayOutputStream temp = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(temp)) {
writer.setOutput(output);
writer.write(image);
}
finally {
writer.dispose();
}
// Validate we actually write BigTIFF
byte[] data = temp.toByteArray();
assertArrayEquals(new byte[] { 'M', 'M', 0, TIFF.BIGTIFF_MAGIC}, Arrays.copyOf(data, 4));
// Read image back and see that it is the same
try (ImageInputStream stream = new ByteArrayImageInputStream(data)) {
reader.setInput(stream);
BufferedImage after = reader.read(0);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertRGBEquals("Pixel values differ: ", image.getRGB(x, y), after.getRGB(x, y), 0);
}
}
}
}
finally {
reader.dispose();
}
}
}
@@ -153,7 +153,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType2() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6,
TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -164,7 +164,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType3_1D() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6,
TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
TIFFExtension.COMPRESSION_CCITT_T4, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -175,7 +175,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType3_1D_FILL() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6,
TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
TIFFExtension.COMPRESSION_CCITT_T4, TIFFExtension.GROUP3OPT_FILLBITS);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -184,7 +184,7 @@ public class CCITTFaxDecoderStreamTest {
}
@Test
public void testFidCompressionType() throws IOException {
public void testFindCompressionType() throws IOException {
// RLE
assertEquals(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, CCITTFaxDecoderStream.findCompressionType(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, new ByteArrayInputStream(DATA_RLE_UNALIGNED)));
@@ -193,8 +193,8 @@ public class CCITTFaxDecoderStreamTest {
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_1D_FILL)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_FILL)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_2D_lsb2msb)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ByteArrayInputStream(DATA_G3_LONG)));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ReverseInputStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb))));
assertEquals(TIFFExtension.COMPRESSION_CCITT_T4, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T4, new ReverseInputStream(new ByteArrayInputStream(DATA_G3_LONG))));
// Group 4/CCITT_T6
assertEquals(TIFFExtension.COMPRESSION_CCITT_T6, CCITTFaxDecoderStream.findCompressionType(TIFFExtension.COMPRESSION_CCITT_T6, new ByteArrayInputStream(DATA_G4)));
@@ -208,7 +208,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType3_2D() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6,
TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
TIFFExtension.COMPRESSION_CCITT_T4, TIFFExtension.GROUP3OPT_2DENCODING);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -219,7 +219,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType3_2D_FILL() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6,
TIFFExtension.COMPRESSION_CCITT_T4, 1,
TIFFExtension.COMPRESSION_CCITT_T4,
TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
@@ -230,8 +230,8 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType3_2D_REVERSED() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb), 6,
TIFFExtension.COMPRESSION_CCITT_T4, 2, TIFFExtension.GROUP3OPT_2DENCODING);
InputStream stream = new CCITTFaxDecoderStream(new ReverseInputStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb)), 6,
TIFFExtension.COMPRESSION_CCITT_T4, TIFFExtension.GROUP3OPT_2DENCODING);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -242,7 +242,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType4() throws IOException {
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6,
TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
TIFFExtension.COMPRESSION_CCITT_T6, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -265,7 +265,7 @@ public class CCITTFaxDecoderStreamTest {
new DataInputStream(inputStream).readFully(data);
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
6, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
6, TIFFExtension.COMPRESSION_CCITT_T6, 0L);
byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes
new DataInputStream(stream).readFully(bytes);
@@ -290,7 +290,7 @@ public class CCITTFaxDecoderStreamTest {
byte[] encoded = imageOutput.toByteArray();
InputStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encoded), 8,
TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
TIFFExtension.COMPRESSION_CCITT_T6, 0L);
byte decoded = (byte) inputStream.read();
assertEquals(data[0], decoded);
}
@@ -307,7 +307,7 @@ public class CCITTFaxDecoderStreamTest {
}
InputStream inputStream = new CCITTFaxDecoderStream(stream,
24, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
24, TIFFExtension.COMPRESSION_CCITT_T6, 0L);
byte decoded = (byte) inputStream.read();
assertEquals((byte) 0b10101010, decoded);
}
@@ -315,7 +315,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType4ByteAligned() throws IOException {
CCITTFaxDecoderStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4_ALIGNED), 6,
TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L, true);
TIFFExtension.COMPRESSION_CCITT_T6, 0L, true);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -326,7 +326,7 @@ public class CCITTFaxDecoderStreamTest {
@Test
public void testDecodeType2NotByteAligned() throws IOException {
CCITTFaxDecoderStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_RLE_UNALIGNED), 6,
TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L, false);
TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 0L, false);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
@@ -348,7 +348,7 @@ public class CCITTFaxDecoderStreamTest {
new DataInputStream(inputStream).readFully(data);
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
1728, TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
1728, TIFFExtension.COMPRESSION_CCITT_T4, TIFFExtension.GROUP3OPT_FILLBITS);
byte[] bytes = new byte[216 * 1168]; // 1728 x 1168 pixel, 1 bpp => 216 bytes * 1168
new DataInputStream(stream).readFully(bytes);
@@ -163,7 +163,7 @@ public class CCITTFaxEncoderStreamTest {
byte[] encodedData = imageOutput.toByteArray();
byte[] decodedData = new byte[data.length];
CCITTFaxDecoderStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 3200, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
CCITTFaxDecoderStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 3200, TIFFExtension.COMPRESSION_CCITT_T6, 0L);
new DataInputStream(inputStream).readFully(decodedData);
inputStream.close();
@@ -184,8 +184,12 @@ public class CCITTFaxEncoderStreamTest {
outputSteam.close();
byte[] encodedData = imageOutput.toByteArray();
InputStream inStream = new ByteArrayInputStream(encodedData);
if(fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT){
inStream = new ReverseInputStream(inStream);
}
try (CCITTFaxDecoderStream inputStream =
new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 6, type, fillOrder, options)) {
new CCITTFaxDecoderStream(inStream, 6, type, options)) {
new DataInputStream(inputStream).readFully(redecodedData);
}
@@ -29,18 +29,14 @@
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
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.metadata.tiff.TIFFReader;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import com.twelvemonkeys.lang.StringUtil;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static org.junit.Assert.*;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.imageio.metadata.IIOInvalidTreeException;
@@ -49,13 +45,20 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
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.metadata.tiff.TIFFReader;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import com.twelvemonkeys.lang.StringUtil;
/**
* TIFFImageMetadataTest.
@@ -188,11 +191,11 @@ public class TIFFImageMetadataTest {
@Test
public void testMetadataNativeFormat() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
Node root = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
Node root = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
// Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
assertNotNull(root);
assertEquals(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
assertEquals(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
assertEquals(1, root.getChildNodes().getLength());
// IFD: "TIFFIFD"
@@ -235,8 +238,8 @@ public class TIFFImageMetadataTest {
assertSingleNodeWithValue(entries, TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, "5");
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCounts);
assertSingleNodeWithValue(entries, TIFF.TAG_PLANAR_CONFIGURATION, TIFF.TYPE_SHORT, "1");
assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0");
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0");
assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0/1");
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0/1");
assertSingleNodeWithValue(entries, 32995, TIFF.TYPE_SHORT, "0"); // Matteing tag, obsoleted by ExtraSamples tag in TIFF 6.0
}
@@ -244,10 +247,10 @@ public class TIFFImageMetadataTest {
public void testTreeDetached() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
Node nativeTree = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
Node nativeTree = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
assertNotNull(nativeTree);
Node nativeTree2 = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
Node nativeTree2 = metadata.getAsTree(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
assertNotNull(nativeTree2);
assertNotSame(nativeTree, nativeTree2);
@@ -266,7 +269,7 @@ public class TIFFImageMetadataTest {
public void testMergeTree() throws IOException {
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
Node nativeTree = metadata.getAsTree(nativeFormat);
assertNotNull(nativeTree);
@@ -296,7 +299,7 @@ public class TIFFImageMetadataTest {
// Validate there's one and only one resolution unit, x res and y res
// Validate resolution unit == 1, x res & y res
assertSingleNodeWithValue(fields, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, String.valueOf(TIFFBaseline.RESOLUTION_UNIT_DPI));
assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300");
assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300/1");
assertSingleNodeWithValue(fields, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, "30001/100");
}
@@ -376,7 +379,7 @@ public class TIFFImageMetadataTest {
public void testMergeTreeFormatMisMatch() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
}
@@ -384,7 +387,7 @@ public class TIFFImageMetadataTest {
public void testMergeTreeInvalid() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
}
@@ -395,7 +398,7 @@ public class TIFFImageMetadataTest {
// Read from file, set empty to see that all is cleared
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
root.appendChild(new IIOMetadataNode("TIFFIFD"));
@@ -418,7 +421,7 @@ public class TIFFImageMetadataTest {
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
@@ -492,7 +495,7 @@ public class TIFFImageMetadataTest {
public void testSetFromTreeFormatMisMatch() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
}
@@ -500,7 +503,7 @@ public class TIFFImageMetadataTest {
public void testSetFromTreeInvalid() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
}
@@ -563,6 +566,16 @@ public class TIFFImageMetadataTest {
assertNotNull(standardTree);
}
@Test
public void testGuessMissingPhotometric() throws IOException {
IIOMetadata metadata = createMetadata("/tiff/guessPhotometric/group4.tif");
// Test that we don't blow up with a NPE due to missing photometric
IIOMetadataNode standardTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
assertNotNull(standardTree);
}
// TODO: Test that failed set leaves metadata unchanged
private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {
@@ -58,8 +58,6 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
@@ -97,7 +95,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"), new Dimension(1728, 1146)), // Gray, CCITT type 2 compressed
new TestData(getClassLoaderResource("/tiff/cramps-tile.tif"), new Dimension(800, 607)), // Gray/WhiteIsZero, uncompressed, striped & tiled...
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg_no_profile.tif"), new Dimension(150, 63)), // CMYK, JPEG compressed, no ICC profile
new TestData(getClassLoaderResource("/tiff/cmyk_jpeg.tif"), new Dimension(100, 100)), // CMYK, JPEG compressed, with ICC profile
new TestData(getClassLoaderResource("/tiff/grayscale-alpha.tiff"), new Dimension(248, 351)), // Gray + unassociated alpha
@@ -125,6 +122,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif"), new Dimension(2480, 3518)), // B/W, CCITT T4
new TestData(getClassLoaderResource("/tiff/CCITTgetNextChangingElement.tif"), new Dimension(2402,195)),
new TestData(getClassLoaderResource("/tiff/ccitt-too-many-changes.tif"), new Dimension(24,153)),
new TestData(getClassLoaderResource("/tiff/ccitt/G32DS.tif"), new Dimension(2464,3248)), // B/W, FillOrder Right to Left
// CIELab
new TestData(getClassLoaderResource("/tiff/ColorCheckerCalculator.tif"), new Dimension(798, 546)), // CIELab 8 bit/sample
// Gray
@@ -172,10 +170,55 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/jpeg-lossless-16bit-gray.tif"), new Dimension(512, 512)), // Lossless JPEG Gray, 16 bit/sample
new TestData(getClassLoaderResource("/tiff/jpeg-lossless-24bit-rgb.tif"), new Dimension(512, 512)), // Lossless JPEG RGB, 8 bit/sample
// Custom PIXTIFF ZIP (Compression: 50013)
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)) // ZIP Gray, 8 bit/sample
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)), // ZIP Gray, 8 bit/sample
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)) // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
);
}
@Override
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
return Arrays.asList(
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-02.tif"), new Dimension(73, 43)), // Gray 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-04.tif"), new Dimension(73, 43)), // Gray 4 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-06.tif"), new Dimension(73, 43)), // Gray 6 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-08.tif"), new Dimension(73, 43)), // Gray 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-10.tif"), new Dimension(73, 43)), // Gray 10 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-12.tif"), new Dimension(73, 43)), // Gray 12 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-14.tif"), new Dimension(73, 43)), // Gray 14 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-16.tif"), new Dimension(73, 43)), // Gray 16 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-24.tif"), new Dimension(73, 43)), // Gray 24 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-minisblack-32.tif"), new Dimension(73, 43)), // Gray 32 bit/sample
// Palette
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-02.tif"), new Dimension(73, 43)), // Palette 2 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-04.tif"), new Dimension(73, 43)), // Palette 4 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample
// RGB Interleaved (PlanarConfiguration: 1)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-14.tif"), new Dimension(73, 43)), // RGB 14 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-16.tif"), new Dimension(73, 43)), // RGB 16 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-24.tif"), new Dimension(73, 43)), // RGB 24 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-32.tif"), new Dimension(73, 43)), // RGB 32 bit/sample
// RGB Planar (PlanarConfiguration: 2)
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-08.tif"), new Dimension(73, 43)) // RGB 8 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-14.tif"), new Dimension(73, 43)), // RGB 14 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-16.tif"), new Dimension(73, 43)), // RGB 16 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-24.tif"), new Dimension(73, 43)), // RGB 24 bit/sample
// // RGB Interleaved Floating point..!? We can read this one, but the samples are not normalized, so colors are way too bright...
// new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-32.tif"), new Dimension(73, 43)), // RGB 32 bit FP samples (!)
// // Separated (CMYK) Interleaved (PlanarConfiguration: 1)
// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-contig-08.tif"), new Dimension(73, 43)), // CMYK 8 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-contig-16.tif"), new Dimension(73, 43)), // CMYK 16 bit/sample
// // Separated (CMYK) Planar (PlanarConfiguration: 2)
// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-08.tif"), new Dimension(73, 43)), // CMYK 8 bit/sample
// new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-16.tif"), new Dimension(73, 43)) // CMYK 16 bit/sample
);
}
private List<TestData> getUnsupportedTestData() {
return Arrays.asList(
// RGB Interleaved (PlanarConfiguration: 1)
@@ -63,6 +63,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataTest.createTIFFFieldNode;
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
import static org.junit.Assert.*;
@@ -113,7 +114,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
@@ -163,7 +164,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
String nativeFormat = SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
@@ -554,6 +555,28 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
}
}
@Test
public void testWriteGrayNoProfile() throws IOException {
ImageWriter writer = createWriter();
FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(512);
try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
writer.setOutput(output);
writer.write(new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_GRAY));
}
try (ImageInputStream input = ImageIO.createImageInputStream(bytes.createInputStream())) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
TIFFImageMetadata metadata = (TIFFImageMetadata) reader.getImageMetadata(0);
Directory ifd = metadata.getIFD();
assertNull("Unexpected ICC profile for default gray", ifd.getEntryById(TIFF.TAG_ICC_PROFILE));
}
}
@Test
public void testWriteParamJPEGQuality() throws IOException {
ImageWriter writer = createWriter();
@@ -583,7 +606,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/a33.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/a33.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@@ -40,15 +40,13 @@ import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;
import java.nio.ByteOrder;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME;
import static java.nio.ByteOrder.BIG_ENDIAN;
import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
/**
@@ -0,0 +1,24 @@
package com.twelvemonkeys.imageio.plugins.webp;
import java.awt.*;
/**
* Represents one animation frame (ANMF) chunk.
*/
final class AnimationFrame extends RIFFChunk {
final Rectangle bounds;
final int duration;
final boolean blend;
final boolean dispose;
AnimationFrame(long length, long offset, Rectangle rectangle, int duration, int flags) {
super(WebP.CHUNK_ANMF, length, offset);
this.bounds = rectangle.getBounds();
this.duration = duration; // Duration in ms
blend = (flags & 2) == 0; // 0: Use alpha blending (SrcOver), 1: Do not blend (Src)
dispose = (flags & 1) != 0; // 0: Do not dispose, 1: Dispose to (fill bounds with) background color
}
}
@@ -50,11 +50,10 @@ import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.WritableRaster;
import java.awt.image.*;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
@@ -74,6 +73,7 @@ final class WebPImageReader extends ImageReaderBase {
// Either VP8_, VP8L or VP8X chunk
private VP8xChunk header;
private ICC_Profile iccProfile;
private final List<AnimationFrame> frames = new ArrayList<>();
WebPImageReader(ImageReaderSpi provider) {
super(provider);
@@ -84,19 +84,12 @@ final class WebPImageReader extends ImageReaderBase {
header = null;
iccProfile = null;
lsbBitReader = null;
frames.clear();
}
@Override
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
// TODO: Figure out why this makes the reader order of magnitudes faster (2-3x?)
// ...or, how to make VP8 decoder make longer reads/make a better FileImageInputStream...
super.setInput(input, seekForwardOnly, ignoreMetadata);
// try {
// super.setInput(new BufferedImageInputStream((ImageInputStream) input), seekForwardOnly, ignoreMetadata);
// }
// catch (IOException e) {
// throw new IOError(e);
// }
lsbBitReader = new LSBBitReader(imageInput);
}
@@ -104,12 +97,82 @@ final class WebPImageReader extends ImageReaderBase {
private void readHeader(int imageIndex) throws IOException {
checkBounds(imageIndex);
// TODO: Consider just storing the chunks, parse until VP8, VP8L or VP8X chunk
readHeader();
if (header.containsANIM) {
readFrame(imageIndex);
}
}
private void readFrame(int frameIndex) throws IOException {
if (!header.containsANIM) {
throw new IndexOutOfBoundsException("imageIndex >= 1 for non-animated WebP: " + frameIndex);
}
if (frameIndex < frames.size()) {
return;
}
// Note: Always extended format if we have animation
// Seek to last frame, or end of header if no frames...
RIFFChunk frame = frames.isEmpty() ? header : frames.get(frames.size() - 1);
imageInput.seek(frame.offset + frame.length);
while (imageInput.getStreamPosition() < imageInput.length()) {
int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt();
long chunkStart = imageInput.getStreamPosition();
if (DEBUG) {
System.out.printf("chunk: '%s'\n", fourCC(nextChunk));
System.out.println("chunkLength: " + chunkLength);
System.out.println("chunkStart: " + chunkStart);
}
switch (nextChunk) {
case WebP.CHUNK_ANIM:
// TODO: 32 bit bg color (hint!) + 16 bit loop count
// + expose bg color in std image metadata...
break;
case WebP.CHUNK_ANMF:
// TODO: Expose x/y offset in std image metadata
int x = 2 * (int) lsbBitReader.readBits(24); // Might be more efficient to read as 3 bytes...
int y = 2 * (int) lsbBitReader.readBits(24);
int w = 1 + (int) lsbBitReader.readBits(24);
int h = 1 + (int) lsbBitReader.readBits(24);
Rectangle bounds = new Rectangle(x, y, w, h);
// TODO: Expose duration/flags in image metadata
int duration = (int) imageInput.readBits(24);
int flags = imageInput.readUnsignedByte(); // 6 bit reserved + blend mode + disposal mode
frames.add(new AnimationFrame(chunkLength, chunkStart, bounds, duration, flags));
break;
default:
// Skip
break;
}
if (frameIndex < frames.size()) {
return;
}
imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length
}
throw new IndexOutOfBoundsException(String.format("imageIndex > %d: %d", frames.size(), frameIndex));
}
private void readHeader() throws IOException {
if (header != null) {
return;
}
// TODO: Generalize RIFF chunk parsing!
// TODO: Generalize RIFF chunk parsing! Visitor?
// RIFF native order is Little Endian
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
@@ -135,10 +198,10 @@ final class WebPImageReader extends ImageReaderBase {
switch (chunk) {
case WebP.CHUNK_VP8_:
//https://tools.ietf.org/html/rfc6386#section-9.1
int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = interframe (not used in WebP)
int frameType = lsbBitReader.readBit(); // 0 = key frame, 1 = inter frame (not used in WebP)
if (frameType != 0) {
throw new IIOException("Unexpected WebP frame type (expected 0): " + frameType);
throw new IIOException("Unexpected WebP frame type, expected key frame (0): " + frameType);
}
int versionNumber = (int) lsbBitReader.readBits(3); // 0 - 3 = different profiles (see spec)
@@ -218,7 +281,7 @@ final class WebPImageReader extends ImageReaderBase {
if (header.containsICCP) {
// ICCP chunk must be first chunk, if present
while (iccProfile != null && imageInput.getStreamPosition() < imageInput.length()) {
while (iccProfile == null && imageInput.getStreamPosition() < imageInput.length()) {
int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt();
long chunkStart = imageInput.getStreamPosition();
@@ -260,30 +323,48 @@ final class WebPImageReader extends ImageReaderBase {
@Override
public int getNumImages(boolean allowSearch) throws IOException {
// TODO: Support ANIM/ANMF
return super.getNumImages(allowSearch);
assertInput();
readHeader();
if (header.containsANIM && allowSearch) {
if (isSeekForwardOnly()) {
throw new IllegalStateException("Illegal combination of allowSearch with seekForwardOnly");
}
readAllFrames();
return frames.size();
}
return header.containsANIM ? -1 : 1;
}
private void readAllFrames() throws IOException {
try {
readFrame(Integer.MAX_VALUE);
}
catch (IndexOutOfBoundsException ignore) {}
}
@Override
public int getWidth(int imageIndex) throws IOException {
readHeader(imageIndex);
return header.width;
return header.containsANIM ? frames.get(imageIndex).bounds.width
: header.width;
}
@Override
public int getHeight(int imageIndex) throws IOException {
readHeader(imageIndex);
return header.height;
return header.containsANIM ? frames.get(imageIndex).bounds.height
: header.height;
}
@Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
readHeader(imageIndex);
// TODO: Spec says:
// "alpha value is codified in bits 31..24, red in bits 23..16, green in bits 15..8 and blue in bits 7..0,
// but implementations of the format are free to use another representation internally."
// TODO: Doc says alpha flag is "hint only" :-P
if (iccProfile != null && !ColorSpaces.isCS_sRGB(iccProfile)) {
ICC_ColorSpace colorSpace = ColorSpaces.createColorSpace(iccProfile);
int[] bandOffsets = header.containsALPH ? new int[] {0, 1, 2, 3} : new int[] {0, 1, 2};
@@ -298,6 +379,11 @@ final class WebPImageReader extends ImageReaderBase {
ImageTypeSpecifier rawImageType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> types = new ArrayList<>();
if (rawImageType.getBufferedImageType() == BufferedImage.TYPE_CUSTOM) {
types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR));
}
types.add(rawImageType);
types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB));
types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_BGR));
@@ -327,74 +413,15 @@ final class WebPImageReader extends ImageReaderBase {
break;
case WebP.CHUNK_VP8X:
imageInput.seek(header.offset + header.length);
while (imageInput.getStreamPosition() < imageInput.length()) {
int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt();
long chunkStart = imageInput.getStreamPosition();
if (DEBUG) {
System.out.printf("chunk: '%s'\n", fourCC(nextChunk));
System.out.println("chunkLength: " + chunkLength);
System.out.println("chunkStart: " + chunkStart);
}
switch (nextChunk) {
case WebP.CHUNK_ALPH:
int reserved = (int) imageInput.readBits(2);
if (reserved != 0) {
// Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
}
int preProcessing = (int) imageInput.readBits(2);
int filtering = (int) imageInput.readBits(2);
int compression = (int) imageInput.readBits(2);
if (DEBUG) {
System.out.println("preProcessing: " + preProcessing);
System.out.println("filtering: " + filtering);
System.out.println("compression: " + compression);
}
switch (compression) {
case 0:
readUncompressedAlpha(destination.getAlphaRaster());
break;
case 1:
opaqueAlpha(destination.getAlphaRaster()); // TODO: Remove when correctly implemented!
readVP8Lossless(destination.getAlphaRaster(), param);
break;
default:
processWarningOccurred("Unknown WebP alpha compression: " + compression);
opaqueAlpha(destination.getAlphaRaster());
break;
}
break;
case WebP.CHUNK_VP8_:
readVP8(RasterUtils.asByteRaster(destination.getRaster())
.createWritableChild(0, 0, width, height, 0, 0, new int[]{0, 1, 2}), param);
break;
case WebP.CHUNK_VP8L:
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param);
break;
case WebP.CHUNK_ANIM:
case WebP.CHUNK_ANMF:
processWarningOccurred("Ignoring unsupported chunk: " + fourCC(nextChunk));
break;
default:
processWarningOccurred("Ignoring unexpected chunk: " + fourCC(nextChunk));
break;
}
imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length
if (header.containsANIM) {
AnimationFrame frame = frames.get(imageIndex);
imageInput.seek(frame.offset + 16);
opaqueAlpha(destination.getAlphaRaster());
readVP8Extended(destination, param, frame.offset + frame.length);
}
else {
imageInput.seek(header.offset + header.length);
readVP8Extended(destination, param, imageInput.length());
}
break;
@@ -403,15 +430,114 @@ final class WebPImageReader extends ImageReaderBase {
throw new IIOException("Unknown first chunk for WebP: " + fourCC(header.fourCC));
}
applyICCProfileIfNeeded(destination);
if (abortRequested()) {
processReadAborted();
} else {
}
else {
processImageComplete();
}
return destination;
}
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd) throws IOException {
while (imageInput.getStreamPosition() < streamEnd) {
int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt();
long chunkStart = imageInput.getStreamPosition();
if (DEBUG) {
System.out.printf("chunk: '%s'\n", fourCC(nextChunk));
System.out.println("chunkLength: " + chunkLength);
System.out.println("chunkStart: " + chunkStart);
}
switch (nextChunk) {
case WebP.CHUNK_ALPH:
int reserved = (int) imageInput.readBits(2);
if (reserved != 0) {
// Spec says SHOULD be 0
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
}
int preProcessing = (int) imageInput.readBits(2);
int filtering = (int) imageInput.readBits(2);
int compression = (int) imageInput.readBits(2);
if (DEBUG) {
System.out.println("preProcessing: " + preProcessing);
System.out.println("filtering: " + filtering);
System.out.println("compression: " + compression);
}
switch (compression) {
case 0:
readUncompressedAlpha(destination.getAlphaRaster());
break;
case 1:
opaqueAlpha(destination.getAlphaRaster()); // TODO: Remove when correctly implemented!
// readVP8Lossless(destination.getAlphaRaster(), param);
break;
default:
processWarningOccurred("Unknown WebP alpha compression: " + compression);
opaqueAlpha(destination.getAlphaRaster());
break;
}
break;
case WebP.CHUNK_VP8_:
readVP8(RasterUtils.asByteRaster(destination.getRaster())
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[]{ 0, 1, 2}), param);
break;
case WebP.CHUNK_VP8L:
readVP8Lossless(RasterUtils.asByteRaster(destination.getRaster()), param);
break;
case WebP.CHUNK_ANIM:
case WebP.CHUNK_ANMF:
if (!header.containsANIM) {
processWarningOccurred("Ignoring unsupported chunk: " + fourCC(nextChunk));
}
case WebP.CHUNK_ICCP:
// Ignore, we already read this
case WebP.CHUNK_EXIF:
case WebP.CHUNK_XMP_:
// Ignore, we'll read these later
break;
default:
processWarningOccurred("Ignoring unexpected chunk: " + fourCC(nextChunk));
break;
}
imageInput.seek(chunkStart + chunkLength + (chunkLength & 1)); // Padded to even length
}
}
private void applyICCProfileIfNeeded(final BufferedImage destination) {
if (iccProfile != null) {
ColorModel colorModel = destination.getColorModel();
ICC_Profile destinationProfile = ((ICC_ColorSpace) colorModel.getColorSpace()).getProfile();
if (!iccProfile.equals(destinationProfile)) {
if (DEBUG) {
System.err.println("Converting from " + iccProfile + " to " + (ColorSpaces.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
}
WritableRaster raster = colorModel.hasAlpha()
? destination.getRaster().createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[] {0, 1, 2})
: destination.getRaster();
new ColorConvertOp(new ICC_Profile[] {iccProfile, destinationProfile}, null)
.filter(raster, raster);
}
}
}
private void opaqueAlpha(final WritableRaster alphaRaster) {
int h = alphaRaster.getHeight();
int w = alphaRaster.getWidth();
@@ -524,8 +650,4 @@ final class WebPImageReader extends ImageReaderBase {
}
}
}
protected static void showIt(BufferedImage image, String title) {
ImageReaderBase.showIt(image, title);
}
}
@@ -259,12 +259,7 @@ public final class VP8LDecoder {
abs(pGreen - GREEN(T)) + abs(pBlue - BLUE(T));
// Return either left or top, the one closer to the prediction.
if (pL < pT) {
return L;
}
else {
return T;
}
return pL < pT ? L : T;
}
private static int average2(final int a, final int b) {
@@ -42,7 +42,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.imageio.color.YCbCrConverter.convertYCbCr2RGB;
import static com.twelvemonkeys.imageio.color.YCbCrConverter.convertRec601YCbCr2RGB;
public final class VP8Frame {
private static final int BLOCK_TYPES = 4;
@@ -54,8 +54,6 @@ public final class VP8Frame {
private IIOReadProgressListener listener = null;
// private int bufferCount;
// private int buffersToCreate = 1;
private final int[][][][] coefProbs;
private int filterLevel;
@@ -117,7 +115,6 @@ public final class VP8Frame {
int c = frame.readUnsignedByte();
frameType = getBitAsInt(c, 0);
// logger.log("Frame type: " + frameType);
if (frameType != 0) {
return false;
@@ -478,7 +475,6 @@ public final class VP8Frame {
}
public BufferedImage getDebugImageDiff() {
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
WritableRaster imRas = bi.getWritableTile(0, 0);
for (int x = 0; x < getWidth(); x++) {
@@ -1037,12 +1033,12 @@ public final class VP8Frame {
int num_part = 1 << multiTokenPartition;
if (num_part > 1) {
partition += 3 * (num_part - 1);
partition += 3L * (num_part - 1);
}
for (int i = 0; i < num_part; i++) {
// Calculate the length of this partition. The last partition size is implicit.
if (i < num_part - 1) {
partitionSize = readPartitionSize(partitionsStart + (i * 3));
partitionSize = readPartitionSize(partitionsStart + (i * 3L));
bc.seek();
}
else {
@@ -1084,9 +1080,8 @@ public final class VP8Frame {
yuv[2] = (byte) macroBlock.getSubBlock(SubBlock.Plane.V, (x / 2) / 4, (y / 2) / 4).getDest()[(x / 2) % 4][(y / 2) % 4];
// TODO: Consider doing YCbCr -> RGB in reader instead, or pass a flag to allow readRaster reading direct YUV/YCbCr values
convertYCbCr2RGB(yuv, rgb, 0);
convertRec601YCbCr2RGB(yuv, rgb, 0);
byteRGBRaster.setDataElements(dstX, dstY, rgb);
// byteRGBRaster.setDataElements(dstX, dstY, yuv);
}
}
}
@@ -2,8 +2,16 @@ package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import static java.util.Arrays.asList;
@@ -59,4 +67,39 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
protected List<String> getMIMETypes() {
return asList("image/webp", "image/x-webp");
}
@Test
public void testReadAndApplyICCProfile() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/photo-iccp-adobergb.webp"))) {
reader.setInput(stream);
// We'll read a small portion of the image into a destination type that use sRGB
ImageReadParam param = new ImageReadParam();
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
param.setSourceRegion(new Rectangle(20, 20));
BufferedImage image = reader.read(0, param);
assertRGBEquals("RGB values differ, incorrect ICC profile or conversion?", 0xFFEA9600, image.getRGB(10, 10), 8);
}
finally {
reader.dispose();
}
}
@Test
public void testRec601ColorConversion() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/blue_tile.webp"))) {
reader.setInput(stream);
BufferedImage image = reader.read(0, null);
assertRGBEquals("RGB values differ, incorrect Y'CbCr -> RGB conversion", 0xFF72AED5, image.getRGB(80, 80), 1);
}
finally {
reader.dispose();
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+10 -2
View File
@@ -55,6 +55,7 @@
<module>imageio-reference</module>
<module>imageio-jpeg-jep262-interop</module>
<module>imageio-jpeg-jai-interop</module>
<module>imageio-tiff-jai-interop</module>
<module>imageio-tiff-jdk-interop</module>
</modules>
@@ -103,8 +104,15 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.5</version>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
+1 -1
View File
@@ -52,7 +52,7 @@
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-image</artifactId>
<version>${project.version}</version>
<version>3.7.0</version>
<scope>compile</scope>
</dependency>
<dependency>
+2 -2
View File
@@ -73,8 +73,8 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.5</version>
<artifactId>mockito-core</artifactId>
<version>3.12.4</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -30,14 +30,6 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -45,12 +37,22 @@ import java.util.Properties;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.twelvemonkeys.lang.StringUtil;
/**
* BrowserHelperFilter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BrowserHelperFilter.java#1 $
*/
@Deprecated
public class BrowserHelperFilter extends GenericFilter {
private static final String HTTP_HEADER_ACCEPT = "Accept";
protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
@@ -30,14 +30,15 @@
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* DebugServlet class description.
@@ -46,6 +47,7 @@ import java.util.Enumeration;
* @author last modified by $Author: haku $
* @version $Id: DebugServlet.java#1 $
*/
@Deprecated
public class DebugServlet extends GenericServlet {
private long dateModified;
@@ -30,14 +30,21 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import javax.servlet.*;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, protocol-independent filter.
* <p>
@@ -72,6 +79,7 @@ import java.util.Enumeration;
* @see Filter
* @see FilterConfig
*/
@Deprecated
public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
@@ -357,6 +365,7 @@ public abstract class GenericFilter implements Filter, FilterConfig, Serializabl
*
* @deprecated For compatibility only, use {@link #init init} instead.
*/
@Deprecated
@SuppressWarnings("UnusedDeclaration")
public void setFilterConfig(final FilterConfig pFilterConfig) {
try {
@@ -30,11 +30,12 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.lang.reflect.InvocationTargetException;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, protocol-independent servlet.
@@ -52,6 +53,7 @@ import java.lang.reflect.InvocationTargetException;
*
* @version $Id: GenericServlet.java#1 $
*/
@Deprecated
public abstract class GenericServlet extends javax.servlet.GenericServlet {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
@@ -30,11 +30,12 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import java.lang.reflect.InvocationTargetException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.lang.reflect.InvocationTargetException;
import com.twelvemonkeys.lang.BeanUtil;
/**
* Defines a generic, HTTP specific servlet.
@@ -52,6 +53,7 @@ import java.lang.reflect.InvocationTargetException;
*
* @version $Id: HttpServlet.java#1 $
*/
@Deprecated
public abstract class HttpServlet extends javax.servlet.http.HttpServlet {
// TODO: Rewrite to use ServletConfigurator instead of BeanUtil
@@ -30,7 +30,12 @@
package com.twelvemonkeys.servlet;
import java.lang.annotation.*;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to be used by servlets/filters, to have their {@code init}-method
@@ -49,6 +54,7 @@ import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD/*, TODO: ElementType.FIELD*/})
@Deprecated
public @interface InitParam {
static final String UNDEFINED = "";
String name() default UNDEFINED;
@@ -30,12 +30,13 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.Validate;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
import com.twelvemonkeys.lang.Validate;
/**
* A {@code ServletOutputStream} implementation backed by a
* {@link java.io.OutputStream}. For filters that need to buffer the
@@ -71,6 +72,7 @@ import java.io.OutputStream;
* @version $Id: OutputStreamAdapter.java#1 $
*
*/
@Deprecated
public class OutputStreamAdapter extends ServletOutputStream {
/** The wrapped {@code OutputStream}. */
@@ -38,6 +38,7 @@ import javax.servlet.ServletException;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: ServletConfigException.java#2 $
*/
@Deprecated
public class ServletConfigException extends ServletException {
// TODO: Parameters for init-param at fault, and possibly servlet name?
@@ -30,14 +30,15 @@
package com.twelvemonkeys.servlet;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import static com.twelvemonkeys.lang.Validate.notNull;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import static com.twelvemonkeys.lang.Validate.notNull;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
/**
* A delegate for handling stream support in wrapped servlet responses.
@@ -50,6 +51,7 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* @author last modified by $Author: haku $
* @version $Id: ServletResponseStreamDelegate.java#2 $
*/
@Deprecated
public class ServletResponseStreamDelegate {
private Object out = null;
protected final ServletResponse response;
@@ -30,14 +30,6 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@@ -48,6 +40,21 @@ import java.net.URL;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.convert.ConversionException;
import com.twelvemonkeys.util.convert.Converter;
/**
* Various servlet related helper methods.
@@ -57,6 +64,7 @@ import java.util.Map;
* @author last modified by $Author: haku $
* @version $Id: ServletUtil.java#3 $
*/
@Deprecated
public final class ServletUtil {
/**
@@ -337,6 +345,7 @@ public final class ServletUtil {
* @deprecated Use {@link javax.servlet.http.HttpServletRequest#getRequestURL()}
* instead.
*/
@Deprecated
static StringBuffer buildHTTPURL(final HttpServletRequest pRequest) {
StringBuffer resultURL = new StringBuffer();
@@ -30,8 +30,12 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -39,12 +43,9 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
/**
* ThrottleFilter, a filter for easing server during heavy load.
@@ -62,6 +63,7 @@ import java.util.Map;
* @see #setMaxConcurrentThreadCount
* @see #setResponseMessages
*/
@Deprecated
public class ThrottleFilter extends GenericFilter {
/**
@@ -30,12 +30,13 @@
package com.twelvemonkeys.servlet;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* TimingFilter class description.
@@ -44,6 +45,7 @@ import java.io.IOException;
* @author last modified by $Author: haku $
* @version $Id: TimingFilter.java#1 $
*/
@Deprecated
public class TimingFilter extends GenericFilter {
private String attribUsage = null;
@@ -30,12 +30,18 @@
package com.twelvemonkeys.servlet;
import javax.servlet.*;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
/**
* Removes extra unneccessary white space from a servlet response.
* White space is defined as per {@link Character#isWhitespace(char)}.
@@ -112,6 +118,7 @@ import java.io.PrintWriter;
* @author last modified by $Author: haku $
* @version $Id: TrimWhiteSpaceFilter.java#2 $
*/
@Deprecated
public class TrimWhiteSpaceFilter extends GenericFilter {
private boolean autoFlush = true;
@@ -30,10 +30,10 @@
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.lang.Validate;
import java.net.URI;
import com.twelvemonkeys.lang.Validate;
/**
* AbstractCacheRequest
*
@@ -41,6 +41,7 @@ import java.net.URI;
* @author last modified by $Author: haku $
* @version $Id: AbstractCacheRequest.java#1 $
*/
@Deprecated
public abstract class AbstractCacheRequest implements CacheRequest {
private final URI requestURI;
private final String method;
@@ -30,7 +30,11 @@
package com.twelvemonkeys.servlet.cache;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* AbstractCacheResponse
@@ -39,6 +43,7 @@ import java.util.*;
* @author last modified by $Author: haku $
* @version $Id: AbstractCacheResponse.java#1 $
*/
@Deprecated
public abstract class AbstractCacheResponse implements CacheResponse {
private int status;
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>(); // Insertion order
@@ -37,6 +37,7 @@ package com.twelvemonkeys.servlet.cache;
* @author last modified by $Author: haku $
* @version $Id: CacheException.java#1 $
*/
@Deprecated
public class CacheException extends Exception {
public CacheException(Throwable pCause) {
super(pCause);

Some files were not shown because too many files have changed in this diff Show More