Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c1d72fd46 | |||
| 94d480ec64 | |||
| 9654a924b7 | |||
| 6d9acaee2d | |||
| fdad13e28f | |||
| 6ce58dd682 | |||
| f21bc2089a | |||
| 65f3bbbccd | |||
| 8346c4148e | |||
| 7c0b4fd91a | |||
| b818454a3f | |||
| 9c8b4ad0d4 |
@@ -2,7 +2,7 @@
|
||||
|
||||
Master branch build status: [](https://travis-ci.org/haraldk/TwelveMonkeys)
|
||||
|
||||
TwelveMonkeys ImageIO [3.2.1](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.2.1%22) is released (Dec. 11th, 2015).
|
||||
TwelveMonkeys ImageIO [3.2](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.2%22) is released (Nov. 1st, 2015).
|
||||
|
||||
## About
|
||||
|
||||
@@ -479,12 +479,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<version>3.2.1</version> <!-- Alternatively, build your own version -->
|
||||
<version>3.2</version> <!-- Alternatively, build your own version -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>3.2.1</version> <!-- Alternatively, build your own version -->
|
||||
<version>3.2</version> <!-- Alternatively, build your own version -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -492,13 +492,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
||||
|
||||
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
||||
|
||||
twelvemonkeys-common-lang-3.2.1.jar
|
||||
twelvemonkeys-common-io-3.2.1.jar
|
||||
twelvemonkeys-common-image-3.2.1.jar
|
||||
twelvemonkeys-imageio-core-3.2.1.jar
|
||||
twelvemonkeys-imageio-metadata-3.2.1.jar
|
||||
twelvemonkeys-imageio-jpeg-3.2.1.jar
|
||||
twelvemonkeys-imageio-tiff-3.2.1.jar
|
||||
twelvemonkeys-common-lang-3.2.jar
|
||||
twelvemonkeys-common-io-3.2.jar
|
||||
twelvemonkeys-common-image-3.2.jar
|
||||
twelvemonkeys-imageio-core-3.2.jar
|
||||
twelvemonkeys-imageio-metadata-3.2.jar
|
||||
twelvemonkeys-imageio-jpeg-3.2.jar
|
||||
twelvemonkeys-imageio-tiff-3.2.jar
|
||||
|
||||
### Links to prebuilt binaries
|
||||
|
||||
@@ -507,37 +507,37 @@ To depend on the JPEG and TIFF plugin in your IDE or program, add all of the fol
|
||||
Requires Java 7 or later.
|
||||
|
||||
Common dependencies
|
||||
* [common-lang-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.2.1/common-lang-3.2.1.jar)
|
||||
* [common-io-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.2.1/common-io-3.2.1.jar)
|
||||
* [common-image-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.2.1/common-image-3.2.1.jar)
|
||||
* [common-lang-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.2/common-lang-3.2.jar)
|
||||
* [common-io-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.2/common-io-3.2.jar)
|
||||
* [common-image-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.2/common-image-3.2.jar)
|
||||
|
||||
ImageIO dependencies
|
||||
* [imageio-core-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.2.1/imageio-core-3.2.1.jar)
|
||||
* [imageio-metadata-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.2.1/imageio-metadata-3.2.1.jar)
|
||||
* [imageio-core-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.2/imageio-core-3.2.jar)
|
||||
* [imageio-metadata-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.2/imageio-metadata-3.2.jar)
|
||||
|
||||
ImageIO plugins
|
||||
* [imageio-bmp-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.2.1/imageio-bmp-3.2.1.jar)
|
||||
* [imageio-jpeg-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.2.1/imageio-jpeg-3.2.1.jar)
|
||||
* [imageio-tiff-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.2.1/imageio-tiff-3.2.1.jar)
|
||||
* [imageio-pnm-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.2.1/imageio-pnm-3.2.1.jar)
|
||||
* [imageio-psd-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.2.1/imageio-psd-3.2.1.jar)
|
||||
* [imageio-hdr-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.2.1/imageio-hdr-3.2.1.jar)
|
||||
* [imageio-iff-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.2.1/imageio-iff-3.2.1.jar)
|
||||
* [imageio-pcx-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.2.1/imageio-pcx-3.2.1.jar)
|
||||
* [imageio-pict-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.2.1/imageio-pict-3.2.1.jar)
|
||||
* [imageio-sgi-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.2.1/imageio-sgi-3.2.1.jar)
|
||||
* [imageio-tga-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.2.1/imageio-tga-3.2.1.jar)
|
||||
* [imageio-icns-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.2.1/imageio-icns-3.2.1.jar)
|
||||
* [imageio-thumbsdb-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.2.1/imageio-thumbsdb-3.2.1.jar)
|
||||
* [imageio-bmp-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.2/imageio-bmp-3.2.jar)
|
||||
* [imageio-jpeg-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.2/imageio-jpeg-3.2.jar)
|
||||
* [imageio-tiff-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.2/imageio-tiff-3.2.jar)
|
||||
* [imageio-pnm-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.2/imageio-pnm-3.2.jar)
|
||||
* [imageio-psd-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.2/imageio-psd-3.2.jar)
|
||||
* [imageio-hdr-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.2/imageio-hdr-3.2.jar)
|
||||
* [imageio-iff-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.2/imageio-iff-3.2.jar)
|
||||
* [imageio-pcx-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.2/imageio-pcx-3.2.jar)
|
||||
* [imageio-pict-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.2/imageio-pict-3.2.jar)
|
||||
* [imageio-sgi-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.2/imageio-sgi-3.2.jar)
|
||||
* [imageio-tga-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.2/imageio-tga-3.2.jar)
|
||||
* [imageio-icns-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.2/imageio-icns-3.2.jar)
|
||||
* [imageio-thumbsdb-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.2/imageio-thumbsdb-3.2.jar)
|
||||
|
||||
ImageIO plugins requiring 3rd party libs
|
||||
* [imageio-batik-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.2.1/imageio-batik-3.2.1.jar)
|
||||
* [imageio-batik-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.2/imageio-batik-3.2.jar)
|
||||
|
||||
Photoshop Path support for ImageIO
|
||||
* [imageio-clippath-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.2.1/imageio-clippath-3.2.1.jar)
|
||||
* [imageio-clippath-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.2/imageio-clippath-3.2.jar)
|
||||
|
||||
Servlet support
|
||||
* [servlet-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.2.1/servlet-3.2.1.jar)
|
||||
* [servlet-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.2/servlet-3.2.jar)
|
||||
|
||||
##### Old version (3.0.x)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
||||
* <p>Currently only a modification on {@link #filter(BufferedImage, BufferedImage)} is done, which does a Graphics2D fallback for the native lib.</p>
|
||||
*
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
final java.awt.image.AffineTransformOp delegate;
|
||||
|
||||
public static final int TYPE_NEAREST_NEIGHBOR = java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
|
||||
|
||||
public static final int TYPE_BILINEAR = java.awt.image.AffineTransformOp.TYPE_BILINEAR;
|
||||
|
||||
public static final int TYPE_BICUBIC = java.awt.image.AffineTransformOp.TYPE_BICUBIC;
|
||||
|
||||
/**
|
||||
* @param xform The {@link AffineTransform} to use for the operation.
|
||||
* @param hints The {@link RenderingHints} object used to specify the interpolation type for the operation.
|
||||
*/
|
||||
public AffineTransformOp(final AffineTransform xform, final RenderingHints hints) {
|
||||
delegate = new java.awt.image.AffineTransformOp(xform, hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xform The {@link AffineTransform} to use for the operation.
|
||||
* @param interpolationType One of the integer interpolation type constants defined by this class: {@link #TYPE_NEAREST_NEIGHBOR}, {@link #TYPE_BILINEAR}, {@link #TYPE_BICUBIC}.
|
||||
*/
|
||||
public AffineTransformOp(final AffineTransform xform, final int interpolationType) {
|
||||
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
||||
try {
|
||||
return delegate.filter(src, dst);
|
||||
}
|
||||
catch (ImagingOpException ex) {
|
||||
if (dst == null) {
|
||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||
}
|
||||
|
||||
Graphics2D g2d = null;
|
||||
|
||||
try {
|
||||
g2d = dst.createGraphics();
|
||||
int interpolationType = delegate.getInterpolationType();
|
||||
|
||||
if (interpolationType > 0) {
|
||||
Object interpolationValue = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
|
||||
|
||||
switch (interpolationType) {
|
||||
case java.awt.image.AffineTransformOp.TYPE_BILINEAR:
|
||||
interpolationValue = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
|
||||
break;
|
||||
case java.awt.image.AffineTransformOp.TYPE_BICUBIC:
|
||||
interpolationValue = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
|
||||
break;
|
||||
}
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationValue);
|
||||
}
|
||||
else if (getRenderingHints() != null) {
|
||||
g2d.setRenderingHints(getRenderingHints());
|
||||
}
|
||||
|
||||
g2d.drawImage(src, delegate.getTransform(), null);
|
||||
|
||||
return dst;
|
||||
}
|
||||
finally {
|
||||
if (g2d != null) {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds2D(final BufferedImage src) {
|
||||
return delegate.getBounds2D(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage createCompatibleDestImage(final BufferedImage src, final ColorModel destCM) {
|
||||
return delegate.createCompatibleDestImage(src, destCM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableRaster filter(final Raster src, final WritableRaster dest) {
|
||||
return delegate.filter(src, dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle2D getBounds2D(final Raster src) {
|
||||
return delegate.getBounds2D(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableRaster createCompatibleDestRaster(final Raster src) {
|
||||
return delegate.createCompatibleDestRaster(src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getPoint2D(final Point2D srcPt, final Point2D dstPt) {
|
||||
return delegate.getPoint2D(srcPt, dstPt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderingHints getRenderingHints() {
|
||||
return delegate.getRenderingHints();
|
||||
}
|
||||
}
|
||||
@@ -358,9 +358,8 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a deep copy of the given image. The image will have the same
|
||||
* color model and raster type, but will not share image (pixel) data
|
||||
* with the input image.
|
||||
* Creates a copy of the given image. The image will have the same
|
||||
* color model and raster type, but will not share image (pixel) data.
|
||||
*
|
||||
* @param pImage the image to clone.
|
||||
*
|
||||
@@ -379,7 +378,7 @@ public final class ImageUtil {
|
||||
cm.createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
|
||||
drawOnto(img, pImage);
|
||||
drawOnto(pImage, img);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
@@ -1336,7 +1336,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
|
||||
//contribX.n = 0;
|
||||
contribX.p = new Contributor[(int) (width * 2.0 + 1.0 + 0.5)];
|
||||
contribX.p = new Contributor[(int) (width * 2.0 + 1.0)];
|
||||
|
||||
center = (double) i / xscale;
|
||||
int left = (int) Math.ceil(center - width);// Note: Assumes width <= .5
|
||||
@@ -1387,7 +1387,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
else {
|
||||
/* Expanding image */
|
||||
//contribX.n = 0;
|
||||
contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0 + 0.5)];
|
||||
contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0)];
|
||||
|
||||
center = (double) i / xscale;
|
||||
int left = (int) Math.ceil(center - fwidth);
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
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.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* AffineTransformOpTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: AffineTransformOpTest.java,v 1.0 03/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class AffineTransformOpTest {
|
||||
// Some notes:
|
||||
// It would be nice to have the following classes from imageio-core available:
|
||||
// - ColorSpaces (for CMYK testing)
|
||||
// - ImageTypeSpecifiers (for correct specs)
|
||||
// Would perhaps be better to use parameterized test case
|
||||
// Is it enough to test only (quadrant) rotation? Or should we test scale/translate/arbitrary rotation etc?
|
||||
|
||||
// TYPE_INT_RGB == 1 (min), TYPE_BYTE_INDEXED == 13 (max), TYPE_CUSTOM (0) excluded
|
||||
private static final List<Integer> TYPES = Arrays.asList(
|
||||
BufferedImage.TYPE_INT_RGB,
|
||||
BufferedImage.TYPE_INT_ARGB,
|
||||
BufferedImage.TYPE_INT_ARGB_PRE,
|
||||
BufferedImage.TYPE_INT_BGR,
|
||||
BufferedImage.TYPE_3BYTE_BGR,
|
||||
BufferedImage.TYPE_4BYTE_ABGR,
|
||||
BufferedImage.TYPE_4BYTE_ABGR_PRE,
|
||||
BufferedImage.TYPE_USHORT_565_RGB,
|
||||
BufferedImage.TYPE_USHORT_555_RGB,
|
||||
BufferedImage.TYPE_BYTE_GRAY,
|
||||
BufferedImage.TYPE_USHORT_GRAY,
|
||||
BufferedImage.TYPE_BYTE_BINARY,
|
||||
BufferedImage.TYPE_BYTE_BINARY
|
||||
);
|
||||
|
||||
private static final ColorSpace GRAY = ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
private static final ColorSpace S_RGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
|
||||
// Most of these will fail using the standard Op
|
||||
private static final List<ImageTypeSpecifier> SPECS = Arrays.asList(
|
||||
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_USHORT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_SHORT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_INT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_FLOAT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_DOUBLE, true, false),
|
||||
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_USHORT, false, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_SHORT, false, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_INT, false, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_DOUBLE, false, false),
|
||||
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_USHORT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_SHORT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_INT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_FLOAT, true, false),
|
||||
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_DOUBLE, true, false)
|
||||
);
|
||||
|
||||
private final int width = 30;
|
||||
private final int height = 20;
|
||||
|
||||
@Test
|
||||
public void testGetPoint2D() {
|
||||
AffineTransform rotateInstance = AffineTransform.getRotateInstance(2.1);
|
||||
BufferedImageOp original = new java.awt.image.AffineTransformOp(rotateInstance, null);
|
||||
BufferedImageOp fallback = new com.twelvemonkeys.image.AffineTransformOp(rotateInstance, null);
|
||||
|
||||
Point2D point = new Point2D.Double(39.7, 42.91);
|
||||
assertEquals(original.getPoint2D(point, null), fallback.getPoint2D(point, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBounds2D() {
|
||||
AffineTransform shearInstance = AffineTransform.getShearInstance(33.77, 77.33);
|
||||
BufferedImageOp original = new java.awt.image.AffineTransformOp(shearInstance, null);
|
||||
BufferedImageOp fallback = new com.twelvemonkeys.image.AffineTransformOp(shearInstance, null);
|
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
assertEquals(original.getBounds2D(image), fallback.getBounds2D(image));
|
||||
}
|
||||
|
||||
// TODO: ...etc. For all delegated methods, just test that it does exactly what the original does.
|
||||
// It won't test much for now, but it will make sure we don't accidentally break things in the future.
|
||||
|
||||
@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);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
BufferedImage image = new BufferedImage(width, height, type);
|
||||
BufferedImage jreResult = jreOp.filter(image, null);
|
||||
BufferedImage tmResult = tmOp.filter(image, null);
|
||||
|
||||
assertNotNull("No result!", tmResult);
|
||||
assertEquals("Bad type", jreResult.getType(), tmResult.getType());
|
||||
assertEquals("Incorrect color model", jreResult.getColorModel(), tmResult.getColorModel());
|
||||
|
||||
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
|
||||
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
BufferedImage image = spec.createBufferedImage(width, height);
|
||||
|
||||
BufferedImage tmResult = tmOp.filter(image, null);
|
||||
assertNotNull("No result!", tmResult);
|
||||
|
||||
BufferedImage jreResult = null;
|
||||
|
||||
try {
|
||||
jreResult = jreOp.filter(image, null);
|
||||
}
|
||||
catch (ImagingOpException ignore) {
|
||||
// We expect this to fail for certain cases, that's why we crated the class in the first place
|
||||
}
|
||||
|
||||
if (jreResult != null) {
|
||||
assertEquals("Bad type", jreResult.getType(), tmResult.getType());
|
||||
assertEquals("Incorrect color model", jreResult.getColorModel(), tmResult.getColorModel());
|
||||
|
||||
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
|
||||
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
|
||||
}
|
||||
else {
|
||||
assertEquals("Bad type", spec.getBufferedImageType(), tmResult.getType());
|
||||
assertEquals("Incorrect color model", spec.getColorModel(), tmResult.getColorModel());
|
||||
|
||||
assertEquals("Incorrect width", height, tmResult.getWidth());
|
||||
assertEquals("Incorrect height", width, tmResult.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test RasterOp variants
|
||||
|
||||
@Test
|
||||
public void testGetBounds2DRaster() {
|
||||
AffineTransform shearInstance = AffineTransform.getShearInstance(33.77, 77.33);
|
||||
RasterOp original = new java.awt.image.AffineTransformOp(shearInstance, null);
|
||||
RasterOp fallback = new com.twelvemonkeys.image.AffineTransformOp(shearInstance, null);
|
||||
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
assertEquals(original.getBounds2D(image.getRaster()), fallback.getBounds2D(image.getRaster()));
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
Raster raster = new BufferedImage(width, height, type).getRaster();
|
||||
Raster jreResult = null;
|
||||
Raster tmResult = null;
|
||||
|
||||
try {
|
||||
jreResult = jreOp.filter(raster, null);
|
||||
}
|
||||
catch (ImagingOpException ignore) {
|
||||
// We expect this to fail for certain cases, that's why we crated the class in the first place
|
||||
}
|
||||
|
||||
try {
|
||||
tmResult = tmOp.filter(raster, null);
|
||||
}
|
||||
catch (ImagingOpException e) {
|
||||
// Only fail if JRE AffineOp produces a result and our version not
|
||||
if (jreResult != null) {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
|
||||
System.err.println("type: " + type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (jreResult != null) {
|
||||
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
|
||||
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
|
||||
}
|
||||
else {
|
||||
assertEquals("Incorrect width", height, tmResult.getWidth());
|
||||
assertEquals("Incorrect height", width, tmResult.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
||||
Raster jreResult = null;
|
||||
Raster tmResult = null;
|
||||
|
||||
try {
|
||||
jreResult = jreOp.filter(raster, null);
|
||||
}
|
||||
catch (ImagingOpException ignore) {
|
||||
// We expect this to fail for certain cases, that's why we crated the class in the first place
|
||||
}
|
||||
|
||||
try {
|
||||
tmResult = tmOp.filter(raster, null);
|
||||
}
|
||||
catch (ImagingOpException e) {
|
||||
// Only fail if JRE AffineOp produces a result and our version not
|
||||
if (jreResult != null) {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
|
||||
System.err.println("spec: " + spec);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (jreResult != null) {
|
||||
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
|
||||
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
|
||||
}
|
||||
else {
|
||||
assertEquals("Incorrect width", height, tmResult.getWidth());
|
||||
assertEquals("Incorrect height", width, tmResult.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,11 @@ public class ImageUtilTestCase {
|
||||
// Should have same dimensions
|
||||
assertEquals(scaled.getWidth(null), bufferedScaled.getWidth());
|
||||
assertEquals(scaled.getHeight(null), bufferedScaled.getHeight());
|
||||
|
||||
// Hmmm...
|
||||
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|
||||
|| bufferedScaled.getPropertyNames() == null
|
||||
|| bufferedScaled.getPropertyNames().length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -306,7 +306,7 @@ public class ResampleOpTestCase {
|
||||
|
||||
// https://github.com/haraldk/TwelveMonkeys/issues/195
|
||||
@Test
|
||||
public void testAIOOBEHeight() {
|
||||
public void testAIOOBE() {
|
||||
BufferedImage myImage = new BufferedImage(100, 354, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
for (int i = 19; i > 0; i--) {
|
||||
@@ -316,18 +316,6 @@ public class ResampleOpTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/haraldk/TwelveMonkeys/issues/195
|
||||
@Test
|
||||
public void testAIOOBEWidth() {
|
||||
BufferedImage myImage = new BufferedImage(2832, 283, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
for (int i = 145; i > 143; i--) {
|
||||
ResampleOp resampler = new ResampleOp(i, 14, ResampleOp.FILTER_LANCZOS);
|
||||
BufferedImage resizedImage = resampler.filter(myImage, null);
|
||||
assertNotNull(resizedImage);
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Not for general unit testing")
|
||||
@Test
|
||||
public void testTime() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<?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</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
<name>TwelveMonkeys :: Contrib</name>
|
||||
<description>
|
||||
Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-image</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,603 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.contrib.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.AffineTransformOp;
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TIFFUtilities for manipulation TIFF Images and Metadata
|
||||
*
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class TIFFUtilities {
|
||||
private TIFFUtilities() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges all pages from the input TIFF files into one TIFF file at the
|
||||
* output location.
|
||||
*
|
||||
* @param inputFiles
|
||||
* @param outputFile
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void merge(List<File> inputFiles, File outputFile) throws IOException {
|
||||
ImageOutputStream output = null;
|
||||
try {
|
||||
output = ImageIO.createImageOutputStream(outputFile);
|
||||
|
||||
for (File file : inputFiles) {
|
||||
ImageInputStream input = null;
|
||||
try {
|
||||
input = ImageIO.createImageInputStream(file);
|
||||
List<TIFFPage> pages = getPages(input);
|
||||
writePages(output, pages);
|
||||
}
|
||||
finally {
|
||||
if (input != null) {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (output != null) {
|
||||
output.flush();
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits all pages from the input TIFF file to one file per page in the
|
||||
* output directory.
|
||||
*
|
||||
* @param inputFile
|
||||
* @param outputDirectory
|
||||
* @return generated files
|
||||
* @throws IOException
|
||||
*/
|
||||
public static List<File> split(File inputFile, File outputDirectory) throws IOException {
|
||||
ImageInputStream input = null;
|
||||
List<File> outputFiles = new ArrayList<>();
|
||||
try {
|
||||
input = ImageIO.createImageInputStream(inputFile);
|
||||
List<TIFFPage> pages = getPages(input);
|
||||
int pageNo = 1;
|
||||
for (TIFFPage tiffPage : pages) {
|
||||
ArrayList<TIFFPage> outputPages = new ArrayList<TIFFPage>(1);
|
||||
ImageOutputStream outputStream = null;
|
||||
try {
|
||||
File outputFile = new File(outputDirectory, String.format("%04d", pageNo) + ".tif");
|
||||
outputStream = ImageIO.createImageOutputStream(outputFile);
|
||||
outputPages.clear();
|
||||
outputPages.add(tiffPage);
|
||||
writePages(outputStream, outputPages);
|
||||
outputFiles.add(outputFile);
|
||||
}
|
||||
finally {
|
||||
if (outputStream != null) {
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
++pageNo;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (input != null) {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
return outputFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates all pages of a TIFF file by changing TIFF.TAG_ORIENTATION.
|
||||
* <p>
|
||||
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
|
||||
* displayed. Other metadata, such as width and height, relate to the image
|
||||
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
|
||||
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||
* applying TIFF.TAG_ORIENTATION.
|
||||
* </p>
|
||||
*
|
||||
* @param imageInput
|
||||
* @param imageOutput
|
||||
* @param degree Rotation amount, supports 90ďż˝, 180ďż˝ and 270ďż˝.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void rotatePages(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree)
|
||||
throws IOException {
|
||||
rotatePage(imageInput, imageOutput, degree, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates a page of a TIFF file by changing TIFF.TAG_ORIENTATION.
|
||||
* <p>
|
||||
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
|
||||
* displayed. Other metadata, such as width and height, relate to the image
|
||||
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
|
||||
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||
* applying TIFF.TAG_ORIENTATION.
|
||||
* </p>
|
||||
*
|
||||
* @param imageInput
|
||||
* @param imageOutput
|
||||
* @param degree Rotation amount, supports 90ďż˝, 180ďż˝ and 270ďż˝.
|
||||
* @param pageIndex page which should be rotated or -1 for all pages.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void rotatePage(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree, int pageIndex)
|
||||
throws IOException {
|
||||
ImageInputStream input = null;
|
||||
try {
|
||||
List<TIFFPage> pages = getPages(imageInput);
|
||||
if (pageIndex != -1) {
|
||||
pages.get(pageIndex).rotate(degree);
|
||||
}
|
||||
else {
|
||||
for (TIFFPage tiffPage : pages) {
|
||||
tiffPage.rotate(degree);
|
||||
}
|
||||
}
|
||||
writePages(imageOutput, pages);
|
||||
}
|
||||
finally {
|
||||
if (input != null) {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
|
||||
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
|
||||
|
||||
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
|
||||
|
||||
int pageCount = IFDs.directoryCount();
|
||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||
pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput));
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
public static void writePages(ImageOutputStream imageOutput, List<TIFFPage> pages) throws IOException {
|
||||
EXIFWriter exif = new EXIFWriter();
|
||||
long nextPagePos = imageOutput.getStreamPosition();
|
||||
if (nextPagePos == 0) {
|
||||
exif.writeTIFFHeader(imageOutput);
|
||||
nextPagePos = imageOutput.getStreamPosition();
|
||||
imageOutput.writeInt(0);
|
||||
}
|
||||
else {
|
||||
// already has pages, so remember place of EOF to replace with
|
||||
// IFD offset
|
||||
nextPagePos -= 4;
|
||||
}
|
||||
|
||||
for (TIFFPage tiffPage : pages) {
|
||||
long ifdOffset = tiffPage.write(imageOutput, exif);
|
||||
|
||||
long tmp = imageOutput.getStreamPosition();
|
||||
imageOutput.seek(nextPagePos);
|
||||
imageOutput.writeInt((int) ifdOffset);
|
||||
imageOutput.seek(tmp);
|
||||
nextPagePos = tmp;
|
||||
imageOutput.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedImage applyOrientation(BufferedImage input, int orientation) {
|
||||
boolean flipExtends = false;
|
||||
int w = input.getWidth();
|
||||
int h = input.getHeight();
|
||||
double cW = w / 2.0;
|
||||
double cH = h / 2.0;
|
||||
|
||||
AffineTransform orientationTransform = new AffineTransform();
|
||||
switch (orientation) {
|
||||
case TIFFBaseline.ORIENTATION_TOPLEFT:
|
||||
// normal
|
||||
return input;
|
||||
case TIFFExtension.ORIENTATION_TOPRIGHT:
|
||||
// flipped vertically
|
||||
orientationTransform.translate(cW, cH);
|
||||
orientationTransform.scale(-1, 1);
|
||||
orientationTransform.translate(-cW, -cH);
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_BOTRIGHT:
|
||||
// rotated 180
|
||||
orientationTransform.quadrantRotate(2, cW, cH);
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_BOTLEFT:
|
||||
// flipped horizontally
|
||||
orientationTransform.translate(cW, cH);
|
||||
orientationTransform.scale(1, -1);
|
||||
orientationTransform.translate(-cW, -cH);
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_LEFTTOP:
|
||||
orientationTransform.translate(cW, cH);
|
||||
orientationTransform.scale(-1, 1);
|
||||
orientationTransform.quadrantRotate(1);
|
||||
orientationTransform.translate(-cW, -cH);
|
||||
flipExtends = true;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_RIGHTTOP:
|
||||
// rotated 90
|
||||
orientationTransform.quadrantRotate(1, cW, cH);
|
||||
flipExtends = true;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_RIGHTBOT:
|
||||
orientationTransform.translate(cW, cH);
|
||||
orientationTransform.scale(1, -1);
|
||||
orientationTransform.quadrantRotate(1);
|
||||
orientationTransform.translate(-cW, -cH);
|
||||
flipExtends = true;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_LEFTBOT:
|
||||
// rotated 270
|
||||
orientationTransform.quadrantRotate(3, cW, cH);
|
||||
flipExtends = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int newW, newH;
|
||||
if (flipExtends) {
|
||||
newW = h;
|
||||
newH = w;
|
||||
}
|
||||
else {
|
||||
newW = w;
|
||||
newH = h;
|
||||
}
|
||||
|
||||
AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
|
||||
transform.concatenate(orientationTransform);
|
||||
AffineTransformOp transformOp = new AffineTransformOp(transform, null);
|
||||
return transformOp.filter(input, null);
|
||||
}
|
||||
|
||||
public static class TIFFPage {
|
||||
private Directory IFD;
|
||||
private ImageInputStream stream;
|
||||
|
||||
private TIFFPage(Directory IFD, ImageInputStream stream) {
|
||||
this.IFD = IFD;
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
private long write(ImageOutputStream outputStream, EXIFWriter exifWriter) throws IOException {
|
||||
List<Entry> newIFD = writeDirectoryData(IFD, outputStream);
|
||||
return exifWriter.writeIFD(newIFD, outputStream);
|
||||
}
|
||||
|
||||
private List<Entry> writeDirectoryData(Directory IFD, ImageOutputStream outputStream) throws IOException {
|
||||
ArrayList<Entry> newIFD = new ArrayList<Entry>();
|
||||
Iterator<Entry> it = IFD.iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry e = it.next();
|
||||
if (e.getValue() instanceof Directory) {
|
||||
List<Entry> subIFD = writeDirectoryData((Directory) e.getValue(), outputStream);
|
||||
new TIFFEntry((Integer) e.getIdentifier(), TIFF.TYPE_IFD, new AbstractDirectory(subIFD) {
|
||||
});
|
||||
}
|
||||
|
||||
newIFD.add(e);
|
||||
}
|
||||
|
||||
long[] offsets = new long[0];
|
||||
long[] byteCounts = new long[0];
|
||||
int[] newOffsets = new int[0];
|
||||
|
||||
Entry stripOffsetsEntry = IFD.getEntryById(TIFF.TAG_STRIP_OFFSETS);
|
||||
Entry stripByteCountsEntry = IFD.getEntryById(TIFF.TAG_STRIP_BYTE_COUNTS);
|
||||
if (stripOffsetsEntry != null && stripByteCountsEntry != null) {
|
||||
offsets = getValueAsLongArray(stripOffsetsEntry);
|
||||
byteCounts = getValueAsLongArray(stripByteCountsEntry);
|
||||
|
||||
newOffsets = writeData(offsets, byteCounts, outputStream);
|
||||
|
||||
newIFD.remove(stripOffsetsEntry);
|
||||
newIFD.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, newOffsets));
|
||||
}
|
||||
|
||||
Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
|
||||
Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
|
||||
if (oldJpegData != null && oldJpegData.valueCount() > 0 && oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) {
|
||||
if (!Arrays.equals(getValueAsLongArray(oldJpegData), offsets) || !Arrays.equals(getValueAsLongArray(oldJpegDataLength), byteCounts)) {
|
||||
// data already written from TIFF.TAG_STRIP_OFFSETS
|
||||
offsets = getValueAsLongArray(oldJpegData);
|
||||
byteCounts = getValueAsLongArray(oldJpegDataLength);
|
||||
newOffsets = writeData(offsets, byteCounts, outputStream);
|
||||
}
|
||||
newIFD.remove(oldJpegData);
|
||||
newIFD.add(new TIFFEntry(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, newOffsets));
|
||||
}
|
||||
|
||||
Entry oldJpegTable;
|
||||
long[] tableOffsets;
|
||||
|
||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
|
||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
||||
byteCounts = new long[tableOffsets.length];
|
||||
Arrays.fill(byteCounts, 64);
|
||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
||||
newIFD.remove(oldJpegTable);
|
||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_AC_TABLES, newOffsets));
|
||||
}
|
||||
|
||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
|
||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
||||
byteCounts = new long[tableOffsets.length];
|
||||
Arrays.fill(byteCounts, 64);
|
||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
||||
newIFD.remove(oldJpegTable);
|
||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_Q_TABLES, newOffsets));
|
||||
}
|
||||
|
||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
|
||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
||||
byteCounts = new long[tableOffsets.length];
|
||||
Arrays.fill(byteCounts, 64);
|
||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
||||
newIFD.remove(oldJpegTable);
|
||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_DC_TABLES, newOffsets));
|
||||
}
|
||||
|
||||
return newIFD;
|
||||
}
|
||||
|
||||
private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException {
|
||||
int[] newOffsets = new int[offsets.length];
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
newOffsets[i] = (int) outputStream.getStreamPosition();
|
||||
stream.seek(offsets[i]);
|
||||
|
||||
byte[] buffer = new byte[(int) byteCounts[i]];
|
||||
stream.readFully(buffer);
|
||||
outputStream.write(buffer);
|
||||
}
|
||||
return newOffsets;
|
||||
}
|
||||
|
||||
private long[] getValueAsLongArray(Entry entry) throws IIOException {
|
||||
//TODO: code duplication from TIFFReader, should be extracted to metadata api
|
||||
long[] value;
|
||||
|
||||
if (entry.valueCount() == 1) {
|
||||
// For single entries, this will be a boxed type
|
||||
value = new long[] {((Number) entry.getValue()).longValue()};
|
||||
}
|
||||
else if (entry.getValue() instanceof short[]) {
|
||||
short[] shorts = (short[]) entry.getValue();
|
||||
value = new long[shorts.length];
|
||||
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
value[i] = shorts[i];
|
||||
}
|
||||
}
|
||||
else if (entry.getValue() instanceof int[]) {
|
||||
int[] ints = (int[]) entry.getValue();
|
||||
value = new long[ints.length];
|
||||
|
||||
for (int i = 0, length = value.length; i < length; i++) {
|
||||
value[i] = ints[i];
|
||||
}
|
||||
}
|
||||
else if (entry.getValue() instanceof long[]) {
|
||||
value = (long[]) entry.getValue();
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Unsupported %s type: %s (%s)", entry.getFieldName(), entry.getTypeName(), entry.getValue().getClass()));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the image by changing TIFF.TAG_ORIENTATION.
|
||||
* <p>
|
||||
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do
|
||||
* be displayed. Other metadata, such as width and height, relate to the
|
||||
* image as how it is stored. The ImageIO TIFF plugin does not handle
|
||||
* orientation. Use
|
||||
* {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
|
||||
* applying TIFF.TAG_ORIENTATION.
|
||||
* </p>
|
||||
*
|
||||
* @param degree Rotation amount, supports 90ďż˝, 180ďż˝ and 270ďż˝.
|
||||
*/
|
||||
public void rotate(int degree) {
|
||||
Validate.isTrue(degree % 90 == 0 && degree > 0 && degree < 360,
|
||||
"Only rotations by 90, 180 and 270 degree are supported");
|
||||
|
||||
ArrayList<Entry> newIDFData = new ArrayList<>();
|
||||
Iterator<Entry> it = IFD.iterator();
|
||||
while (it.hasNext()) {
|
||||
newIDFData.add(it.next());
|
||||
}
|
||||
|
||||
short orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
|
||||
Entry orientationEntry = IFD.getEntryById(TIFF.TAG_ORIENTATION);
|
||||
if (orientationEntry != null) {
|
||||
orientation = ((Number) orientationEntry.getValue()).shortValue();
|
||||
newIDFData.remove(orientationEntry);
|
||||
}
|
||||
|
||||
int steps = degree / 90;
|
||||
for (int i = 0; i < steps; i++) {
|
||||
switch (orientation) {
|
||||
case TIFFBaseline.ORIENTATION_TOPLEFT:
|
||||
orientation = TIFFExtension.ORIENTATION_RIGHTTOP;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_TOPRIGHT:
|
||||
orientation = TIFFExtension.ORIENTATION_RIGHTBOT;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_BOTRIGHT:
|
||||
orientation = TIFFExtension.ORIENTATION_LEFTBOT;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_BOTLEFT:
|
||||
orientation = TIFFExtension.ORIENTATION_LEFTTOP;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_LEFTTOP:
|
||||
orientation = TIFFExtension.ORIENTATION_TOPRIGHT;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_RIGHTTOP:
|
||||
orientation = TIFFExtension.ORIENTATION_BOTRIGHT;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_RIGHTBOT:
|
||||
orientation = TIFFExtension.ORIENTATION_BOTLEFT;
|
||||
break;
|
||||
case TIFFExtension.ORIENTATION_LEFTBOT:
|
||||
orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
|
||||
IFD = new AbstractDirectory(newIDFData) {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||
*/
|
||||
public static final class TIFFEntry extends AbstractEntry {
|
||||
// TODO: Expose a merge of this and the EXIFEntry class...
|
||||
private final short type;
|
||||
|
||||
private static short guessType(final Object val) {
|
||||
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
|
||||
Object value = Validate.notNull(val);
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
if (array) {
|
||||
value = Array.get(value, 0);
|
||||
}
|
||||
|
||||
// Note: This "narrowing" is to keep data consistent between read/write.
|
||||
// TODO: Check for negative values and use signed types?
|
||||
if (value instanceof Byte) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
if (value instanceof Short) {
|
||||
if (!array && (Short) value < Byte.MAX_VALUE) {
|
||||
return TIFF.TYPE_BYTE;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
if (!array && (Integer) value < Short.MAX_VALUE) {
|
||||
return TIFF.TYPE_SHORT;
|
||||
}
|
||||
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
if (!array && (Long) value < Integer.MAX_VALUE) {
|
||||
return TIFF.TYPE_LONG;
|
||||
}
|
||||
}
|
||||
|
||||
if (value instanceof Rational) {
|
||||
return TIFF.TYPE_RATIONAL;
|
||||
}
|
||||
|
||||
if (value instanceof String) {
|
||||
return TIFF.TYPE_ASCII;
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
|
||||
}
|
||||
|
||||
public TIFFEntry(final int identifier, final Object value) {
|
||||
this(identifier, guessType(value), value);
|
||||
}
|
||||
|
||||
TIFFEntry(int identifier, short type, Object value) {
|
||||
super(identifier, value);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||
*/
|
||||
public interface TIFFExtension {
|
||||
int ORIENTATION_TOPRIGHT = 2;
|
||||
int ORIENTATION_BOTRIGHT = 3;
|
||||
int ORIENTATION_BOTLEFT = 4;
|
||||
int ORIENTATION_LEFTTOP = 5;
|
||||
int ORIENTATION_RIGHTTOP = 6;
|
||||
int ORIENTATION_RIGHTBOT = 7;
|
||||
int ORIENTATION_LEFTBOT = 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||
*/
|
||||
public interface TIFFBaseline {
|
||||
int ORIENTATION_TOPLEFT = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.contrib.tiff;
|
||||
|
||||
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.xml.xpath.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TIFFUtilitiesTest
|
||||
*
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class TIFFUtilitiesTest {
|
||||
|
||||
@Test
|
||||
public void testMerge() throws IOException {
|
||||
// Files from ImageIO TIFF Plugin
|
||||
InputStream stream1 = getClassLoaderResource("/tiff/ccitt/group3_1d.tif").openStream();
|
||||
InputStream stream2 = getClassLoaderResource("/tiff/ccitt/group3_2d.tif").openStream();
|
||||
InputStream stream3 = getClassLoaderResource("/tiff/ccitt/group4.tif").openStream();
|
||||
|
||||
File file1 = File.createTempFile("imageiotest", ".tif");
|
||||
File file2 = File.createTempFile("imageiotest", ".tif");
|
||||
File file3 = File.createTempFile("imageiotest", ".tif");
|
||||
File output = File.createTempFile("imageiotest", ".tif");
|
||||
|
||||
byte[] data;
|
||||
|
||||
data = FileUtil.read(stream1);
|
||||
FileUtil.write(file1, data);
|
||||
stream1.close();
|
||||
|
||||
data = FileUtil.read(stream2);
|
||||
FileUtil.write(file2, data);
|
||||
stream2.close();
|
||||
|
||||
data = FileUtil.read(stream3);
|
||||
FileUtil.write(file3, data);
|
||||
stream3.close();
|
||||
|
||||
List<File> input = Arrays.asList(file1, file2, file3);
|
||||
TIFFUtilities.merge(input, output);
|
||||
|
||||
ImageInputStream iis = ImageIO.createImageInputStream(output);
|
||||
ImageReader reader = ImageIO.getImageReaders(iis).next();
|
||||
reader.setInput(iis);
|
||||
Assert.assertEquals(3, reader.getNumImages(true));
|
||||
|
||||
iis.close();
|
||||
output.delete();
|
||||
file1.delete();
|
||||
file2.delete();
|
||||
file3.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplit() throws IOException {
|
||||
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||
File inputFile = File.createTempFile("imageiotest", "tif");
|
||||
byte[] data = FileUtil.read(inputStream);
|
||||
FileUtil.write(inputFile, data);
|
||||
inputStream.close();
|
||||
|
||||
File outputDirectory = Files.createTempDirectory("imageio").toFile();
|
||||
|
||||
TIFFUtilities.split(inputFile, outputDirectory);
|
||||
|
||||
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
|
||||
|
||||
File[] outputFiles = outputDirectory.listFiles();
|
||||
Assert.assertEquals(3, outputFiles.length);
|
||||
for (File outputFile : outputFiles) {
|
||||
ImageInputStream iis = ImageIO.createImageInputStream(outputFile);
|
||||
reader.setInput(iis);
|
||||
Assert.assertEquals(1, reader.getNumImages(true));
|
||||
iis.close();
|
||||
outputFile.delete();
|
||||
}
|
||||
outputDirectory.delete();
|
||||
inputFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRotate() throws IOException, XPathExpressionException {
|
||||
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
|
||||
|
||||
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||
File inputFile = File.createTempFile("imageiotest", ".tif");
|
||||
byte[] data = FileUtil.read(inputStream);
|
||||
FileUtil.write(inputFile, data);
|
||||
inputStream.close();
|
||||
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
XPathExpression expression = xPath.compile("TIFFIFD/TIFFField[@number='274']/TIFFBytes/TIFFByte/@value");
|
||||
|
||||
// rotate all pages
|
||||
ImageInputStream inputTest1 = ImageIO.createImageInputStream(inputFile);
|
||||
File outputTest1 = File.createTempFile("imageiotest", ".tif");
|
||||
ImageOutputStream iosTest1 = ImageIO.createImageOutputStream(outputTest1);
|
||||
TIFFUtilities.rotatePages(inputTest1, iosTest1, 90);
|
||||
iosTest1.close();
|
||||
|
||||
ImageInputStream checkTest1 = ImageIO.createImageInputStream(outputTest1);
|
||||
reader.setInput(checkTest1);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Node metaData = reader.getImageMetadata(i)
|
||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||
}
|
||||
checkTest1.close();
|
||||
|
||||
// rotate single page further
|
||||
ImageInputStream inputTest2 = ImageIO.createImageInputStream(outputTest1);
|
||||
File outputTest2 = File.createTempFile("imageiotest", ".tif");
|
||||
ImageOutputStream iosTest2 = ImageIO.createImageOutputStream(outputTest2);
|
||||
TIFFUtilities.rotatePage(inputTest2, iosTest2, 90, 1);
|
||||
iosTest2.close();
|
||||
|
||||
ImageInputStream checkTest2 = ImageIO.createImageInputStream(outputTest2);
|
||||
reader.setInput(checkTest2);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Node metaData = reader.getImageMetadata(i)
|
||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||
Assert.assertEquals(orientation, i == 1
|
||||
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||
: TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||
}
|
||||
checkTest2.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApplyOrientation() throws IOException {
|
||||
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
|
||||
File inputFile = File.createTempFile("imageiotest", "tif");
|
||||
byte[] data = FileUtil.read(inputStream);
|
||||
FileUtil.write(inputFile, data);
|
||||
inputStream.close();
|
||||
|
||||
BufferedImage image = ImageIO.read(inputFile);
|
||||
|
||||
// rotate by 90ďż˝
|
||||
BufferedImage image90 = TIFFUtilities.applyOrientation(image, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||
// rotate by 270ďż˝
|
||||
BufferedImage image360 = TIFFUtilities.applyOrientation(image90, TIFFExtension.ORIENTATION_LEFTBOT);
|
||||
|
||||
byte[] original = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] rotated = ((DataBufferByte) image360.getData().getDataBuffer()).getData();
|
||||
|
||||
Assert.assertArrayEquals(original, rotated);
|
||||
}
|
||||
|
||||
protected URL getClassLoaderResource(final String pName) {
|
||||
return getClass().getResource(pName);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -26,51 +26,40 @@
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
<!-- Batik 1.6 contains a mysterious xml-apis:xml-apis:1.1.2 that doesn't exist, override with never version -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-rasterizer-ext</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<groupId>xml-apis</groupId>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
<version>1.3.04</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-extensions</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Nothing works without Xerces. Not sure why neither 1.6 or 1.8 versions needed this... -->
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-extension</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<groupId>xerces</groupId>
|
||||
<artifactId>xercesImpl</artifactId>
|
||||
<version>2.4.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>xmlgraphics-commons</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-anim</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<artifactId>batik-rasterizer</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-svggen</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-transcoder</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<version>1.6.1</version>
|
||||
<scope>provided</scope>
|
||||
|
||||
<!--
|
||||
@@ -86,8 +75,4 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<batik.version>1.8</batik.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
@@ -31,9 +31,9 @@ package com.twelvemonkeys.imageio.plugins.svg;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||
import org.apache.batik.bridge.*;
|
||||
import org.apache.batik.dom.svg.SVGDOMImplementation;
|
||||
import org.apache.batik.dom.svg.SVGOMDocument;
|
||||
import org.apache.batik.dom.util.DOMUtilities;
|
||||
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
||||
import org.apache.batik.gvt.CanvasGraphicsNode;
|
||||
@@ -392,7 +392,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
String ref = new ParsedURL(uri).getRef();
|
||||
|
||||
try {
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height);
|
||||
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
|
||||
@@ -52,7 +52,6 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
/**
|
||||
* Creates an {@code SVGImageReaderSpi}.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public SVGImageReaderSpi() {
|
||||
super(new SVGProviderInfo());
|
||||
}
|
||||
@@ -61,7 +60,6 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
return pSource instanceof ImageInputStream && SVG_READER_AVAILABLE && canDecode((ImageInputStream) pSource);
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
||||
// NOTE: This test is quite quick as it does not involve any parsing,
|
||||
// however it may not recognize all kinds of SVG documents.
|
||||
@@ -96,15 +94,15 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
if (buffer[0] == '?') {
|
||||
// This is the XML declaration or a processing instruction
|
||||
while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) {
|
||||
// Skip until end of XML declaration or processing instruction or EOF
|
||||
while (!(pInput.read() == '?' && pInput.read() == '>')) {
|
||||
// Skip until end of XML declaration or processing instruction
|
||||
}
|
||||
}
|
||||
else if (buffer[0] == '!') {
|
||||
if (buffer[1] == '-' && buffer[2] == '-') {
|
||||
// This is a comment
|
||||
while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
||||
// Skip until end of comment or EOF
|
||||
while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
||||
// Skip until end of comment
|
||||
}
|
||||
}
|
||||
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
||||
@@ -139,8 +137,8 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
while ((pInput.readByte() & 0xFF) != '<') {
|
||||
// Skip over, until next begin tag or EOF
|
||||
while (pInput.read() != '<') {
|
||||
// Skip over, until next begin tag
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +147,6 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
//noinspection ThrowFromFinallyBlock
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* SVGImageReaderSpiTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: SVGImageReaderSpiTest.java,v 1.0 08/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class SVGImageReaderSpiTest {
|
||||
|
||||
private static final String[] VALID_INPUTS = {
|
||||
"/svg/Android_robot.svg", // Minimal, no xml dec, no namespace
|
||||
"/svg/batikLogo.svg", // xml dec, comments, namespace
|
||||
"/svg/blue-square.svg", // xml dec, namespace
|
||||
"/svg/red-square.svg",
|
||||
};
|
||||
|
||||
private static final String[] INVALID_INPUTS = {
|
||||
"<xml>",
|
||||
"<",
|
||||
"<?",
|
||||
"<?1",
|
||||
"<?12",
|
||||
"<?123", // #275 Infinite loop issue
|
||||
"<!--",
|
||||
"<!-- ", // #275 Infinite loop issue
|
||||
"<?123?>", // #275 Infinite loop issue
|
||||
"<svg",
|
||||
};
|
||||
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
private final ImageReaderSpi provider = new SVGImageReaderSpi();
|
||||
|
||||
@Test
|
||||
public void canDecodeInput() throws Exception {
|
||||
for (String validInput : VALID_INPUTS) {
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(validInput))) {
|
||||
assertTrue("Can't read valid input: " + validInput, provider.canDecodeInput(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test will time out, if EOFs are not properly detected, see #275
|
||||
@Test(timeout = 5000)
|
||||
public void canDecodeInputInvalid() throws Exception {
|
||||
for (String invalidInput : INVALID_INPUTS) {
|
||||
try (ImageInputStream input = new ByteArrayImageInputStream(invalidInput.getBytes(StandardCharsets.UTF_8))) {
|
||||
assertFalse("Claims to read invalid input:" + invalidInput, provider.canDecodeInput(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,13 +120,6 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
super.testReadWithSourceRegionParamEqualImage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue: Subsampled reading not supported")
|
||||
@Override
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
super.testReadWithSubsampleParamPixels();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatedRead() throws IOException {
|
||||
Dimension dim = new Dimension(100, 100);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* SVGProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: SVGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class SVGProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new SVGProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,8 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Collections.singletonList(
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(133, 106))
|
||||
// TODO: Dimensions does not look right...
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(841, 673))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,11 +87,4 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
|
||||
public void testReadWithSourceRegionParamEqualImage() throws IOException {
|
||||
super.testReadWithSourceRegionParamEqualImage();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue: Subsampled reading not supported")
|
||||
@Override
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
super.testReadWithSubsampleParamPixels();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* WMFProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: WMFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class WMFProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new WMFProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
|
||||
@@ -160,15 +160,13 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (colors.length > 0) {
|
||||
// There might be more entries in the color map, but we ignore these for reading
|
||||
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
|
||||
// There might be more entries in the color map, but we ignore these for reading
|
||||
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
|
||||
|
||||
// Compute bits for > 8 bits (used only for meta data)
|
||||
int bits = header.getBitCount() <= 8 ? header.getBitCount() : mapSize <= 256 ? 8 : 16;
|
||||
// Compute bits for > 8 bits (used only for meta data)
|
||||
int bits = header.getBitCount() <= 8 ? header.getBitCount() : mapSize <= 256 ? 8 : 16;
|
||||
|
||||
colorMap = new IndexColorModel(bits, mapSize, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
colorMap = new IndexColorModel(bits, mapSize, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,14 +690,12 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
if (imageMetadata != null) {
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
} catch (Throwable t) {
|
||||
if (args.length > 1) {
|
||||
System.err.println("---");
|
||||
System.err.println("---> " + t.getClass().getSimpleName() + ": " + t.getMessage() + " for " + arg);
|
||||
System.err.println("---");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throwAs(RuntimeException.class, t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.event.IIOReadProgressListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -107,8 +108,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_24.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_32.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/24bitpalette.bmp"), new Dimension(320, 200))
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,58 +173,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore("Known issue: Subsampled reading is currently broken")
|
||||
@Test
|
||||
public void testReadWithSubsampleParamPixelsIndexed8() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
TestData data = getTestData().get(0);
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
|
||||
BufferedImage image = null;
|
||||
BufferedImage subsampled = null;
|
||||
try {
|
||||
image = reader.read(0, param);
|
||||
|
||||
param.setSourceSubsampling(2, 2, 0, 0);
|
||||
subsampled = reader.read(0, param);
|
||||
}
|
||||
catch (IOException e) {
|
||||
failBecause("Image could not be read", e);
|
||||
}
|
||||
|
||||
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
|
||||
}
|
||||
|
||||
// TODO: 1. Subsampling is currently broken, should fix it.
|
||||
// 2. BMPs are (normally) stored bottom/up, meaning y subsampling offsets will differ from normal
|
||||
// subsampling of the same data with an offset... Should we deal with this in the reader? Yes?
|
||||
@Ignore("Known issue: Subsampled reading is currently broken")
|
||||
@Test
|
||||
@Override
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
TestData data = getTestData().get(19); // RGB 24
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
|
||||
BufferedImage image = null;
|
||||
BufferedImage subsampled = null;
|
||||
try {
|
||||
image = reader.read(0, param);
|
||||
|
||||
param.setSourceSubsampling(2, 2, 0, 0);
|
||||
subsampled = reader.read(0, param);
|
||||
}
|
||||
catch (IOException e) {
|
||||
failBecause("Image could not be read", e);
|
||||
}
|
||||
|
||||
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testReadCorruptCausesIIOException() throws IOException {
|
||||
// See https://bugs.openjdk.java.net/browse/JDK-8066904
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* BMPProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: BMPProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class BMPProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new BMPProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -119,11 +119,4 @@ public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader>
|
||||
public void testNotBadCaching() throws IOException {
|
||||
super.testNotBadCaching();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue: Subsampled reading currently not supported")
|
||||
@Override
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
super.testReadWithSubsampleParamPixels();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* CURProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: CURProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class CURProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new CURProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -76,11 +76,4 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
|
||||
public void testNotBadCaching() throws IOException {
|
||||
super.testNotBadCaching();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue: Subsampled reading currently not supported")
|
||||
@Override
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
super.testReadWithSubsampleParamPixels();
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* ICOProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICOProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICOProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new ICOProviderInfo();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 188 KiB |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
@@ -100,8 +100,7 @@ public final class ColorSpaces {
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return an ICC color space
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}.
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
|
||||
/**
|
||||
* ComponentColorModel subclass that correctly handles full 16 bit {@code TYPE_SHORT} signed integral samples.
|
||||
**
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: UInt32ColorModel.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
public final class Int16ComponentColorModel extends ComponentColorModel {
|
||||
private final ComponentColorModel delegate;
|
||||
|
||||
public Int16ComponentColorModel(final ColorSpace cs, final boolean hasAlpha, boolean isAlphaPremultiplied) {
|
||||
super(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_SHORT);
|
||||
|
||||
delegate = new ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_USHORT);
|
||||
}
|
||||
|
||||
private void remap(final short[] s, final int i) {
|
||||
// MIN ... -1 -> 0 ... MAX
|
||||
// 0 ... MAX -> MIN ... -1
|
||||
short sample = s[i];
|
||||
|
||||
if (sample < 0) {
|
||||
s[i] = (short) (sample - Short.MIN_VALUE);
|
||||
}
|
||||
else {
|
||||
s[i] = (short) (sample + Short.MIN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(final Object inData) {
|
||||
remap((short[]) inData, 0);
|
||||
|
||||
return delegate.getRed(inData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(final Object inData) {
|
||||
remap((short[]) inData, 1);
|
||||
|
||||
return delegate.getGreen(inData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(final Object inData) {
|
||||
remap((short[]) inData, 2);
|
||||
|
||||
return delegate.getBlue(inData);
|
||||
}
|
||||
}
|
||||
@@ -47,30 +47,17 @@ public final class YCbCrConverter {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
||||
double y;
|
||||
double cb;
|
||||
double cr;
|
||||
|
||||
if (referenceBW == null) {
|
||||
// Default case
|
||||
y = (yCbCr[offset] & 0xff);
|
||||
cb = (yCbCr[offset + 1] & 0xff) - 128;
|
||||
cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||
}
|
||||
else {
|
||||
// Custom values
|
||||
y = ((yCbCr[offset] & 0xff) - referenceBW[0]) * 255.0 / (referenceBW[1] - referenceBW[0]);
|
||||
cb = ((yCbCr[offset + 1] & 0xff) - referenceBW[2]) * 127.0 / (referenceBW[3] - referenceBW[2]);
|
||||
cr = ((yCbCr[offset + 2] & 0xff) - referenceBW[4]) * 127.0 / (referenceBW[5] - referenceBW[4]);
|
||||
}
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||
double y = (yCbCr[offset] & 0xff);
|
||||
double cb = (yCbCr[offset + 1] & 0xff) - 128;
|
||||
double cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
int red = (int) Math.round(cr * (2.0 - 2.0 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2.0 - 2.0 * lumaBlue) + y);
|
||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||
int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
|
||||
|
||||
rgb[offset] = clamp(red);
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
private final String[] writerSpiClassNames;
|
||||
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
|
||||
private final boolean supportsStandardStreamMetadata;
|
||||
private final String nativeStreamMetadataFormatName;
|
||||
private final String nativeStreameMetadataFormatName;
|
||||
private final String nativeStreamMetadataFormatClassName;
|
||||
private final String[] extraStreamMetadataFormatNames;
|
||||
private final String[] extraStreamMetadataFormatClassNames;
|
||||
@@ -97,7 +97,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
this.writerClassName = writerClassName;
|
||||
this.writerSpiClassNames = writerSpiClassNames;
|
||||
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
|
||||
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreameMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
||||
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
||||
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
||||
@@ -149,7 +149,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatName() {
|
||||
return nativeStreamMetadataFormatName;
|
||||
return nativeStreameMetadataFormatName;
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatClassName() {
|
||||
|
||||
@@ -5,8 +5,7 @@ import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* ImageInputStream that writes through a delegate, but keeps local position and bit offset.
|
||||
* Note: Flushing or closing this stream will *not* have an effect on the delegate.
|
||||
* SubImageOutputStream.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
|
||||
@@ -28,19 +28,14 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* Factory class for creating {@code ImageTypeSpecifier}s.
|
||||
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
|
||||
* in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
|
||||
* In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
|
||||
*
|
||||
* @see javax.imageio.ImageTypeSpecifier
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -59,20 +54,6 @@ public final class ImageTypeSpecifiers {
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
@@ -98,11 +79,7 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) {
|
||||
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
|
||||
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
|
||||
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
|
||||
}
|
||||
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
|
||||
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
|
||||
}
|
||||
@@ -112,11 +89,7 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) {
|
||||
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
|
||||
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
|
||||
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
|
||||
}
|
||||
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
|
||||
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
|
||||
}
|
||||
@@ -125,34 +98,6 @@ public final class ImageTypeSpecifiers {
|
||||
return ImageTypeSpecifier.createGrayscale(bits, dataType, false, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createPackedGrayscale(final ColorSpace colorSpace, final int bits, final int dataType) {
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_GRAY, colorSpace, "ColorSpace must be TYPE_GRAY");
|
||||
isTrue(bits == 1 || bits == 2 || bits == 4, bits, "bits must be 1, 2, or 4: %s");
|
||||
isTrue(dataType == DataBuffer.TYPE_BYTE, dataType, "dataType must be TYPE_BYTE: %s");
|
||||
|
||||
int numEntries = 1 << bits;
|
||||
|
||||
byte[] arr = new byte[numEntries];
|
||||
byte[] arg = new byte[numEntries];
|
||||
byte[] arb = new byte[numEntries];
|
||||
|
||||
// Scale array values according to color profile..
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
float[] gray = new float[]{i / (float) (numEntries - 1)};
|
||||
float[] rgb = colorSpace.toRGB(gray);
|
||||
|
||||
arr[i] = (byte) (rgb[0] * 255);
|
||||
arg[i] = (byte) (rgb[1] * 255);
|
||||
arb[i] = (byte) (rgb[2]* 255);
|
||||
}
|
||||
|
||||
ColorModel colorModel = new IndexColorModel(bits, numEntries, arr, arg, arb);
|
||||
SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, sampleModel);
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createIndexed(final byte[] redLUT, final byte[] greenLUT,
|
||||
final byte[] blueLUT, final byte[] alphaLUT,
|
||||
final int bits, final int dataType) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.Int16ComponentColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.PixelInterleavedSampleModel;
|
||||
|
||||
/**
|
||||
* ImageTypeSpecifier for interleaved 16 bit signed integral samples.
|
||||
*
|
||||
* @see com.twelvemonkeys.imageio.color.Int16ColorModel
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
final class Int16ImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
Int16ImageTypeSpecifier(final ColorSpace cs, int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
|
||||
super(
|
||||
new Int16ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied),
|
||||
new PixelInterleavedSampleModel(
|
||||
DataBuffer.TYPE_SHORT, 1, 1,
|
||||
cs.getNumComponents() + (hasAlpha ? 1 : 0),
|
||||
cs.getNumComponents() + (hasAlpha ? 1 : 0),
|
||||
bandOffsets
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.TypeSafeMatcher;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadataFormat;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ReaderWriterProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ReaderWriterProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public abstract class ReaderWriterProviderInfoTest {
|
||||
|
||||
private final ReaderWriterProviderInfo providerInfo = createProviderInfo();
|
||||
|
||||
protected abstract ReaderWriterProviderInfo createProviderInfo();
|
||||
|
||||
protected final ReaderWriterProviderInfo getProviderInfo() {
|
||||
return providerInfo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readerClassName() throws Exception {
|
||||
assertClassExists(providerInfo.readerClassName(), ImageReader.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readerSpiClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.readerSpiClassNames(), ImageReaderSpi.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputTypes() throws Exception {
|
||||
assertNotNull(providerInfo.inputTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writerClassName() throws Exception {
|
||||
assertClassExists(providerInfo.writerClassName(), ImageWriter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writerSpiClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.writerSpiClassNames(), ImageWriterSpi.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outputTypes() throws Exception {
|
||||
assertNotNull(providerInfo.outputTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeStreamMetadataFormatClassName() throws Exception {
|
||||
assertClassExists(providerInfo.nativeStreamMetadataFormatClassName(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extraStreamMetadataFormatClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.extraStreamMetadataFormatClassNames(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeImageMetadataFormatClassName() throws Exception {
|
||||
assertClassExists(providerInfo.nativeImageMetadataFormatClassName(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extraImageMetadataFormatClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.extraImageMetadataFormatClassNames(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatNames() {
|
||||
String[] names = providerInfo.formatNames();
|
||||
assertNotNull(names);
|
||||
assertFalse(names.length == 0);
|
||||
|
||||
List<String> list = asList(names);
|
||||
|
||||
for (String name : list) {
|
||||
assertNotNull(name);
|
||||
assertFalse(name.isEmpty());
|
||||
|
||||
assertTrue(list.contains(name.toLowerCase()));
|
||||
assertTrue(list.contains(name.toUpperCase()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suffixes() {
|
||||
String[] suffixes = providerInfo.suffixes();
|
||||
assertNotNull(suffixes);
|
||||
assertFalse(suffixes.length == 0);
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
assertNotNull(suffix);
|
||||
assertFalse(suffix.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mimeTypes() {
|
||||
String[] mimeTypes = providerInfo.mimeTypes();
|
||||
assertNotNull(mimeTypes);
|
||||
assertFalse(mimeTypes.length == 0);
|
||||
|
||||
for (String mimeType : mimeTypes) {
|
||||
assertNotNull(mimeType);
|
||||
assertFalse(mimeType.isEmpty());
|
||||
|
||||
assertTrue(mimeType.length() > 1);
|
||||
assertTrue(mimeType.indexOf('/') > 0);
|
||||
assertTrue(mimeType.indexOf('/') < mimeType.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertClassExists(final String className, final Class<T> type) {
|
||||
if (className != null) {
|
||||
try {
|
||||
final Class<?> cl = Class.forName(className);
|
||||
|
||||
assertThat(cl, new TypeSafeMatcher<Class<?>>() {
|
||||
@Override
|
||||
public boolean matchesSafely(Class<?> item) {
|
||||
return type.isAssignableFrom(cl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("is subclass of ").appendValue(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
fail("Class not found: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertClassesExist(final String[] classNames, final Class<T> type) {
|
||||
if (classNames != null) {
|
||||
for (String className : classNames) {
|
||||
assertClassExists(className, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -45,7 +46,6 @@ import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
@@ -98,7 +98,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static void failBecause(String message, Throwable exception) {
|
||||
private static void failBecause(String message, Throwable exception) {
|
||||
AssertionError error = new AssertionError(message);
|
||||
error.initCause(exception);
|
||||
throw error;
|
||||
@@ -471,6 +471,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
assertEquals("Read image has wrong height: ", (data.getDimension(0).height + 4) / 5, image.getHeight());
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
@@ -478,66 +479,28 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRegion(new Rectangle(Math.min(100, reader.getWidth(0)), Math.min(100, reader.getHeight(0))));
|
||||
|
||||
BufferedImage image = null;
|
||||
BufferedImage subsampled = null;
|
||||
try {
|
||||
image = reader.read(0, param);
|
||||
param.setSourceSubsampling(2, 2, 1, 1); // Hmm.. Seems to be the offset the fake version (ReplicateScaleFilter) uses
|
||||
|
||||
param.setSourceSubsampling(2, 2, 0, 0);
|
||||
subsampled = reader.read(0, param);
|
||||
}
|
||||
catch (IOException e) {
|
||||
failBecause("Image could not be read", e);
|
||||
}
|
||||
|
||||
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
|
||||
}
|
||||
BufferedImage expected = ImageUtil.toBuffered(IIOUtil.fakeSubsampling(image, param));
|
||||
|
||||
// TODO: Subsample all test data
|
||||
// TODO: Subsample with varying ratios and offsets
|
||||
// JPanel panel = new JPanel();
|
||||
// panel.add(new JLabel("Expected", new BufferedImageIcon(expected, 300, 300), JLabel.CENTER));
|
||||
// panel.add(new JLabel("Actual", new BufferedImageIcon(subsampled, 300, 300), JLabel.CENTER));
|
||||
// JOptionPane.showConfirmDialog(null, panel);
|
||||
|
||||
protected final void assertSubsampledImageDataEquals(String message, BufferedImage expected, BufferedImage actual, ImageReadParam param) throws IOException {
|
||||
assertNotNull("Expected image was null", expected);
|
||||
assertNotNull("Actual image was null!", actual);
|
||||
|
||||
if (expected == actual) {
|
||||
return;
|
||||
}
|
||||
|
||||
int xOff = param.getSubsamplingXOffset();
|
||||
int yOff = param.getSubsamplingYOffset();
|
||||
int xSub = param.getSourceXSubsampling();
|
||||
int ySub = param.getSourceYSubsampling();
|
||||
|
||||
assertEquals("Subsampled image has wrong width: ", (expected.getWidth() - xOff + xSub - 1) / xSub, actual.getWidth());
|
||||
assertEquals("Subsampled image has wrong height: ", (expected.getHeight() - yOff + ySub - 1) / ySub, actual.getHeight());
|
||||
assertEquals("Subsampled has different type", expected.getType(), actual.getType());
|
||||
|
||||
for (int y = 0; y < actual.getHeight(); y++) {
|
||||
for (int x = 0; x < actual.getWidth(); x++) {
|
||||
int expectedRGB = expected.getRGB(xOff + x * xSub, yOff + y * ySub);
|
||||
int actualRGB = actual.getRGB(x, y);
|
||||
|
||||
try {
|
||||
assertEquals(String.format("%s alpha at (%d, %d)", message, x, y), (expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 5);
|
||||
assertEquals(String.format("%s red at (%d, %d)", message, x, y), (expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5);
|
||||
assertEquals(String.format("%s green at (%d, %d)", message, x, y), (expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5);
|
||||
assertEquals(String.format("%s blue at (%d, %d)", message, x, y), expectedRGB & 0xff, actualRGB & 0xff, 5);
|
||||
}
|
||||
catch (AssertionError e) {
|
||||
File tempExpected = File.createTempFile("junit-expected-", ".png");
|
||||
System.err.println("tempExpected.getAbsolutePath(): " + tempExpected.getAbsolutePath());
|
||||
ImageIO.write(expected, "PNG", tempExpected);
|
||||
File tempActual = File.createTempFile("junit-actual-", ".png");
|
||||
System.err.println("tempActual.getAbsolutePath(): " + tempActual.getAbsolutePath());
|
||||
ImageIO.write(actual, "PNG", tempActual);
|
||||
|
||||
|
||||
assertEquals(String.format("%s ARGB at (%d, %d)", message, x, y), String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB));
|
||||
}
|
||||
}
|
||||
}
|
||||
assertImageDataEquals("Subsampled image data does not match expected", expected, subsampled);
|
||||
}
|
||||
|
||||
protected final void assertImageDataEquals(String message, BufferedImage expected, BufferedImage actual) {
|
||||
@@ -1639,21 +1602,6 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
return getClass().getResource(pName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slightly fuzzy RGB equals method. Variable tolerance.
|
||||
*/
|
||||
public static void assertRGBEquals(String message, int expectedRGB, int actualRGB, int tolerance) {
|
||||
try {
|
||||
assertEquals((expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
|
||||
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
|
||||
assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, tolerance);
|
||||
assertEquals((expectedRGB ) & 0xff, (actualRGB ) & 0xff, tolerance);
|
||||
}
|
||||
catch (AssertionError e) {
|
||||
assertEquals(message, String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB));
|
||||
}
|
||||
}
|
||||
|
||||
static final protected class TestData {
|
||||
private final Object input;
|
||||
private final List<Dimension> sizes;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -39,7 +40,7 @@ public class ImageTypeSpecifiersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked32() {
|
||||
public void testCreatePacked() {
|
||||
// TYPE_INT_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
@@ -60,70 +61,31 @@ public class ImageTypeSpecifiersTest {
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked16() {
|
||||
// TYPE_USHORT_555_RGB
|
||||
assertEquals(
|
||||
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
|
||||
|
||||
// "SHORT 555 RGB" (impossible for some reason)
|
||||
// assertEquals(
|
||||
// ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false),
|
||||
// ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false)
|
||||
// );
|
||||
// TYPE_USHORT_565_RGB
|
||||
assertEquals(
|
||||
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB PRE"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
|
||||
);
|
||||
|
||||
// Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32)
|
||||
assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked8() {
|
||||
// "BYTE 332 RGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB PRE"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
|
||||
);
|
||||
|
||||
// Extra: Make sure color models bits is actually 8 (ImageTypeSpecifiers equivalent returns 32)
|
||||
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
|
||||
final int redMask, final int greenMask, final int blueMask, final int alphaMask,
|
||||
final int transferType, final boolean isAlphaPremultiplied) {
|
||||
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel =
|
||||
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -375,7 +337,11 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false),
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false), // NOTE: Unsigned TYPE_SHORT makes no sense...
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
|
||||
);
|
||||
}
|
||||
@@ -434,11 +400,19 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false),
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
|
||||
);
|
||||
assertEquals(
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true),
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
|
||||
);
|
||||
}
|
||||
@@ -463,30 +437,6 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale1() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 1, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale2() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(2, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 2, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale4() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 4, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateIndexedByteArrays1to8() {
|
||||
for (int bits = 1; bits <= 8; bits <<= 1) {
|
||||
@@ -612,6 +562,7 @@ public class ImageTypeSpecifiersTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static byte[] createByteLut(final int count) {
|
||||
byte[] lut = new byte[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* HDRProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class HDRProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new HDRProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
|
||||
@@ -47,7 +47,7 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo {
|
||||
"image/x-apple-icons", // Common extension MIME
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.ics.ICNImageReaderSpi"},
|
||||
null, null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* ICNSProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICNSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICNSProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new ICNSProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* IFFProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: IFFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class IFFProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new IFFProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
Copyright (c) 2016, Harald Kuhr
|
||||
Copyright (C) 2015, Michael Martinez (JPEG Lossless decoder)
|
||||
Copyright (C) 2004, Helmut Dersch (Java JPEG decoder)
|
||||
Copyright (c) 2013, Harald Kuhr
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
|
||||
@@ -28,11 +28,6 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* AdobeDCTSegment
|
||||
*
|
||||
@@ -40,44 +35,44 @@ import java.io.IOException;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
||||
*/
|
||||
final class AdobeDCT extends Application {
|
||||
static final int Unknown = 0;
|
||||
static final int YCC = 1;
|
||||
static final int YCCK = 2;
|
||||
class AdobeDCTSegment {
|
||||
public static final int Unknown = 0;
|
||||
public static final int YCC = 1;
|
||||
public static final int YCCK = 2;
|
||||
|
||||
final int version;
|
||||
final int flags0;
|
||||
final int flags1;
|
||||
final int transform;
|
||||
|
||||
private AdobeDCT(int version, int flags0, int flags1, int transform) {
|
||||
super(JPEG.APP14, "Adobe", new byte[]{'A', 'd', 'o', 'b', 'e', 0, (byte) version, (byte) (flags0 >> 8), (byte) (flags0 & 0xff), (byte) (flags1 >> 8), (byte) (flags1 & 0xff), (byte) transform});
|
||||
|
||||
AdobeDCTSegment(int version, int flags0, int flags1, int transform) {
|
||||
this.version = version; // 100 or 101
|
||||
this.flags0 = flags0;
|
||||
this.flags1 = flags1;
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public int getFlags0() {
|
||||
return flags0;
|
||||
}
|
||||
|
||||
public int getFlags1() {
|
||||
return flags1;
|
||||
}
|
||||
|
||||
public int getTransform() {
|
||||
return transform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"AdobeDCT[ver: %d.%02d, flags: %s %s, transform: %d]",
|
||||
version / 100, version % 100, Integer.toBinaryString(flags0), Integer.toBinaryString(flags1), transform
|
||||
);
|
||||
}
|
||||
|
||||
public static AdobeDCT read(final DataInput data, final int length) throws IOException {
|
||||
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
|
||||
|
||||
data.skipBytes(6); // A, d, o, b, e, \0
|
||||
|
||||
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
|
||||
return new AdobeDCT(
|
||||
data.readUnsignedByte(),
|
||||
data.readUnsignedShort(),
|
||||
data.readUnsignedShort(),
|
||||
data.readUnsignedByte()
|
||||
getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Application.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Application.java,v 1.0 22/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class Application extends Segment {
|
||||
|
||||
final String identifier;
|
||||
final byte[] data;
|
||||
|
||||
Application(final int marker, final String identifier, final byte[] data) {
|
||||
super(marker);
|
||||
|
||||
this.identifier = identifier; // NOTE: Some JPEGs contain APP segments without NULL-terminated identifier
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
InputStream data() {
|
||||
int offset = identifier.length() + 1;
|
||||
return new ByteArrayInputStream(data, offset, data.length - offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "APP" + (marker & 0x0f) + "/" + identifier + "[length: " + data.length + "]";
|
||||
}
|
||||
|
||||
public static Application read(final int marker, final String identifier, final DataInput data, final int length) throws IOException {
|
||||
switch (marker) {
|
||||
case JPEG.APP0:
|
||||
// JFIF
|
||||
if ("JFIF".equals(identifier)) {
|
||||
return JFIF.read(data, length);
|
||||
}
|
||||
case JPEG.APP1:
|
||||
// JFXX
|
||||
if ("JFXX".equals(identifier)) {
|
||||
return JFXX.read(data, length);
|
||||
}
|
||||
// TODO: Exif?
|
||||
case JPEG.APP2:
|
||||
// ICC_PROFILE
|
||||
if ("ICC_PROFILE".equals(identifier)) {
|
||||
return ICCProfile.read(data, length);
|
||||
}
|
||||
case JPEG.APP14:
|
||||
// Adobe
|
||||
if ("Adobe".equals(identifier)) {
|
||||
return AdobeDCT.read(data, length);
|
||||
}
|
||||
|
||||
default:
|
||||
// Generic APPn segment
|
||||
byte[] bytes = new byte[length - 2];
|
||||
data.readFully(bytes);
|
||||
return new Application(marker, identifier, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
|
||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||
|
||||
EXIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final Directory ifd, final ImageInputStream stream) {
|
||||
public EXIFThumbnailReader(ThumbnailReadProgressListener progressListener, ImageReader jpegReader, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
|
||||
super(progressListener, imageIndex, thumbnailIndex);
|
||||
this.reader = Validate.notNull(jpegReader);
|
||||
this.ifd = ifd;
|
||||
@@ -96,7 +96,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage readJPEGCached(final boolean pixelsExposed) throws IOException {
|
||||
private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException {
|
||||
BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null;
|
||||
|
||||
if (thumbnail == null) {
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Frame
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$
|
||||
*/
|
||||
final class Frame extends Segment {
|
||||
final int samplePrecision; // Sample precision
|
||||
final int lines; // Height
|
||||
final int samplesPerLine; // Width
|
||||
|
||||
final Component[] components; // Components specifications
|
||||
|
||||
private Frame(final int marker, final int samplePrecision, final int lines, final int samplesPerLine, final Component[] components) {
|
||||
super(marker);
|
||||
|
||||
this.samplePrecision = samplePrecision;
|
||||
this.lines = lines;
|
||||
this.samplesPerLine = samplesPerLine;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
int process() {
|
||||
return marker & 0xff - 0xc0;
|
||||
}
|
||||
|
||||
int componentsInFrame() {
|
||||
return components.length;
|
||||
}
|
||||
|
||||
Component getComponent(final int id) {
|
||||
for (Component component : components) {
|
||||
if (component.id == id) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("No such component id: %d", id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
||||
process(), marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
||||
);
|
||||
}
|
||||
|
||||
static Frame read(final int marker, final DataInput data, final int length) throws IOException {
|
||||
int samplePrecision = data.readUnsignedByte();
|
||||
int lines = data.readUnsignedShort();
|
||||
int samplesPerLine = data.readUnsignedShort();
|
||||
int componentsInFrame = data.readUnsignedByte();
|
||||
|
||||
int expected = 8 + componentsInFrame * 3;
|
||||
if (length != expected) {
|
||||
throw new IIOException(String.format("Unexpected SOF length: %d != %d", length, expected));
|
||||
}
|
||||
|
||||
Component[] components = new Component[componentsInFrame];
|
||||
|
||||
for (int i = 0; i < componentsInFrame; i++) {
|
||||
int id = data.readUnsignedByte();
|
||||
int sub = data.readUnsignedByte();
|
||||
int qtSel = data.readUnsignedByte();
|
||||
|
||||
components[i] = new Component(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
||||
}
|
||||
|
||||
return new Frame(marker, samplePrecision, lines, samplesPerLine, components);
|
||||
}
|
||||
|
||||
static Frame read(final int marker, final ImageInputStream data) throws IOException {
|
||||
int length = data.readUnsignedShort();
|
||||
|
||||
return read(marker, new SubImageInputStream(data, length), length);
|
||||
}
|
||||
|
||||
public static final class Component {
|
||||
final int id;
|
||||
final int hSub; // Horizontal sampling factor
|
||||
final int vSub; // Vertical sampling factor
|
||||
final int qtSel; // Quantization table destination selector
|
||||
|
||||
Component(int id, int hSub, int vSub, int qtSel) {
|
||||
this.id = id;
|
||||
this.hSub = hSub;
|
||||
this.vSub = vSub;
|
||||
this.qtSel = qtSel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Use id either as component number or component name, based on value
|
||||
Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id;
|
||||
return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (C) 2015, Michael Martinez
|
||||
* Copyright (C) 2004, Helmut Dersch
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
final class HuffmanTable extends Segment {
|
||||
|
||||
private final int l[][][] = new int[4][2][16];
|
||||
private final int th[] = new int[4]; // 1: this table is present
|
||||
final int v[][][][] = new int[4][2][16][200]; // tables
|
||||
final int[][] tc = new int[4][2]; // 1: this table is present
|
||||
|
||||
static final int MSB = 0x80000000;
|
||||
|
||||
private HuffmanTable() {
|
||||
super(JPEG.DHT);
|
||||
}
|
||||
|
||||
void buildHuffTables(final int[][][] HuffTab) throws IOException {
|
||||
for (int t = 0; t < 4; t++) {
|
||||
for (int c = 0; c < 2; c++) {
|
||||
if (tc[t][c] != 0) {
|
||||
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build_HuffTab()
|
||||
// Parameter: t table ID
|
||||
// c table class ( 0 for DC, 1 for AC )
|
||||
// L[i] # of codewords which length is i
|
||||
// V[i][j] Huffman Value (length=i)
|
||||
// Effect:
|
||||
// build up HuffTab[t][c] using L and V.
|
||||
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
|
||||
int temp = 256;
|
||||
int k = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) { // i+1 is Code length
|
||||
for (int j = 0; j < L[i]; j++) {
|
||||
for (int n = 0; n < (temp >> (i + 1)); n++) {
|
||||
tab[k] = V[i][j] | ((i + 1) << 8);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; k < 256; i++, k++) {
|
||||
tab[k] = i | MSB;
|
||||
}
|
||||
|
||||
int currentTable = 1;
|
||||
k = 0;
|
||||
|
||||
for (int i = 8; i < 16; i++) { // i+1 is Code length
|
||||
for (int j = 0; j < L[i]; j++) {
|
||||
for (int n = 0; n < (temp >> (i - 7)); n++) {
|
||||
tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
|
||||
k++;
|
||||
}
|
||||
if (k >= 256) {
|
||||
if (k > 256) {
|
||||
throw new IIOException("JPEG Huffman Table error");
|
||||
}
|
||||
|
||||
k = 0;
|
||||
currentTable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("DHT[");
|
||||
|
||||
for (int t = 0; t < tc.length; t++) {
|
||||
for (int c = 0; c < tc[t].length; c++) {
|
||||
if (tc[t][c] != 0) {
|
||||
if (builder.length() > 4) {
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
builder.append("id: ");
|
||||
builder.append(t);
|
||||
|
||||
builder.append(", class: ");
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(']');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Segment read(final DataInput data, final int length) throws IOException {
|
||||
int count = 2;
|
||||
|
||||
HuffmanTable table = new HuffmanTable();
|
||||
|
||||
while (count < length) {
|
||||
int temp = data.readUnsignedByte();
|
||||
count++;
|
||||
int t = temp & 0x0F;
|
||||
if (t > 3) {
|
||||
throw new IIOException("Unexpected JPEG Huffman Table Id (> 3):" + t);
|
||||
}
|
||||
|
||||
int c = temp >> 4;
|
||||
if (c > 2) {
|
||||
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
|
||||
}
|
||||
|
||||
table.th[t] = 1;
|
||||
table.tc[t][c] = 1;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
table.l[t][c][i] = data.readUnsignedByte();
|
||||
count++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < table.l[t][c][i]; j++) {
|
||||
if (count > length) {
|
||||
throw new IIOException("JPEG Huffman Table format error");
|
||||
}
|
||||
table.v[t][c][i][j] = data.readUnsignedByte();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count != length) {
|
||||
throw new IIOException("JPEG Huffman Table format error, bad segment length: " + length);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* ICCProfile.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICCProfile.java,v 1.0 22/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
final class ICCProfile extends Application {
|
||||
private ICCProfile(final byte[] data) {
|
||||
super(JPEG.APP2, "ICC_PROFILE", data);
|
||||
}
|
||||
|
||||
// TODO: Create util method to concat all ICC segments to one and return ICC_Profile (move from JPEGImageReader)
|
||||
// If so, how to deal with warnings from the original code? Throw exceptions instead?
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ICC_PROFILE[" + data[0] + "/" + data[1] + " length: " + data.length + "]";
|
||||
}
|
||||
|
||||
public static ICCProfile read(DataInput data, int length) throws IOException {
|
||||
byte[] bytes = new byte[length - 2];
|
||||
data.readFully(bytes);
|
||||
|
||||
return new ICCProfile(bytes);
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* JFIFSegment
|
||||
@@ -40,7 +39,7 @@ import java.nio.ByteBuffer;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
|
||||
*/
|
||||
final class JFIF extends Application {
|
||||
class JFIFSegment {
|
||||
final int majorVersion;
|
||||
final int minorVersion;
|
||||
final int units;
|
||||
@@ -50,9 +49,7 @@ final class JFIF extends Application {
|
||||
final int yThumbnail;
|
||||
final byte[] thumbnail;
|
||||
|
||||
private JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail, byte[] data) {
|
||||
super(JPEG.APP0, "JFIF", data);
|
||||
|
||||
private JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
|
||||
this.majorVersion = majorVersion;
|
||||
this.minorVersion = minorVersion;
|
||||
this.units = units;
|
||||
@@ -89,41 +86,20 @@ final class JFIF extends Application {
|
||||
return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
|
||||
}
|
||||
|
||||
public static JFIF read(final DataInput data, int length) throws IOException {
|
||||
if (length < 2 + 5 + 9) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
data.readFully(new byte[5]);
|
||||
|
||||
byte[] bytes = new byte[length - 2 - 5];
|
||||
data.readFully(bytes);
|
||||
public static JFIFSegment read(final InputStream data) throws IOException {
|
||||
DataInputStream stream = new DataInputStream(data);
|
||||
|
||||
int x, y;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
|
||||
return new JFIF(
|
||||
buffer.get() & 0xff,
|
||||
buffer.get() & 0xff,
|
||||
buffer.get() & 0xff,
|
||||
buffer.getShort() & 0xffff,
|
||||
buffer.getShort() & 0xffff,
|
||||
x = buffer.get() & 0xff,
|
||||
y = buffer.get() & 0xff,
|
||||
getBytes(buffer, x * y * 3),
|
||||
bytes
|
||||
return new JFIFSegment(
|
||||
stream.readUnsignedByte(),
|
||||
stream.readUnsignedByte(),
|
||||
stream.readUnsignedByte(),
|
||||
stream.readUnsignedShort(),
|
||||
stream.readUnsignedShort(),
|
||||
x = stream.readUnsignedByte(),
|
||||
y = stream.readUnsignedByte(),
|
||||
JPEGImageReader.readFully(stream, x * y * 3)
|
||||
);
|
||||
}
|
||||
|
||||
private static byte[] getBytes(ByteBuffer buffer, int len) {
|
||||
if (len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] dst = new byte[len];
|
||||
buffer.get(dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ import java.io.IOException;
|
||||
* @version $Id: JFIFThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$
|
||||
*/
|
||||
final class JFIFThumbnailReader extends ThumbnailReader {
|
||||
private final JFIF segment;
|
||||
private final JFIFSegment segment;
|
||||
|
||||
JFIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final JFIF segment) {
|
||||
public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
|
||||
super(progressListener, imageIndex, thumbnailIndex);
|
||||
this.segment = segment;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* JFXXSegment
|
||||
@@ -39,7 +39,7 @@ import java.util.Arrays;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
|
||||
*/
|
||||
final class JFXX extends Application {
|
||||
class JFXXSegment {
|
||||
public static final int JPEG = 0x10;
|
||||
public static final int INDEXED = 0x11;
|
||||
public static final int RGB = 0x13;
|
||||
@@ -47,9 +47,7 @@ final class JFXX extends Application {
|
||||
final int extensionCode;
|
||||
final byte[] thumbnail;
|
||||
|
||||
private JFXX(final int extensionCode, final byte[] thumbnail, final byte[] data) {
|
||||
super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", data);
|
||||
|
||||
private JFXXSegment(int extensionCode, byte[] thumbnail) {
|
||||
this.extensionCode = extensionCode;
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
@@ -72,16 +70,12 @@ final class JFXX extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public static JFXX read(final DataInput data, final int length) throws IOException {
|
||||
data.readFully(new byte[5]);
|
||||
public static JFXXSegment read(InputStream data, int length) throws IOException {
|
||||
DataInputStream stream = new DataInputStream(data);
|
||||
|
||||
byte[] bytes = new byte[length - 2 - 5];
|
||||
data.readFully(bytes);
|
||||
|
||||
return new JFXX(
|
||||
bytes[0] & 0xff,
|
||||
bytes.length - 1 > 0 ? Arrays.copyOfRange(bytes, 1, bytes.length - 1) : null,
|
||||
bytes
|
||||
return new JFXXSegment(
|
||||
stream.readUnsignedByte(),
|
||||
JPEGImageReader.readFully(stream, length - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -50,11 +50,11 @@ import java.lang.ref.SoftReference;
|
||||
final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
|
||||
private final ImageReader reader;
|
||||
private final JFXX segment;
|
||||
private final JFXXSegment segment;
|
||||
|
||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||
|
||||
JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXX segment) {
|
||||
protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
|
||||
super(progressListener, imageIndex, thumbnailIndex);
|
||||
this.reader = Validate.notNull(jpegReader);
|
||||
this.segment = segment;
|
||||
@@ -66,13 +66,13 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
|
||||
BufferedImage thumbnail;
|
||||
switch (segment.extensionCode) {
|
||||
case JFXX.JPEG:
|
||||
case JFXXSegment.JPEG:
|
||||
thumbnail = readJPEGCached(true);
|
||||
break;
|
||||
case JFXX.INDEXED:
|
||||
case JFXXSegment.INDEXED:
|
||||
thumbnail = readIndexed();
|
||||
break;
|
||||
case JFXX.RGB:
|
||||
case JFXXSegment.RGB:
|
||||
thumbnail = readRGB();
|
||||
break;
|
||||
default:
|
||||
@@ -85,7 +85,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
IIOMetadata readMetadata() throws IOException {
|
||||
public IIOMetadata readMetadata() throws IOException {
|
||||
ImageInputStream input = new ByteArrayImageInputStream(segment.thumbnail);
|
||||
|
||||
try {
|
||||
@@ -119,10 +119,10 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
@Override
|
||||
public int getWidth() throws IOException {
|
||||
switch (segment.extensionCode) {
|
||||
case JFXX.RGB:
|
||||
case JFXX.INDEXED:
|
||||
case JFXXSegment.RGB:
|
||||
case JFXXSegment.INDEXED:
|
||||
return segment.thumbnail[0] & 0xff;
|
||||
case JFXX.JPEG:
|
||||
case JFXXSegment.JPEG:
|
||||
return readJPEGCached(false).getWidth();
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
|
||||
@@ -132,10 +132,10 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
@Override
|
||||
public int getHeight() throws IOException {
|
||||
switch (segment.extensionCode) {
|
||||
case JFXX.RGB:
|
||||
case JFXX.INDEXED:
|
||||
case JFXXSegment.RGB:
|
||||
case JFXXSegment.INDEXED:
|
||||
return segment.thumbnail[1] & 0xff;
|
||||
case JFXX.JPEG:
|
||||
case JFXXSegment.JPEG:
|
||||
return readJPEGCached(false).getHeight();
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JPEGImage10Metadata.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: JPEGImage10Metadata.java,v 1.0 10/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class JPEGImage10Metadata extends AbstractMetadata {
|
||||
|
||||
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place....
|
||||
|
||||
private final List<Segment> segments;
|
||||
|
||||
JPEGImage10Metadata(List<Segment> segments) {
|
||||
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
|
||||
|
||||
this.segments = segments;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node getNativeTree() {
|
||||
IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
|
||||
|
||||
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
|
||||
root.appendChild(jpegVariety);
|
||||
// TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless
|
||||
|
||||
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
|
||||
root.appendChild(markerSequence);
|
||||
|
||||
for (Segment segment : segments)
|
||||
switch (segment.marker) {
|
||||
// SOF3 is the only one supported by now
|
||||
case JPEG.SOF3:
|
||||
Frame sofSegment = (Frame) segment;
|
||||
|
||||
IIOMetadataNode sof = new IIOMetadataNode("sof");
|
||||
sof.setAttribute("process", String.valueOf(sofSegment.marker & 0xf));
|
||||
sof.setAttribute("samplePrecision", String.valueOf(sofSegment.samplePrecision));
|
||||
sof.setAttribute("numLines", String.valueOf(sofSegment.lines));
|
||||
sof.setAttribute("samplesPerLine", String.valueOf(sofSegment.samplesPerLine));
|
||||
sof.setAttribute("numFrameComponents", String.valueOf(sofSegment.componentsInFrame()));
|
||||
|
||||
for (Frame.Component component : sofSegment.components) {
|
||||
IIOMetadataNode componentSpec = new IIOMetadataNode("componentSpec");
|
||||
componentSpec.setAttribute("componentId", String.valueOf(component.id));
|
||||
componentSpec.setAttribute("HsamplingFactor", String.valueOf(component.hSub));
|
||||
componentSpec.setAttribute("VsamplingFactor", String.valueOf(component.vSub));
|
||||
componentSpec.setAttribute("QtableSelector", String.valueOf(component.qtSel));
|
||||
|
||||
sof.appendChild(componentSpec);
|
||||
}
|
||||
|
||||
markerSequence.appendChild(sof);
|
||||
break;
|
||||
|
||||
case JPEG.DHT:
|
||||
HuffmanTable huffmanTable = (HuffmanTable) segment;
|
||||
IIOMetadataNode dht = new IIOMetadataNode("dht");
|
||||
|
||||
// Uses fixed tables...
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
if (huffmanTable.tc[i][j] != 0) {
|
||||
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
|
||||
dhtable.setAttribute("class", String.valueOf(j));
|
||||
dhtable.setAttribute("htableId", String.valueOf(i));
|
||||
dht.appendChild(dhtable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
markerSequence.appendChild(dht);
|
||||
break;
|
||||
|
||||
case JPEG.DQT:
|
||||
markerSequence.appendChild(new IIOMetadataNode("dqt"));
|
||||
// TODO:
|
||||
break;
|
||||
|
||||
case JPEG.SOS:
|
||||
Scan scan = (Scan) segment;
|
||||
IIOMetadataNode sos = new IIOMetadataNode("sos");
|
||||
sos.setAttribute("numScanComponents", String.valueOf(scan.components.length));
|
||||
sos.setAttribute("startSpectralSelection", String.valueOf(scan.spectralSelStart));
|
||||
sos.setAttribute("endSpectralSelection", String.valueOf(scan.spectralSelEnd));
|
||||
sos.setAttribute("approxHigh", String.valueOf(scan.approxHigh));
|
||||
sos.setAttribute("approxLow", String.valueOf(scan.approxLow));
|
||||
|
||||
for (Scan.Component component : scan.components) {
|
||||
IIOMetadataNode spec = new IIOMetadataNode("scanComponentSpec");
|
||||
spec.setAttribute("componentSelector", String.valueOf(component.scanCompSel));
|
||||
spec.setAttribute("dcHuffTable", String.valueOf(component.dcTabSel));
|
||||
spec.setAttribute("acHuffTable", String.valueOf(component.acTabSel));
|
||||
sos.appendChild(spec);
|
||||
}
|
||||
|
||||
markerSequence.appendChild(sos);
|
||||
break;
|
||||
|
||||
case JPEG.COM:
|
||||
IIOMetadataNode com = new IIOMetadataNode("com");
|
||||
com.setAttribute("comment", ((Comment) segment).comment);
|
||||
|
||||
markerSequence.appendChild(com);
|
||||
|
||||
break;
|
||||
|
||||
case JPEG.APP14:
|
||||
if (segment instanceof AdobeDCT) {
|
||||
AdobeDCT adobe = (AdobeDCT) segment;
|
||||
IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
|
||||
app14Adobe.setAttribute("version", String.valueOf(adobe.version));
|
||||
app14Adobe.setAttribute("flags0", String.valueOf(adobe.flags0));
|
||||
app14Adobe.setAttribute("flags1", String.valueOf(adobe.flags1));
|
||||
app14Adobe.setAttribute("transform", String.valueOf(adobe.transform));
|
||||
markerSequence.appendChild(app14Adobe);
|
||||
break;
|
||||
}
|
||||
// Else, fall through to unknown segment
|
||||
|
||||
default:
|
||||
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
|
||||
unknown.setAttribute("MarkerTag", String.valueOf(segment.marker & 0xFF));
|
||||
unknown.setUserObject(((Application) segment).data);
|
||||
markerSequence.appendChild(unknown);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (segment instanceof Frame) {
|
||||
Frame sofSegment = (Frame) segment;
|
||||
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
|
||||
colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "Gray" : "RGB"); // TODO YCC, YCCK, CMYK etc
|
||||
chroma.appendChild(colorSpaceType);
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame()));
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", "JPEG"); // ...or "JPEG-LOSSLESS" (which is the name used by the JAI JPEGImageWriter for it's compression name)?
|
||||
compression.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only
|
||||
compression.appendChild(lossless);
|
||||
|
||||
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
|
||||
numProgressiveScans.setAttribute("value", "1"); // TODO!
|
||||
compression.appendChild(numProgressiveScans);
|
||||
|
||||
return compression;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "normal"); // TODO
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (segment instanceof Comment) {
|
||||
IIOMetadataNode com = new IIOMetadataNode("TextEntry");
|
||||
com.setAttribute("keyword", "comment");
|
||||
com.setAttribute("value", ((Comment) segment).comment);
|
||||
|
||||
text.appendChild(com);
|
||||
}
|
||||
}
|
||||
|
||||
return text.hasChildNodes() ? text : null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -10,7 +11,9 @@ import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -36,7 +39,7 @@ final class JPEGImage10MetadataCleaner {
|
||||
IIOMetadata cleanMetadata(final IIOMetadata imageMetadata) throws IOException {
|
||||
// We filter out pretty much everything from the stream..
|
||||
// Meaning we have to read get *all APP segments* and re-insert into metadata.
|
||||
List<Application> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
|
||||
List<JPEGSegment> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
|
||||
|
||||
// NOTE: There's a bug in the merging code in JPEGMetadata mergeUnknownNode that makes sure all "unknown" nodes are added twice in certain conditions.... ARGHBL...
|
||||
// DONE: 1: Work around
|
||||
@@ -67,11 +70,11 @@ final class JPEGImage10MetadataCleaner {
|
||||
IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
|
||||
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
|
||||
|
||||
JFIF jfifSegment = reader.getJFIF();
|
||||
JFXX jfxx = reader.getJFXX();
|
||||
AdobeDCT adobeDCT = reader.getAdobeDCT();
|
||||
JFIFSegment jfifSegment = reader.getJFIF();
|
||||
JFXXSegment jfxxSegment = reader.getJFXX();
|
||||
AdobeDCTSegment adobeDCT = reader.getAdobeDCT();
|
||||
ICC_Profile embeddedICCProfile = reader.getEmbeddedICCProfile(true);
|
||||
Frame sof = reader.getSOF();
|
||||
SOFSegment sof = reader.getSOF();
|
||||
|
||||
boolean hasRealJFIF = false;
|
||||
boolean hasRealJFXX = false;
|
||||
@@ -101,17 +104,17 @@ final class JPEGImage10MetadataCleaner {
|
||||
hasRealICC = true;
|
||||
}
|
||||
|
||||
if (jfxx != null) {
|
||||
if (jfxxSegment != null) {
|
||||
IIOMetadataNode JFXX = new IIOMetadataNode("JFXX");
|
||||
jfif.appendChild(JFXX);
|
||||
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
|
||||
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode));
|
||||
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxxSegment.extensionCode));
|
||||
|
||||
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxx);
|
||||
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxxSegment);
|
||||
IIOMetadataNode jfifThumb;
|
||||
|
||||
switch (jfxx.extensionCode) {
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.JPEG:
|
||||
switch (jfxxSegment.extensionCode) {
|
||||
case JFXXSegment.JPEG:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
|
||||
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
|
||||
IIOMetadata thumbMeta = thumbnailReader.readMetadata();
|
||||
@@ -120,14 +123,14 @@ final class JPEGImage10MetadataCleaner {
|
||||
app0JFXX.appendChild(jfifThumb);
|
||||
break;
|
||||
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.INDEXED:
|
||||
case JFXXSegment.INDEXED:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbPalette");
|
||||
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
|
||||
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
|
||||
app0JFXX.appendChild(jfifThumb);
|
||||
break;
|
||||
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.RGB:
|
||||
case JFXXSegment.RGB:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbRGB");
|
||||
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
|
||||
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
|
||||
@@ -135,7 +138,7 @@ final class JPEGImage10MetadataCleaner {
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxx.extensionCode));
|
||||
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxxSegment.extensionCode));
|
||||
}
|
||||
|
||||
JFXX.appendChild(app0JFXX);
|
||||
@@ -153,12 +156,12 @@ final class JPEGImage10MetadataCleaner {
|
||||
}
|
||||
|
||||
// Special case: Broken AdobeDCT segment, inconsistent with SOF, use values from SOF
|
||||
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() < 4 ||
|
||||
adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() < 3)) {
|
||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() < 4 ||
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() < 3)) {
|
||||
reader.processWarningOccurred(String.format(
|
||||
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " +
|
||||
"Ignoring Adobe App14 marker.",
|
||||
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
sof.marker & 0xf, sof.componentsInFrame()
|
||||
));
|
||||
|
||||
@@ -173,43 +176,46 @@ final class JPEGImage10MetadataCleaner {
|
||||
}
|
||||
|
||||
Node next = null;
|
||||
for (Application segment : appSegments) {
|
||||
for (JPEGSegment segment : appSegments) {
|
||||
// Except real app0JFIF, app0JFXX, app2ICC and app14Adobe, add all the app segments that we filtered away as "unknown" markers
|
||||
if (segment.marker == JPEG.APP0 && "JFIF".equals(segment.identifier) && hasRealJFIF) {
|
||||
if (segment.marker() == JPEG.APP0 && "JFIF".equals(segment.identifier()) && hasRealJFIF) {
|
||||
continue;
|
||||
}
|
||||
else if (segment.marker == JPEG.APP0 && "JFXX".equals(segment.identifier) && hasRealJFXX) {
|
||||
else if (segment.marker() == JPEG.APP0 && "JFXX".equals(segment.identifier()) && hasRealJFXX) {
|
||||
continue;
|
||||
}
|
||||
else if (segment.marker == JPEG.APP1 && "Exif".equals(segment.identifier) /* always inserted */) {
|
||||
else if (segment.marker() == JPEG.APP1 && "Exif".equals(segment.identifier()) /* always inserted */) {
|
||||
continue;
|
||||
}
|
||||
else if (segment.marker == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier) && hasRealICC) {
|
||||
else if (segment.marker() == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier()) && hasRealICC) {
|
||||
continue;
|
||||
}
|
||||
else if (segment.marker == JPEG.APP14 && "Adobe".equals(segment.identifier) /* always inserted */) {
|
||||
else if (segment.marker() == JPEG.APP14 && "Adobe".equals(segment.identifier()) /* always inserted */) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
|
||||
unknown.setAttribute("MarkerTag", Integer.toString(segment.marker & 0xff));
|
||||
unknown.setAttribute("MarkerTag", Integer.toString(segment.marker() & 0xff));
|
||||
|
||||
unknown.setUserObject(segment.data);
|
||||
DataInputStream stream = new DataInputStream(segment.data());
|
||||
|
||||
// try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(segment.data))) {
|
||||
// String identifier = segment.identifier;
|
||||
// int off = identifier != null ? identifier.length() + 1 : 0;
|
||||
//
|
||||
// byte[] data = new byte[off + segment.data.length];
|
||||
//
|
||||
// if (identifier != null) {
|
||||
// System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length());
|
||||
// }
|
||||
//
|
||||
// stream.readFully(data, off, segment.data.length);
|
||||
//
|
||||
// unknown.setUserObject(data);
|
||||
// }
|
||||
try {
|
||||
String identifier = segment.identifier();
|
||||
int off = identifier != null ? identifier.length() + 1 : 0;
|
||||
|
||||
byte[] data = new byte[off + segment.length()];
|
||||
|
||||
if (identifier != null) {
|
||||
System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length());
|
||||
}
|
||||
|
||||
stream.readFully(data, off, segment.length());
|
||||
|
||||
unknown.setUserObject(data);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
if (next == null) {
|
||||
// To be semi-compatible with the functionality in mergeTree,
|
||||
@@ -265,12 +271,12 @@ final class JPEGImage10MetadataCleaner {
|
||||
dht.getParentNode().insertBefore(acTables, dht.getNextSibling());
|
||||
|
||||
// Split into 2 dht nodes, one for AC and one for DC
|
||||
for (int i = dhtables.getLength() - 1; i >= 0 ; i--) {
|
||||
for (int i = 0; i < dhtables.getLength(); i++) {
|
||||
Element dhtable = (Element) dhtables.item(i);
|
||||
String tableClass = dhtable.getAttribute("class");
|
||||
if ("1".equals(tableClass)) {
|
||||
dht.removeChild(dhtable);
|
||||
acTables.insertBefore(dhtable, acTables.getFirstChild());
|
||||
acTables.appendChild(dhtable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,33 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
static final int ALL_APP_MARKERS = -1;
|
||||
|
||||
/** Segment identifiers for the JPEG segments we care about reading. */
|
||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
|
||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
||||
|
||||
private static Map<Integer, List<String>> createSegmentIds() {
|
||||
Map<Integer, List<String>> map = new LinkedHashMap<>();
|
||||
|
||||
// Need all APP markers to be able to re-generate proper metadata later
|
||||
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
|
||||
map.put(appMarker, JPEGSegmentUtil.ALL_IDS);
|
||||
}
|
||||
|
||||
// SOFn markers
|
||||
map.put(JPEG.SOF0, null);
|
||||
map.put(JPEG.SOF1, null);
|
||||
map.put(JPEG.SOF2, null);
|
||||
map.put(JPEG.SOF3, null);
|
||||
map.put(JPEG.SOF5, null);
|
||||
map.put(JPEG.SOF6, null);
|
||||
map.put(JPEG.SOF7, null);
|
||||
map.put(JPEG.SOF9, null);
|
||||
map.put(JPEG.SOF10, null);
|
||||
map.put(JPEG.SOF11, null);
|
||||
map.put(JPEG.SOF13, null);
|
||||
map.put(JPEG.SOF14, null);
|
||||
map.put(JPEG.SOF15, null);
|
||||
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
/** Our JPEG reading delegate */
|
||||
private final ImageReader delegate;
|
||||
@@ -124,7 +150,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
private JPEGImage10MetadataCleaner metadataCleaner;
|
||||
|
||||
/** Cached list of JPEG segments we filter from the underlying stream */
|
||||
private List<Segment> segments;
|
||||
private List<JPEGSegment> segments;
|
||||
|
||||
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||
super(provider);
|
||||
@@ -173,12 +199,6 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
public int getNumImages(boolean allowSearch) throws IOException {
|
||||
if (allowSearch) {
|
||||
if (isLossless()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return delegate.getNumImages(allowSearch);
|
||||
}
|
||||
@@ -188,46 +208,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLossless() throws IOException {
|
||||
assertInput();
|
||||
|
||||
try {
|
||||
Frame sof = getSOF();
|
||||
if (sof.marker == JPEG.SOF3) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IIOException ignore) {
|
||||
// May happen if no SOF is found, in case we'll just fall through
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
|
||||
Frame sof = getSOF();
|
||||
if (sof.marker == JPEG.SOF3) {
|
||||
return sof.samplesPerLine;
|
||||
}
|
||||
|
||||
return delegate.getWidth(imageIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
|
||||
Frame sof = getSOF();
|
||||
if (sof.marker == JPEG.SOF3) {
|
||||
return sof.lines;
|
||||
}
|
||||
|
||||
return delegate.getHeight(imageIndex);
|
||||
}
|
||||
|
||||
@@ -288,7 +275,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
catch (IIOException | NullPointerException ignore) {
|
||||
catch (NullPointerException ignore) {
|
||||
// Fall through
|
||||
}
|
||||
|
||||
@@ -331,17 +318,17 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
assertInput();
|
||||
checkBounds(imageIndex);
|
||||
|
||||
Frame sof = getSOF();
|
||||
SOFSegment sof = getSOF();
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
AdobeDCT adobeDCT = getAdobeDCT();
|
||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||
boolean bogusAdobeDCT = false;
|
||||
|
||||
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() != 3 ||
|
||||
adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() != 4)) {
|
||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
|
||||
processWarningOccurred(String.format(
|
||||
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " +
|
||||
"Ignoring Adobe App14 marker.",
|
||||
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
sof.marker & 0xf, sof.componentsInFrame()
|
||||
));
|
||||
|
||||
@@ -351,16 +338,9 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
|
||||
|
||||
if (sof.marker == JPEG.SOF3) {
|
||||
// TODO: What about stream position?
|
||||
// TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
|
||||
// Read image as lossless
|
||||
return new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
|
||||
}
|
||||
|
||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
else if (delegate.canReadRaster() && (
|
||||
if (delegate.canReadRaster() && (
|
||||
bogusAdobeDCT ||
|
||||
sourceCSType == JPEGColorSpace.CMYK ||
|
||||
sourceCSType == JPEGColorSpace.YCCK ||
|
||||
@@ -372,7 +352,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// TODO: Possible to optimize slightly, to avoid readAsRaster for non-CMYK and other good types?
|
||||
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, profile);
|
||||
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, ensureDisplayProfile(profile));
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -382,7 +362,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return delegate.read(imageIndex, param);
|
||||
}
|
||||
|
||||
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
|
||||
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, SOFSegment startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
|
||||
int origWidth = getWidth(imageIndex);
|
||||
int origHeight = getHeight(imageIndex);
|
||||
|
||||
@@ -438,10 +418,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
"Colors may look incorrect."
|
||||
);
|
||||
|
||||
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
||||
if (cmykCS != image.getColorModel().getColorSpace()) {
|
||||
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
else {
|
||||
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
||||
@@ -517,7 +494,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return image;
|
||||
}
|
||||
|
||||
static JPEGColorSpace getSourceCSType(JFIF jfif, AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
|
||||
static JPEGColorSpace getSourceCSType(JFIFSegment jfif, AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException {
|
||||
/*
|
||||
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
|
||||
|
||||
@@ -559,27 +536,27 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
*/
|
||||
|
||||
if (adobeDCT != null) {
|
||||
switch (adobeDCT.transform) {
|
||||
case AdobeDCT.YCC:
|
||||
if (startOfFrame.componentsInFrame() != 3) {
|
||||
switch (adobeDCT.getTransform()) {
|
||||
case AdobeDCTSegment.YCC:
|
||||
if (startOfFrame.components.length != 3) {
|
||||
// This probably means the Adobe marker is bogus
|
||||
break;
|
||||
}
|
||||
return JPEGColorSpace.YCbCr;
|
||||
case AdobeDCT.YCCK:
|
||||
if (startOfFrame.componentsInFrame() != 4) {
|
||||
case AdobeDCTSegment.YCCK:
|
||||
if (startOfFrame.components.length != 4) {
|
||||
// This probably means the Adobe marker is bogus
|
||||
break;
|
||||
}
|
||||
return JPEGColorSpace.YCCK;
|
||||
case AdobeDCT.Unknown:
|
||||
if (startOfFrame.componentsInFrame() == 1) {
|
||||
case AdobeDCTSegment.Unknown:
|
||||
if (startOfFrame.components.length == 1) {
|
||||
return JPEGColorSpace.Gray;
|
||||
}
|
||||
else if (startOfFrame.componentsInFrame() == 3) {
|
||||
else if (startOfFrame.components.length == 3) {
|
||||
return JPEGColorSpace.RGB;
|
||||
}
|
||||
else if (startOfFrame.componentsInFrame() == 4) {
|
||||
else if (startOfFrame.components.length == 4) {
|
||||
return JPEGColorSpace.CMYK;
|
||||
}
|
||||
// Else fall through
|
||||
@@ -588,7 +565,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// TODO: We should probably allow component ids out of order (ie. BGR or KMCY)...
|
||||
switch (startOfFrame.componentsInFrame()) {
|
||||
switch (startOfFrame.components.length) {
|
||||
case 1:
|
||||
return JPEGColorSpace.Gray;
|
||||
case 2:
|
||||
@@ -606,7 +583,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
// If subsampled, YCbCr else RGB
|
||||
for (Frame.Component component : startOfFrame.components) {
|
||||
for (SOFComponent component : startOfFrame.components) {
|
||||
if (component.hSub != 1 || component.vSub != 1) {
|
||||
return JPEGColorSpace.YCbCr;
|
||||
}
|
||||
@@ -634,7 +611,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
else {
|
||||
// TODO: JPEGMetadata (standard format) will report YCbCrA for 4 channel subsampled... :-/
|
||||
// If subsampled, YCCK else CMYK
|
||||
for (Frame.Component component : startOfFrame.components) {
|
||||
for (SOFComponent component : startOfFrame.components) {
|
||||
if (component.hSub != 1 || component.vSub != 1) {
|
||||
return JPEGColorSpace.YCCK;
|
||||
}
|
||||
@@ -681,27 +658,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
if (segments == null) {
|
||||
long start = DEBUG ? System.currentTimeMillis() : 0;
|
||||
|
||||
// TODO: Consider just reading the segments directly, for better performance...
|
||||
List<JPEGSegment> jpegSegments = readSegments();
|
||||
|
||||
List<Segment> segments = new ArrayList<>(jpegSegments.size());
|
||||
|
||||
for (JPEGSegment segment : jpegSegments) {
|
||||
try (DataInputStream data = new DataInputStream(segment.segmentData())) {
|
||||
segments.add(Segment.read(segment.marker(), segment.identifier(), segment.segmentLength(), data));
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO: Handle bad segments better, for now, just ignore any bad APP markers
|
||||
if (segment.marker() >= JPEG.APP0 && JPEG.APP15 >= segment.marker()) {
|
||||
processWarningOccurred("Bogus APP" + (segment.marker() & 0x0f) + "/" + segment.identifier() + " segment, ignoring");
|
||||
continue;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
this.segments = segments;
|
||||
readSegments();
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("Read metadata in " + (System.currentTimeMillis() - start) + " ms");
|
||||
@@ -709,13 +666,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private List<JPEGSegment> readSegments() throws IOException {
|
||||
private void readSegments() throws IOException {
|
||||
imageInput.mark();
|
||||
|
||||
try {
|
||||
imageInput.seek(0); // TODO: Seek to wanted image, skip images on the way
|
||||
|
||||
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||
}
|
||||
catch (IIOException | IllegalArgumentException ignore) {
|
||||
if (DEBUG) {
|
||||
@@ -727,62 +684,125 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// In case of an exception, avoid NPE when referencing segments later
|
||||
return Collections.emptyList();
|
||||
if (segments == null) {
|
||||
segments = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
List<Application> getAppSegments(final int marker, final String identifier) throws IOException {
|
||||
List<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
|
||||
initHeader();
|
||||
|
||||
List<Application> appSegments = Collections.emptyList();
|
||||
List<JPEGSegment> appSegments = Collections.emptyList();
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (segment instanceof Application
|
||||
&& (marker == ALL_APP_MARKERS || marker == segment.marker)
|
||||
&& (identifier == null || identifier.equals(((Application) segment).identifier))) {
|
||||
for (JPEGSegment segment : segments) {
|
||||
if ((marker == ALL_APP_MARKERS && segment.marker() >= JPEG.APP0 && segment.marker() <= JPEG.APP15 || segment.marker() == marker)
|
||||
&& (identifier == null || identifier.equals(segment.identifier()))) {
|
||||
if (appSegments == Collections.EMPTY_LIST) {
|
||||
appSegments = new ArrayList<>(segments.size());
|
||||
}
|
||||
|
||||
appSegments.add((Application) segment);
|
||||
appSegments.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return appSegments;
|
||||
}
|
||||
|
||||
Frame getSOF() throws IOException {
|
||||
SOFSegment getSOF() throws IOException {
|
||||
initHeader();
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (segment instanceof Frame) {
|
||||
return (Frame) segment;
|
||||
for (JPEGSegment segment : segments) {
|
||||
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 ||
|
||||
JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 ||
|
||||
JPEG.SOF9 >= segment.marker() && segment.marker() <= JPEG.SOF11 ||
|
||||
JPEG.SOF13 >= segment.marker() && segment.marker() <= JPEG.SOF15) {
|
||||
|
||||
DataInputStream data = new DataInputStream(segment.data());
|
||||
|
||||
try {
|
||||
int samplePrecision = data.readUnsignedByte();
|
||||
int lines = data.readUnsignedShort();
|
||||
int samplesPerLine = data.readUnsignedShort();
|
||||
int componentsInFrame = data.readUnsignedByte();
|
||||
|
||||
SOFComponent[] components = new SOFComponent[componentsInFrame];
|
||||
|
||||
for (int i = 0; i < componentsInFrame; i++) {
|
||||
int id = data.readUnsignedByte();
|
||||
int sub = data.readUnsignedByte();
|
||||
int qtSel = data.readUnsignedByte();
|
||||
|
||||
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
||||
}
|
||||
|
||||
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
|
||||
}
|
||||
finally {
|
||||
data.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IIOException("No SOF segment in stream");
|
||||
}
|
||||
|
||||
AdobeDCT getAdobeDCT() throws IOException {
|
||||
List<Application> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
||||
return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0);
|
||||
AdobeDCTSegment getAdobeDCT() throws IOException {
|
||||
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
|
||||
List<JPEGSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
||||
|
||||
if (!adobe.isEmpty()) {
|
||||
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
|
||||
DataInputStream stream = new DataInputStream(adobe.get(0).data());
|
||||
|
||||
return new AdobeDCTSegment(
|
||||
stream.readUnsignedByte(),
|
||||
stream.readUnsignedShort(),
|
||||
stream.readUnsignedShort(),
|
||||
stream.readUnsignedByte()
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
JFIF getJFIF() throws IOException{
|
||||
List<Application> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
||||
return jfif.isEmpty() ? null : (JFIF) jfif.get(0);
|
||||
JFIFSegment getJFIF() throws IOException{
|
||||
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
||||
|
||||
if (!jfif.isEmpty()) {
|
||||
JPEGSegment segment = jfif.get(0);
|
||||
|
||||
if (segment.length() >= 9) {
|
||||
return JFIFSegment.read(segment.data());
|
||||
}
|
||||
else {
|
||||
processWarningOccurred("Bogus JFIF segment, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
JFXX getJFXX() throws IOException {
|
||||
List<Application> jfxx = getAppSegments(JPEG.APP0, "JFXX");
|
||||
return jfxx.isEmpty() ? null : (JFXX) jfxx.get(0);
|
||||
JFXXSegment getJFXX() throws IOException {
|
||||
List<JPEGSegment> jfxx = getAppSegments(JPEG.APP0, "JFXX");
|
||||
|
||||
if (!jfxx.isEmpty()) {
|
||||
JPEGSegment segment = jfxx.get(0);
|
||||
if (segment.length() >= 1) {
|
||||
return JFXXSegment.read(segment.data(), segment.length());
|
||||
}
|
||||
else {
|
||||
processWarningOccurred("Bogus JFXX segment, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CompoundDirectory getExif() throws IOException {
|
||||
List<Application> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||
|
||||
if (!exifSegments.isEmpty()) {
|
||||
Application exif = exifSegments.get(0);
|
||||
JPEGSegment exif = exifSegments.get(0);
|
||||
InputStream data = exif.data();
|
||||
|
||||
if (data.read() == -1) { // Read pad
|
||||
@@ -818,13 +838,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
// TODO: Allow metadata to contain the wrongly indexed profiles, if readable
|
||||
// NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side
|
||||
|
||||
List<Application> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
||||
|
||||
// TODO: Possibly move this logic to the ICCProfile class...
|
||||
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
||||
|
||||
if (segments.size() == 1) {
|
||||
// Faster code for the common case
|
||||
Application segment = segments.get(0);
|
||||
JPEGSegment segment = segments.get(0);
|
||||
DataInputStream stream = new DataInputStream(segment.data());
|
||||
int chunkNumber = stream.readUnsignedByte();
|
||||
int chunkCount = stream.readUnsignedByte();
|
||||
@@ -891,8 +909,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
try {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(stream);
|
||||
|
||||
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
|
||||
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
|
||||
return allowBadProfile ? profile : ColorSpaces.validateProfile(profile);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
||||
@@ -908,15 +925,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
|
||||
if (isLossless()) {
|
||||
// TODO: What about stream position?
|
||||
// TODO: Param handling: Reading as raster should support source region, subsampling etc.
|
||||
return new JPEGLosslessDecoderWrapper(this).readRaster(segments, imageInput);
|
||||
}
|
||||
|
||||
public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException {
|
||||
return delegate.readRaster(imageIndex, param);
|
||||
}
|
||||
|
||||
@@ -950,18 +959,18 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate();
|
||||
|
||||
// Read JFIF thumbnails if present
|
||||
JFIF jfif = getJFIF();
|
||||
JFIFSegment jfif = getJFIF();
|
||||
if (jfif != null && jfif.thumbnail != null) {
|
||||
thumbnails.add(new JFIFThumbnailReader(thumbnailProgressDelegator, imageIndex, thumbnails.size(), jfif));
|
||||
}
|
||||
|
||||
// Read JFXX thumbnails if present
|
||||
JFXX jfxx = getJFXX();
|
||||
JFXXSegment jfxx = getJFXX();
|
||||
if (jfxx != null && jfxx.thumbnail != null) {
|
||||
switch (jfxx.extensionCode) {
|
||||
case JFXX.JPEG:
|
||||
case JFXX.INDEXED:
|
||||
case JFXX.RGB:
|
||||
case JFXXSegment.JPEG:
|
||||
case JFXXSegment.INDEXED:
|
||||
case JFXXSegment.RGB:
|
||||
thumbnails.add(new JFXXThumbnailReader(thumbnailProgressDelegator, getThumbnailReader(), imageIndex, thumbnails.size(), jfxx));
|
||||
break;
|
||||
default:
|
||||
@@ -970,9 +979,9 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// Read Exif thumbnails if present
|
||||
List<Application> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
|
||||
if (!exifSegments.isEmpty()) {
|
||||
Application exif = exifSegments.get(0);
|
||||
JPEGSegment exif = exifSegments.get(0);
|
||||
InputStream data = exif.data();
|
||||
|
||||
if (data.read() == -1) {
|
||||
@@ -1057,29 +1066,24 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
IIOMetadata imageMetadata;
|
||||
|
||||
if (isLossless()) {
|
||||
return new JPEGImage10Metadata(segments);
|
||||
try {
|
||||
imageMetadata = delegate.getImageMetadata(imageIndex);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
imageMetadata = delegate.getImageMetadata(imageIndex);
|
||||
}
|
||||
catch (IndexOutOfBoundsException knownIssue) {
|
||||
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
||||
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
|
||||
}
|
||||
catch (NegativeArraySizeException knownIssue) {
|
||||
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
|
||||
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
|
||||
catch (IndexOutOfBoundsException knownIssue) {
|
||||
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
||||
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
|
||||
}
|
||||
catch (NegativeArraySizeException knownIssue) {
|
||||
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
|
||||
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
|
||||
}
|
||||
|
||||
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
|
||||
if (metadataCleaner == null) {
|
||||
metadataCleaner = new JPEGImage10MetadataCleaner(this);
|
||||
}
|
||||
|
||||
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
|
||||
if (metadataCleaner == null) {
|
||||
metadataCleaner = new JPEGImage10MetadataCleaner(this);
|
||||
}
|
||||
|
||||
return metadataCleaner.cleanMetadata(imageMetadata);
|
||||
}
|
||||
return metadataCleaner.cleanMetadata(imageMetadata);
|
||||
}
|
||||
|
||||
return imageMetadata;
|
||||
@@ -1363,8 +1367,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);
|
||||
}
|
||||
else {
|
||||
// image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
||||
image = null;
|
||||
image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
||||
}
|
||||
param.setDestination(image);
|
||||
|
||||
|
||||
@@ -1,743 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (C) 2015, Michael Martinez
|
||||
* Copyright (C) 2004, Helmut Dersch
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class JPEGLosslessDecoder {
|
||||
|
||||
private final ImageInputStream input;
|
||||
private final JPEGImageReader listenerDelegate;
|
||||
|
||||
private final Frame frame;
|
||||
private final List<HuffmanTable> huffTables;
|
||||
private final QuantizationTable quantTable;
|
||||
private Scan scan;
|
||||
|
||||
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
|
||||
private final int IDCT_Source[] = new int[64];
|
||||
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan
|
||||
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan
|
||||
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan
|
||||
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
|
||||
|
||||
private boolean restarting;
|
||||
private int marker;
|
||||
private int markerIndex;
|
||||
private int numComp;
|
||||
private int restartInterval;
|
||||
private int selection;
|
||||
private int xDim, yDim;
|
||||
private int xLoc;
|
||||
private int yLoc;
|
||||
private int mask;
|
||||
private int[] outputData;
|
||||
private int[] outputRedData;
|
||||
private int[] outputGreenData;
|
||||
private int[] outputBlueData;
|
||||
|
||||
private static final int IDCT_P[] = {
|
||||
0, 5, 40, 16, 45, 2, 7, 42,
|
||||
21, 56, 8, 61, 18, 47, 1, 4,
|
||||
41, 23, 58, 13, 32, 24, 37, 10,
|
||||
63, 17, 44, 3, 6, 43, 20, 57,
|
||||
15, 34, 29, 48, 53, 26, 39, 9,
|
||||
60, 19, 46, 22, 59, 12, 33, 31,
|
||||
50, 55, 25, 36, 11, 62, 14, 35,
|
||||
28, 49, 52, 27, 38, 30, 51, 54
|
||||
};
|
||||
private static final int TABLE[] = {
|
||||
0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63
|
||||
};
|
||||
|
||||
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
|
||||
private static final int RESTART_MARKER_END = 0xFFD7;
|
||||
private static final int MAX_HUFFMAN_SUBTREE = 50;
|
||||
private static final int MSB = 0x80000000;
|
||||
|
||||
int getDimX() {
|
||||
return xDim;
|
||||
}
|
||||
|
||||
int getDimY() {
|
||||
return yDim;
|
||||
}
|
||||
|
||||
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
|
||||
Validate.notNull(segments);
|
||||
|
||||
frame = get(segments, Frame.class);
|
||||
scan = get(segments, Scan.class);
|
||||
|
||||
QuantizationTable qt = get(segments, QuantizationTable.class);
|
||||
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
|
||||
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
|
||||
|
||||
RestartInterval dri = get(segments, RestartInterval.class);
|
||||
restartInterval = dri != null ? dri.interval : 0;
|
||||
|
||||
input = data;
|
||||
this.listenerDelegate = listenerDelegate;
|
||||
}
|
||||
|
||||
private <T> List<T> getAll(final List<Segment> segments, final Class<T> type) {
|
||||
ArrayList<T> list = new ArrayList<>();
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (type.isInstance(segment)) {
|
||||
list.add(type.cast(segment));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private <T> T get(final List<Segment> segments, final Class<T> type) {
|
||||
for (Segment segment : segments) {
|
||||
if (type.isInstance(segment)) {
|
||||
return type.cast(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int[][] decode() throws IOException {
|
||||
int current, scanNum = 0;
|
||||
final int pred[] = new int[10];
|
||||
int[][] outputRef;
|
||||
|
||||
xLoc = 0;
|
||||
yLoc = 0;
|
||||
current = input.readUnsignedShort();
|
||||
|
||||
if (current != JPEG.SOI) { // SOI
|
||||
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
|
||||
}
|
||||
|
||||
for (HuffmanTable huffTable : huffTables) {
|
||||
huffTable.buildHuffTables(HuffTab);
|
||||
}
|
||||
|
||||
quantTable.enhanceTables(TABLE);
|
||||
|
||||
current = input.readUnsignedShort();
|
||||
|
||||
do {
|
||||
// Skip until first SOS
|
||||
while (current != JPEG.SOS) {
|
||||
input.skipBytes(input.readUnsignedShort() - 2);
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
|
||||
int precision = frame.samplePrecision;
|
||||
|
||||
if (precision == 8) {
|
||||
mask = 0xFF;
|
||||
}
|
||||
else {
|
||||
mask = 0xFFFF;
|
||||
}
|
||||
|
||||
Frame.Component[] components = frame.components;
|
||||
|
||||
scan = readScan();
|
||||
numComp = scan.components.length;
|
||||
selection = scan.spectralSelStart;
|
||||
|
||||
final Scan.Component[] scanComps = scan.components;
|
||||
final int[][] quantTables = quantTable.quantTables;
|
||||
|
||||
for (int i = 0; i < numComp; i++) {
|
||||
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
|
||||
qTab[i] = quantTables[component.qtSel];
|
||||
nBlock[i] = component.vSub * component.hSub;
|
||||
|
||||
int dcTabSel = scanComps[i].dcTabSel;
|
||||
int acTabSel = scanComps[i].acTabSel;
|
||||
|
||||
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
|
||||
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
|
||||
if (useACForDC(dcTabSel)) {
|
||||
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
|
||||
|
||||
dcTab[i] = HuffTab[dcTabSel][1];
|
||||
acTab[i] = HuffTab[acTabSel][0];
|
||||
}
|
||||
else {
|
||||
// All good
|
||||
dcTab[i] = HuffTab[dcTabSel][0];
|
||||
acTab[i] = HuffTab[acTabSel][1];
|
||||
}
|
||||
}
|
||||
|
||||
xDim = frame.samplesPerLine;
|
||||
yDim = frame.lines;
|
||||
|
||||
outputRef = new int[numComp][];
|
||||
|
||||
// TODO: Support 4 components (RGBA/YCCA/CMYK/YCCK), others?
|
||||
if (numComp == 1) {
|
||||
outputData = new int[xDim * yDim];
|
||||
outputRef[0] = outputData;
|
||||
}
|
||||
else {
|
||||
outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
|
||||
outputGreenData = new int[xDim * yDim];
|
||||
outputBlueData = new int[xDim * yDim];
|
||||
|
||||
outputRef[0] = outputRedData;
|
||||
outputRef[1] = outputGreenData;
|
||||
outputRef[2] = outputBlueData;
|
||||
}
|
||||
|
||||
scanNum++;
|
||||
|
||||
while (true) { // Decode one scan
|
||||
int temp[] = new int[1]; // to store remainder bits
|
||||
int index[] = new int[1];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
pred[i] = (1 << (precision - 1));
|
||||
}
|
||||
|
||||
if (restartInterval == 0) {
|
||||
current = decode(pred, temp, index);
|
||||
|
||||
while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) {
|
||||
output(pred);
|
||||
current = decode(pred, temp, index);
|
||||
}
|
||||
|
||||
break; //current=MARKER
|
||||
}
|
||||
|
||||
for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) {
|
||||
restarting = (mcuNum == 0);
|
||||
current = decode(pred, temp, index);
|
||||
output(pred);
|
||||
|
||||
if (current != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current == 0) {
|
||||
if (markerIndex != 0) {
|
||||
current = (0xFF00 | marker);
|
||||
markerIndex = 0;
|
||||
}
|
||||
else {
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
}
|
||||
|
||||
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
|
||||
break; //current=MARKER
|
||||
}
|
||||
}
|
||||
|
||||
if ((current == JPEG.DNL) && (scanNum == 1)) { //DNL
|
||||
readNumber();
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
|
||||
|
||||
return outputRef;
|
||||
}
|
||||
|
||||
private void processWarningOccured(String warning) {
|
||||
listenerDelegate.processWarningOccurred(warning);
|
||||
}
|
||||
|
||||
private boolean useACForDC(final int dcTabSel) {
|
||||
if (isLossless()) {
|
||||
for (HuffmanTable huffTable : huffTables) {
|
||||
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLossless() {
|
||||
switch (frame.marker) {
|
||||
case JPEG.SOF3:
|
||||
case JPEG.SOF7:
|
||||
case JPEG.SOF11:
|
||||
case JPEG.SOF15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
|
||||
for (Frame.Component component : components) {
|
||||
if (component.id == sel) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("No such component id: " + sel);
|
||||
}
|
||||
|
||||
private Scan readScan() throws IOException {
|
||||
int length = input.readUnsignedShort();
|
||||
return Scan.read(input, length);
|
||||
}
|
||||
|
||||
private int decode(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
if (numComp == 1) {
|
||||
return decodeSingle(prev, temp, index);
|
||||
}
|
||||
else if (numComp == 3) {
|
||||
return decodeRGB(prev, temp, index);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
// At the beginning of the first line and
|
||||
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
|
||||
if (restarting) {
|
||||
restarting = false;
|
||||
prev[0] = (1 << (frame.samplePrecision - 1));
|
||||
}
|
||||
else {
|
||||
switch (selection) {
|
||||
case 2:
|
||||
prev[0] = getPreviousY(outputData);
|
||||
break;
|
||||
case 3:
|
||||
prev[0] = getPreviousXY(outputData);
|
||||
break;
|
||||
case 4:
|
||||
prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
|
||||
break;
|
||||
case 5:
|
||||
prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
|
||||
break;
|
||||
case 6:
|
||||
prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
|
||||
break;
|
||||
case 7:
|
||||
prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
|
||||
break;
|
||||
default:
|
||||
prev[0] = getPreviousX(outputData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < nBlock[0]; i++) {
|
||||
int value = getHuffmanValue(dcTab[0], temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int n = getn(prev, value, temp, index);
|
||||
|
||||
int nRestart = (n >> 8);
|
||||
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
|
||||
return nRestart;
|
||||
}
|
||||
|
||||
prev[0] += n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
switch (selection) {
|
||||
case 2:
|
||||
prev[0] = getPreviousY(outputRedData);
|
||||
prev[1] = getPreviousY(outputGreenData);
|
||||
prev[2] = getPreviousY(outputBlueData);
|
||||
break;
|
||||
case 3:
|
||||
prev[0] = getPreviousXY(outputRedData);
|
||||
prev[1] = getPreviousXY(outputGreenData);
|
||||
prev[2] = getPreviousXY(outputBlueData);
|
||||
break;
|
||||
case 4:
|
||||
prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData);
|
||||
prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData);
|
||||
prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData);
|
||||
break;
|
||||
case 5:
|
||||
prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1);
|
||||
prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
|
||||
prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
|
||||
break;
|
||||
case 6:
|
||||
prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1);
|
||||
prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
|
||||
prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
|
||||
break;
|
||||
case 7:
|
||||
prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2);
|
||||
prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2);
|
||||
prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2);
|
||||
break;
|
||||
default:
|
||||
prev[0] = getPreviousX(outputRedData);
|
||||
prev[1] = getPreviousX(outputGreenData);
|
||||
prev[2] = getPreviousX(outputBlueData);
|
||||
break;
|
||||
}
|
||||
|
||||
int value, actab[], dctab[];
|
||||
int qtab[];
|
||||
|
||||
for (int ctrC = 0; ctrC < numComp; ctrC++) {
|
||||
qtab = qTab[ctrC];
|
||||
actab = acTab[ctrC];
|
||||
dctab = dcTab[ctrC];
|
||||
for (int i = 0; i < nBlock[ctrC]; i++) {
|
||||
for (int k = 0; k < IDCT_Source.length; k++) {
|
||||
IDCT_Source[k] = 0;
|
||||
}
|
||||
|
||||
value = getHuffmanValue(dctab, temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index);
|
||||
IDCT_Source[0] *= qtab[0];
|
||||
|
||||
for (int j = 1; j < 64; j++) {
|
||||
value = getHuffmanValue(actab, temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
j += (value >> 4);
|
||||
|
||||
if ((value & 0x0F) == 0) {
|
||||
if ((value >> 4) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
|
||||
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
|
||||
// HuffTab[*][*][0-256] is always the only 1st-layer table.
|
||||
//
|
||||
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
|
||||
//
|
||||
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||
// ):
|
||||
// return: Huffman Value of table
|
||||
// 0xFF?? if it receives a MARKER
|
||||
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||
// temp temp storage for remainded bits
|
||||
// index index to bit of temp
|
||||
// in FILE pointer
|
||||
// Effect:
|
||||
// temp store new remainded bits
|
||||
// index change to new index
|
||||
// in change to new position
|
||||
// NOTE:
|
||||
// Initial by temp=0; index=0;
|
||||
// NOTE: (explain temp and index)
|
||||
// temp: is always in the form at calling time or returning time
|
||||
// | byte 4 | byte 3 | byte 2 | byte 1 |
|
||||
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
|
||||
// ^index=3 (from 0 to 15)
|
||||
// 321
|
||||
// NOTE (marker and marker_index):
|
||||
// If get a MARKER from 'in', marker=the low-byte of the MARKER
|
||||
// and marker_index=9
|
||||
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
||||
// will not be called
|
||||
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException {
|
||||
int code, input;
|
||||
final int mask = 0xFFFF;
|
||||
|
||||
if (index[0] < 8) {
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
temp[0] |= input;
|
||||
}
|
||||
else {
|
||||
index[0] -= 8;
|
||||
}
|
||||
|
||||
code = table[temp[0] >> index[0]];
|
||||
|
||||
if ((code & MSB) != 0) {
|
||||
if (markerIndex != 0) {
|
||||
markerIndex = 0;
|
||||
return 0xFF00 | marker;
|
||||
}
|
||||
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])];
|
||||
index[0] += 8;
|
||||
}
|
||||
|
||||
index[0] += 8 - (code >> 8);
|
||||
|
||||
if (index[0] < 0) {
|
||||
throw new IIOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()");
|
||||
}
|
||||
|
||||
if (index[0] < markerIndex) {
|
||||
markerIndex = 0;
|
||||
return 0xFF00 | marker;
|
||||
}
|
||||
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
return code & 0xFF;
|
||||
}
|
||||
|
||||
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException {
|
||||
int result;
|
||||
final int one = 1;
|
||||
final int n_one = -1;
|
||||
final int mask = 0xFFFF;
|
||||
int input;
|
||||
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n == 16) {
|
||||
if (pred[0] >= 0) {
|
||||
return -32768;
|
||||
}
|
||||
else {
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
index[0] -= n;
|
||||
|
||||
if (index[0] >= 0) {
|
||||
if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
result = temp[0] >> index[0];
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
}
|
||||
else {
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
index[0] += 8;
|
||||
|
||||
if (index[0] < 0) {
|
||||
if (markerIndex != 0) {
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
index[0] += 8;
|
||||
}
|
||||
|
||||
if (index[0] < 0) {
|
||||
throw new IOException("index=" + index[0] + " in getn()");
|
||||
}
|
||||
|
||||
if (index[0] < markerIndex) {
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
result = temp[0] >> index[0];
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
}
|
||||
|
||||
if (result < (one << (n - 1))) {
|
||||
result += (n_one << n) + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getPreviousX(final int data[]) {
|
||||
if (xLoc > 0) {
|
||||
return data[((yLoc * xDim) + xLoc) - 1];
|
||||
}
|
||||
else if (yLoc > 0) {
|
||||
return getPreviousY(data);
|
||||
}
|
||||
else {
|
||||
return (1 << (frame.samplePrecision - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private int getPreviousXY(final int data[]) {
|
||||
if ((xLoc > 0) && (yLoc > 0)) {
|
||||
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
|
||||
}
|
||||
else {
|
||||
return getPreviousY(data);
|
||||
}
|
||||
}
|
||||
|
||||
private int getPreviousY(final int data[]) {
|
||||
if (yLoc > 0) {
|
||||
return data[((yLoc - 1) * xDim) + xLoc];
|
||||
}
|
||||
else {
|
||||
return getPreviousX(data);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLastPixel() {
|
||||
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
|
||||
}
|
||||
|
||||
private void output(final int pred[]) {
|
||||
if (numComp == 1) {
|
||||
outputSingle(pred);
|
||||
}
|
||||
else {
|
||||
outputRGB(pred);
|
||||
}
|
||||
}
|
||||
|
||||
private void outputSingle(final int pred[]) {
|
||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||
outputData[(yLoc * xDim) + xLoc] = mask & pred[0];
|
||||
xLoc++;
|
||||
|
||||
if (xLoc >= xDim) {
|
||||
yLoc++;
|
||||
xLoc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void outputRGB(final int pred[]) {
|
||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||
outputRedData[(yLoc * xDim) + xLoc] = pred[0];
|
||||
outputGreenData[(yLoc * xDim) + xLoc] = pred[1];
|
||||
outputBlueData[(yLoc * xDim) + xLoc] = pred[2];
|
||||
xLoc++;
|
||||
|
||||
if (xLoc >= xDim) {
|
||||
yLoc++;
|
||||
xLoc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int readNumber() throws IOException {
|
||||
final int Ld = input.readUnsignedShort();
|
||||
|
||||
if (Ld != 4) {
|
||||
throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]");
|
||||
}
|
||||
|
||||
return input.readUnsignedShort();
|
||||
}
|
||||
|
||||
int getNumComponents() {
|
||||
return numComp;
|
||||
}
|
||||
|
||||
int getPrecision() {
|
||||
return frame.samplePrecision;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (c) 2016, Herman Kroll
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferUShort;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class provides the conversion of input data
|
||||
* containing a JPEG Lossless to an BufferedImage.
|
||||
* <p>
|
||||
* Take care, that only the following lossless formats are supported:
|
||||
* 1.2.840.10008.1.2.4.57 JPEG Lossless, Nonhierarchical (Processes 14)
|
||||
* 1.2.840.10008.1.2.4.70 JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
|
||||
* <p>
|
||||
* Currently the following conversions are supported
|
||||
* - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB
|
||||
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
|
||||
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
|
||||
*
|
||||
* @author Hermann Kroll
|
||||
*/
|
||||
final class JPEGLosslessDecoderWrapper {
|
||||
|
||||
private final JPEGImageReader listenerDelegate;
|
||||
|
||||
JPEGLosslessDecoderWrapper(final JPEGImageReader listenerDelegate) {
|
||||
this.listenerDelegate = listenerDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a JPEG Lossless stream to a {@code BufferedImage}.
|
||||
* Currently the following conversions are supported:
|
||||
* - 24Bit, RGB -> BufferedImage.TYPE_3BYTE_BGR
|
||||
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
|
||||
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
|
||||
*
|
||||
* @param segments segments
|
||||
* @param input input stream which contains a jpeg lossless data
|
||||
* @return if successfully a BufferedImage is returned
|
||||
* @throws IOException is thrown if the decoder failed or a conversion is not supported
|
||||
*/
|
||||
BufferedImage readImage(final List<Segment> segments, final ImageInputStream input) throws IOException {
|
||||
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input), listenerDelegate);
|
||||
|
||||
// TODO: Allow 10/12/14 bit (using a ComponentColorModel with correct bits, as in TIFF)
|
||||
// TODO: Rewrite this to pass a pre-allocated buffer of correct type (byte/short)/correct bands
|
||||
// TODO: Progress callbacks
|
||||
// TODO: Warning callbacks
|
||||
// Callback can then do subsampling etc.
|
||||
int[][] decoded = decoder.decode();
|
||||
int width = decoder.getDimX();
|
||||
int height = decoder.getDimY();
|
||||
|
||||
// Single component, assumed to be Gray
|
||||
if (decoder.getNumComponents() == 1) {
|
||||
switch (decoder.getPrecision()) {
|
||||
case 8:
|
||||
return to8Bit1ComponentGrayScale(decoded, width, height);
|
||||
case 16:
|
||||
return to16Bit1ComponentGrayScale(decoded, width, height);
|
||||
}
|
||||
}
|
||||
// 3 components, assumed to be RGB
|
||||
else if (decoder.getNumComponents() == 3) {
|
||||
switch (decoder.getPrecision()) {
|
||||
case 8:
|
||||
return to24Bit3ComponentRGB(decoded, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IIOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) not supported");
|
||||
}
|
||||
|
||||
private ImageInputStream createBufferedInput(final ImageInputStream input) throws IOException {
|
||||
return input instanceof BufferedImageInputStream ? input : new BufferedImageInputStream(input);
|
||||
}
|
||||
|
||||
Raster readRaster(final List<Segment> segments, final ImageInputStream input) throws IOException {
|
||||
// TODO: Can perhaps be implemented faster
|
||||
return readImage(segments, input).getRaster();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the decoded buffer into a BufferedImage.
|
||||
* precision: 16 bit, componentCount = 1
|
||||
*
|
||||
* @param decoded data buffer
|
||||
* @param width of the image
|
||||
* @param height of the image
|
||||
* @return a BufferedImage.TYPE_USHORT_GRAY
|
||||
*/
|
||||
private BufferedImage to16Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
|
||||
short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
|
||||
|
||||
for (int i = 0; i < imageBuffer.length; i++) {
|
||||
imageBuffer[i] = (short) decoded[0][i];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the decoded buffer into a BufferedImage.
|
||||
* precision: 8 bit, componentCount = 1
|
||||
*
|
||||
* @param decoded data buffer
|
||||
* @param width of the image
|
||||
* @param height of the image
|
||||
* @return a BufferedImage.TYPE_BYTE_GRAY
|
||||
*/
|
||||
private BufferedImage to8Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
|
||||
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||
|
||||
for (int i = 0; i < imageBuffer.length; i++) {
|
||||
imageBuffer[i] = (byte) decoded[0][i];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the decoded buffer into a BufferedImage.
|
||||
* precision: 8 bit, componentCount = 3
|
||||
*
|
||||
* @param decoded data buffer
|
||||
* @param width of the image
|
||||
* @param height of the image
|
||||
* @return a BufferedImage.TYPE_3BYTE_RGB
|
||||
*/
|
||||
private BufferedImage to24Bit3ComponentRGB(int[][] decoded, int width, int height) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
|
||||
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||
|
||||
for (int i = 0; i < imageBuffer.length / 3; i++) {
|
||||
// Convert to RGB (BGR)
|
||||
imageBuffer[i * 3 + 2] = (byte) decoded[0][i];
|
||||
imageBuffer[i * 3 + 1] = (byte) decoded[1][i];
|
||||
imageBuffer[i * 3] = (byte) decoded[2][i];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,7 +38,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
* @version $Id: JPEGProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class JPEGProviderInfo extends ReaderWriterProviderInfo {
|
||||
JPEGProviderInfo() {
|
||||
protected JPEGProviderInfo() {
|
||||
super(
|
||||
JPEGProviderInfo.class,
|
||||
new String[] {"JPEG", "jpeg", "JPG", "jpg"},
|
||||
|
||||
@@ -240,21 +240,16 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
private void streamInit() throws IOException {
|
||||
stream.seek(0);
|
||||
|
||||
try {
|
||||
int soi = stream.readUnsignedShort();
|
||||
|
||||
if (soi != JPEG.SOI) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
|
||||
}
|
||||
|
||||
int soi = stream.readUnsignedShort();
|
||||
if (soi != JPEG.SOI) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
|
||||
}
|
||||
else {
|
||||
segment = new Segment(soi, 0, 0, 2);
|
||||
|
||||
segments.add(segment);
|
||||
currentSegment = segments.size() - 1; // 0
|
||||
}
|
||||
catch (EOFException eof) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (short stream. expected SOI: 0x%04x)", JPEG.SOI), eof);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAppSegmentMarker(final int marker) {
|
||||
@@ -361,6 +356,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
return stream.read(b, off, len);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("0x%04x[%d-%d]", marker, realStart, realEnd());
|
||||
@@ -408,7 +404,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
byte[] replacementData = new byte[length];
|
||||
|
||||
int numQTs = length / 128;
|
||||
int newSegmentLength = 2 + (1 + 64) * numQTs; // Len + (qtInfo + qtSize) * numQTs
|
||||
int newSegmentLength = 2 + 1 + 64 * numQTs;
|
||||
|
||||
replacementData[0] = (byte) ((JPEG.DQT >> 8) & 0xff);
|
||||
replacementData[1] = (byte) (JPEG.DQT & 0xff);
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (C) 2015, Michael Martinez
|
||||
* Copyright (C) 2004, Helmut Dersch
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
final class QuantizationTable extends Segment {
|
||||
|
||||
private final int precision[] = new int[4]; // Quantization precision 8 or 16
|
||||
private final int[] tq = new int[4]; // 1: this table is presented
|
||||
|
||||
final int quantTables[][] = new int[4][64]; // Tables
|
||||
|
||||
QuantizationTable() {
|
||||
super(JPEG.DQT);
|
||||
|
||||
tq[0] = 0;
|
||||
tq[1] = 0;
|
||||
tq[2] = 0;
|
||||
tq[3] = 0;
|
||||
}
|
||||
|
||||
// TODO: Get rid of table param, make it a member?
|
||||
void enhanceTables(final int[] table) throws IOException {
|
||||
for (int t = 0; t < 4; t++) {
|
||||
if (tq[t] != 0) {
|
||||
enhanceQuantizationTable(quantTables[t], table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void enhanceQuantizationTable(final int qtab[], final int[] table) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
qtab[table[ i]] *= 90;
|
||||
qtab[table[(4 * 8) + i]] *= 90;
|
||||
qtab[table[(2 * 8) + i]] *= 118;
|
||||
qtab[table[(6 * 8) + i]] *= 49;
|
||||
qtab[table[(5 * 8) + i]] *= 71;
|
||||
qtab[table[( 8) + i]] *= 126;
|
||||
qtab[table[(7 * 8) + i]] *= 25;
|
||||
qtab[table[(3 * 8) + i]] *= 106;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
qtab[table[( 8 * i)]] *= 90;
|
||||
qtab[table[4 + (8 * i)]] *= 90;
|
||||
qtab[table[2 + (8 * i)]] *= 118;
|
||||
qtab[table[6 + (8 * i)]] *= 49;
|
||||
qtab[table[5 + (8 * i)]] *= 71;
|
||||
qtab[table[1 + (8 * i)]] *= 126;
|
||||
qtab[table[7 + (8 * i)]] *= 25;
|
||||
qtab[table[3 + (8 * i)]] *= 106;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
qtab[i] >>= 6;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// TODO: Tables...
|
||||
return "DQT[]";
|
||||
}
|
||||
|
||||
public static QuantizationTable read(final DataInput data, final int length) throws IOException {
|
||||
int count = 2;
|
||||
|
||||
QuantizationTable table = new QuantizationTable();
|
||||
while (count < length) {
|
||||
final int temp = data.readUnsignedByte();
|
||||
count++;
|
||||
final int t = temp & 0x0F;
|
||||
|
||||
if (t > 3) {
|
||||
throw new IIOException("Unexpected JPEG Quantization Table Id (> 3): " + t);
|
||||
}
|
||||
|
||||
table.precision[t] = temp >> 4;
|
||||
|
||||
if (table.precision[t] == 0) {
|
||||
table.precision[t] = 8;
|
||||
}
|
||||
else if (table.precision[t] == 1) {
|
||||
table.precision[t] = 16;
|
||||
}
|
||||
else {
|
||||
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
|
||||
}
|
||||
|
||||
table.tq[t] = 1;
|
||||
|
||||
if (table.precision[t] == 8) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (count > length) {
|
||||
throw new IIOException("JPEG Quantization Table format error");
|
||||
}
|
||||
|
||||
table.quantTables[t][i] = data.readUnsignedByte();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (count > length) {
|
||||
throw new IIOException("JPEG Quantization Table format error");
|
||||
}
|
||||
|
||||
table.quantTables[t][i] = data.readUnsignedShort();
|
||||
count += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count != length) {
|
||||
throw new IIOException("JPEG Quantization Table error, bad segment length: " + length);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* RestartInterval.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: RestartInterval.java,v 1.0 24/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class RestartInterval extends Segment {
|
||||
final int interval;
|
||||
|
||||
private RestartInterval(int interval) {
|
||||
super(JPEG.DRI);
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DRI[" + interval + "]";
|
||||
}
|
||||
|
||||
public static RestartInterval read(final DataInput data, final int length) throws IOException {
|
||||
if (length != 4) {
|
||||
throw new IIOException("Unexpected length of DRI segment: " + length);
|
||||
}
|
||||
|
||||
return new RestartInterval(data.readUnsignedShort());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,34 +28,32 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Represents an unknown segment in the JPEG stream.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Unknown.java,v 1.0 22/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
final class Unknown extends Segment {
|
||||
final byte[] data;
|
||||
* SOFComponent
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SOFComponent.java,v 1.0 22.04.13 16:40 haraldk Exp$
|
||||
*/
|
||||
final class SOFComponent {
|
||||
final int id;
|
||||
final int hSub;
|
||||
final int vSub;
|
||||
final int qtSel;
|
||||
|
||||
private Unknown(final int marker, final byte[] data) {
|
||||
super(marker);
|
||||
|
||||
this.data = data;
|
||||
SOFComponent(int id, int hSub, int vSub, int qtSel) {
|
||||
this.id = id;
|
||||
this.hSub = hSub;
|
||||
this.vSub = vSub;
|
||||
this.qtSel = qtSel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Unknown[%04x, length: %d]", marker, data.length);
|
||||
}
|
||||
|
||||
public static Segment read(int marker, int length, DataInput data) throws IOException {
|
||||
byte[] bytes = new byte[length - 2];
|
||||
data.readFully(bytes);
|
||||
|
||||
return new Unknown(marker, bytes);
|
||||
// Use id either as component number or component name, based on value
|
||||
Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id;
|
||||
return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,36 +28,39 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Comment.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Comment.java,v 1.0 23/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class Comment extends Segment {
|
||||
final String comment;
|
||||
* SOFSegment
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$
|
||||
*/
|
||||
final class SOFSegment {
|
||||
final int marker;
|
||||
final int samplePrecision;
|
||||
final int lines; // height
|
||||
final int samplesPerLine; // width
|
||||
final SOFComponent[] components;
|
||||
|
||||
private Comment(final String comment) {
|
||||
super(JPEG.COM);
|
||||
this.comment = comment;
|
||||
SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) {
|
||||
this.marker = marker;
|
||||
this.samplePrecision = samplePrecision;
|
||||
this.lines = lines;
|
||||
this.samplesPerLine = samplesPerLine;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
final int componentsInFrame() {
|
||||
return components.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "COM[" + comment + "]";
|
||||
}
|
||||
|
||||
public static Segment read(final DataInput data, final int length) throws IOException {
|
||||
byte[] ascii = new byte[length - 2];
|
||||
data.readFully(ascii);
|
||||
|
||||
return new Comment(new String(ascii, StandardCharsets.UTF_8)); // Strictly, it is ASCII, but UTF-8 is compatible
|
||||
return String.format(
|
||||
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
||||
marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
final class Scan extends Segment {
|
||||
final int spectralSelStart; // Start of spectral or predictor selection
|
||||
final int spectralSelEnd; // End of spectral selection
|
||||
final int approxHigh;
|
||||
final int approxLow;
|
||||
|
||||
final Component[] components;
|
||||
|
||||
Scan(final Component[] components, final int spectralStart, final int spectralSelEnd, final int approxHigh, final int approxLow) {
|
||||
super(JPEG.SOS);
|
||||
|
||||
this.components = components;
|
||||
this.spectralSelStart = spectralStart;
|
||||
this.spectralSelEnd = spectralSelEnd;
|
||||
this.approxHigh = approxHigh;
|
||||
this.approxLow = approxLow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"SOS[spectralSelStart: %d, spectralSelEnd: %d, approxHigh: %d, approxLow: %d, components: %s]",
|
||||
spectralSelStart, spectralSelEnd, approxHigh, approxLow, Arrays.toString(components)
|
||||
);
|
||||
}
|
||||
|
||||
public static Scan read(final ImageInputStream data) throws IOException {
|
||||
int length = data.readUnsignedShort();
|
||||
|
||||
return read(new SubImageInputStream(data, length), length);
|
||||
}
|
||||
|
||||
public static Scan read(final DataInput data, final int length) throws IOException {
|
||||
int numComp = data.readUnsignedByte();
|
||||
|
||||
int expected = 6 + numComp * 2;
|
||||
if (expected != length) {
|
||||
throw new IIOException(String.format("Unexpected SOS length: %d != %d", length, expected));
|
||||
}
|
||||
|
||||
Component[] components = new Component[numComp];
|
||||
|
||||
for (int i = 0; i < numComp; i++) {
|
||||
int scanCompSel = data.readUnsignedByte();
|
||||
final int temp = data.readUnsignedByte();
|
||||
|
||||
components[i] = new Component(scanCompSel, temp & 0x0F, temp >> 4);
|
||||
}
|
||||
|
||||
int selection = data.readUnsignedByte();
|
||||
int spectralEnd = data.readUnsignedByte();
|
||||
int temp = data.readUnsignedByte();
|
||||
|
||||
return new Scan(components, selection, spectralEnd, temp >> 4, temp & 0x0F);
|
||||
}
|
||||
|
||||
public final static class Component {
|
||||
final int scanCompSel; // Scan component selector
|
||||
final int acTabSel; // AC table selector
|
||||
final int dcTabSel; // DC table selector
|
||||
|
||||
Component(final int scanCompSel, final int acTabSel, final int dcTabSel) {
|
||||
this.scanCompSel = scanCompSel;
|
||||
this.acTabSel = acTabSel;
|
||||
this.dcTabSel = dcTabSel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("scanCompSel: %d, acTabSel: %d, dcTabSel: %d", scanCompSel, acTabSel, dcTabSel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* 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 "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Segment.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Segment.java,v 1.0 22/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
abstract class Segment {
|
||||
final int marker;
|
||||
|
||||
protected Segment(final int marker) {
|
||||
this.marker = Validate.isTrue(marker >> 8 == 0xFF, marker, "Unknown JPEG marker: 0x%04x");
|
||||
}
|
||||
|
||||
static Segment read(int marker, String identifier, int length, DataInput data) throws IOException {
|
||||
switch (marker) {
|
||||
case JPEG.DHT:
|
||||
return HuffmanTable.read(data, length);
|
||||
case JPEG.DQT:
|
||||
return QuantizationTable.read(data, length);
|
||||
case JPEG.SOF0:
|
||||
case JPEG.SOF1:
|
||||
case JPEG.SOF2:
|
||||
case JPEG.SOF3:
|
||||
case JPEG.SOF5:
|
||||
case JPEG.SOF6:
|
||||
case JPEG.SOF7:
|
||||
case JPEG.SOF9:
|
||||
case JPEG.SOF10:
|
||||
case JPEG.SOF11:
|
||||
case JPEG.SOF13:
|
||||
case JPEG.SOF14:
|
||||
case JPEG.SOF15:
|
||||
return Frame.read(marker, data, length);
|
||||
case JPEG.SOS:
|
||||
return Scan.read(data, length);
|
||||
case JPEG.COM:
|
||||
return Comment.read(data, length);
|
||||
// TODO: JPEG.DAC
|
||||
case JPEG.DRI:
|
||||
return RestartInterval.read(data, length);
|
||||
case JPEG.APP0:
|
||||
case JPEG.APP1:
|
||||
case JPEG.APP2:
|
||||
case JPEG.APP3:
|
||||
case JPEG.APP4:
|
||||
case JPEG.APP5:
|
||||
case JPEG.APP6:
|
||||
case JPEG.APP7:
|
||||
case JPEG.APP8:
|
||||
case JPEG.APP9:
|
||||
case JPEG.APP10:
|
||||
case JPEG.APP11:
|
||||
case JPEG.APP12:
|
||||
case JPEG.APP13:
|
||||
case JPEG.APP14:
|
||||
case JPEG.APP15:
|
||||
return Application.read(marker, identifier, data, length);
|
||||
default:
|
||||
return Unknown.read(marker, length, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import java.io.IOException;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ThumbnailReader.java,v 1.0 18.04.12 12:22 haraldk Exp$
|
||||
*/
|
||||
// TODO: Get rid of the com.sun import!!
|
||||
abstract class ThumbnailReader {
|
||||
|
||||
private final ThumbnailReadProgressListener progressListener;
|
||||
@@ -67,9 +68,19 @@ abstract class ThumbnailReader {
|
||||
}
|
||||
|
||||
static protected BufferedImage readJPEGThumbnail(final ImageReader reader, final ImageInputStream stream) throws IOException {
|
||||
reader.setInput(stream);
|
||||
// try {
|
||||
// try {
|
||||
reader.setInput(stream);
|
||||
|
||||
return reader.read(0);
|
||||
return reader.read(0);
|
||||
// }
|
||||
// finally {
|
||||
// input.close();
|
||||
// }
|
||||
// }
|
||||
// finally {
|
||||
// reader.dispose();
|
||||
// }
|
||||
}
|
||||
|
||||
static protected BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) {
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.mockito.InOrder;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@@ -61,8 +60,7 @@ public class JFIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||
assertNotNull(segments);
|
||||
assertFalse(segments.isEmpty());
|
||||
|
||||
JPEGSegment segment = segments.get(0);
|
||||
return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIF.read(new DataInputStream(segment.segmentData()), segment.segmentLength()));
|
||||
return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIFSegment.read(segments.get(0).data()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.mockito.InOrder;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,7 +64,7 @@ public class JFXXThumbnailReaderTest extends AbstractThumbnailReaderTest {
|
||||
assertFalse(segments.isEmpty());
|
||||
|
||||
JPEGSegment jfxx = segments.get(0);
|
||||
return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXX.read(new DataInputStream(jfxx.segmentData()), jfxx.length()));
|
||||
return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXXSegment.read(jfxx.data(), jfxx.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* JPEGImage10MetadataCleanerTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: JPEGImage10MetadataCleanerTest.java,v 1.0 08/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class JPEGImage10MetadataCleanerTest {
|
||||
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
protected static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
|
||||
|
||||
protected static ImageReaderSpi lookupDelegateProvider() {
|
||||
return JPEGImageReaderSpi.lookupDelegateProvider(IIORegistry.getDefaultInstance());
|
||||
}
|
||||
|
||||
// Unit/regression test for #276
|
||||
@Test
|
||||
public void cleanMetadataMoreThan4DHTSegments() throws Exception {
|
||||
List<String> testData = Arrays.asList("/jpeg/5dhtsegments.jpg", "/jpeg/6dhtsegments.jpg");
|
||||
|
||||
for (String data : testData) {
|
||||
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClass().getResource(data));
|
||||
ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(data))) {
|
||||
|
||||
ImageReader origReader = SPI.delegateProvider.createReaderInstance();
|
||||
origReader.setInput(origInput);
|
||||
|
||||
ImageReader reader = SPI.createReaderInstance();
|
||||
reader.setInput(input);
|
||||
|
||||
IIOMetadata original = origReader.getImageMetadata(0);
|
||||
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
|
||||
|
||||
JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
|
||||
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0));
|
||||
|
||||
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
|
||||
|
||||
NodeList origDHT = origTree.getElementsByTagName("dht");
|
||||
assertEquals(1, origDHT.getLength());
|
||||
|
||||
NodeList cleanDHT = cleanTree.getElementsByTagName("dht");
|
||||
assertEquals(2, cleanDHT.getLength());
|
||||
|
||||
NodeList cleanDHTable = cleanTree.getElementsByTagName("dhtable");
|
||||
NodeList origDHTable = origTree.getElementsByTagName("dhtable");
|
||||
|
||||
assertEquals(origDHTable.getLength(), cleanDHTable.getLength());
|
||||
|
||||
// Note: This also tests that the order of the htables are the same,
|
||||
// but that will only hold if they are already sorted by class.
|
||||
// Luckily, they are in these cases...
|
||||
for (int i = 0; i < origDHTable.getLength(); i++) {
|
||||
Element cleanDHTableElem = (Element) cleanDHTable.item(i);
|
||||
Element origDHTableElem = (Element) origDHTable.item(i);
|
||||
|
||||
assertNotNull(cleanDHTableElem);
|
||||
|
||||
assertNotNull(cleanDHTableElem.getAttribute("class"));
|
||||
assertEquals(origDHTableElem.getAttribute("class"), cleanDHTableElem.getAttribute("class"));
|
||||
|
||||
assertNotNull(cleanDHTableElem.getAttribute("htableId"));
|
||||
assertEquals(origDHTableElem.getAttribute("htableId"), cleanDHTableElem.getAttribute("htableId"));
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
origReader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,14 +94,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)),
|
||||
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
|
||||
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
|
||||
new TestData(getClassLoaderResource("/jpeg/app-marker-missing-null-term.jpg"), new Dimension(200, 150)),
|
||||
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/16_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 16 bit
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/f-18.jpg"), new Dimension(320, 240)), // Lossless RGB, 3 DHTs
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)), // Lossless RGB, 8 bit per component (24 bit)
|
||||
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512)) // Lossless gray, 16 bit
|
||||
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131))
|
||||
);
|
||||
|
||||
// More test data in specific tests below
|
||||
@@ -902,76 +895,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadCMYKAsCMYKSameRGBasRGB() throws IOException {
|
||||
// Make sure CMYK images can be read and still contain their original (embedded) color profile
|
||||
JPEGImageReader reader = createReader();
|
||||
|
||||
// NOTE: Data without ICC profile won't work in this test, as we might end up
|
||||
// using the non-ICC color conversion case and fail miserably on the CI server.
|
||||
List<TestData> cmykData = Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(100, 100)),
|
||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"), new Dimension(100, 100))
|
||||
);
|
||||
|
||||
for (TestData data : cmykData) {
|
||||
reader.setInput(data.getInputStream());
|
||||
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
|
||||
|
||||
assertTrue(data + " has no image types", types.hasNext());
|
||||
|
||||
ImageTypeSpecifier cmykType = null;
|
||||
ImageTypeSpecifier rgbType = null;
|
||||
|
||||
while (types.hasNext()) {
|
||||
ImageTypeSpecifier type = types.next();
|
||||
|
||||
int csType = type.getColorModel().getColorSpace().getType();
|
||||
if (rgbType == null && csType == ColorSpace.TYPE_RGB) {
|
||||
rgbType = type;
|
||||
}
|
||||
else if (cmykType == null && csType == ColorSpace.TYPE_CMYK) {
|
||||
cmykType = type;
|
||||
}
|
||||
|
||||
if (rgbType != null && cmykType != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull("No RGB types for " + data, rgbType);
|
||||
assertNotNull("No CMYK types for " + data, cmykType);
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); // We don't really need to read it all
|
||||
|
||||
param.setDestinationType(cmykType);
|
||||
BufferedImage imageCMYK = reader.read(0, param);
|
||||
|
||||
param.setDestinationType(rgbType);
|
||||
BufferedImage imageRGB = reader.read(0, param);
|
||||
|
||||
assertNotNull(imageCMYK);
|
||||
assertEquals(ColorSpace.TYPE_CMYK, imageCMYK.getColorModel().getColorSpace().getType());
|
||||
|
||||
assertNotNull(imageRGB);
|
||||
assertEquals(ColorSpace.TYPE_RGB, imageRGB.getColorModel().getColorSpace().getType());
|
||||
|
||||
for (int y = 0; y < imageCMYK.getHeight(); y++) {
|
||||
for (int x = 0; x < imageCMYK.getWidth(); x++) {
|
||||
int cmykAsRGB = imageCMYK.getRGB(x, y);
|
||||
int rgb = imageRGB.getRGB(x, y);
|
||||
|
||||
if (rgb != cmykAsRGB) {
|
||||
assertRGBEquals(String.format("Diff at [%d, %d]", x, y), rgb, cmykAsRGB, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadNoJFIFYCbCr() throws IOException {
|
||||
// Basically the same issue as http://stackoverflow.com/questions/9340569/jpeg-image-with-wrong-colors
|
||||
@@ -1006,7 +929,9 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
* Slightly fuzzy RGB equals method. Tolerance +/-5 steps.
|
||||
*/
|
||||
private void assertRGBEquals(int expectedRGB, int actualRGB) {
|
||||
assertRGBEquals("RGB values differ", expectedRGB, actualRGB, 5);
|
||||
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5);
|
||||
assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5);
|
||||
assertEquals((expectedRGB ) & 0xff, (actualRGB ) & 0xff, 5);
|
||||
}
|
||||
|
||||
// Regression: Test subsampling offset within of bounds
|
||||
@@ -1038,8 +963,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
assertNotNull(image);
|
||||
|
||||
// Make sure correct color is actually read, not just left empty
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1055,8 +980,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
assertNotNull(image);
|
||||
|
||||
// Make sure correct color is actually read, not just left empty
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1072,8 +997,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
assertNotNull(image);
|
||||
|
||||
// Make sure correct color is actually read, not just left empty
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1102,8 +1027,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
assertNotNull(image);
|
||||
|
||||
// Make sure correct color is actually read, not just left empty
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1119,8 +1044,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
assertNotNull(image);
|
||||
|
||||
// Make sure correct color is actually read, not just left empty
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
|
||||
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1182,7 +1107,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
e.printStackTrace();
|
||||
fail(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* JPEGProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: JPEGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class JPEGProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new JPEGProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -74,24 +74,6 @@ public class JPEGSegmentImageInputStreamTest {
|
||||
stream.read();
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamNonJPEGArray() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[] {42, 42, 0, 0, 77, 99})));
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamEmpty() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
|
||||
stream.read();
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamEmptyArray() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamRealData() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg")));
|
||||
|
||||
|
Before Width: | Height: | Size: 663 KiB |
|
Before Width: | Height: | Size: 629 KiB |
|
Before Width: | Height: | Size: 195 KiB |
|
Before Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3.2</version>
|
||||
<version>3.2.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
|
||||
@@ -102,7 +102,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
return "Orientation";
|
||||
case TIFF.TAG_SAMPLES_PER_PIXEL:
|
||||
return "SamplesPerPixel";
|
||||
return "SamplesPerPixels";
|
||||
case TIFF.TAG_ROWS_PER_STRIP:
|
||||
return "RowsPerStrip";
|
||||
case TIFF.TAG_STRIP_BYTE_COUNTS:
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||
input.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
else {
|
||||
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
|
||||
int magic = input.readUnsignedShort();
|
||||
if (magic != TIFF.TIFF_MAGIC) {
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
}
|
||||
|
||||
long directoryOffset = input.readUnsignedInt();
|
||||
@@ -105,27 +105,21 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
try {
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
if (entry == null) {
|
||||
// System.err.println("Expected: " + entryCount + " values, found only " + i);
|
||||
// TODO: Log warning?
|
||||
nextOffset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
if (readLinked) {
|
||||
if (nextOffset == -1) {
|
||||
try {
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// catch EOF here as missing EOF marker
|
||||
nextOffset = 0;
|
||||
}
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
}
|
||||
|
||||
// Read linked IFDs
|
||||
@@ -144,7 +138,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
);
|
||||
|
||||
ifds.add(0, new IFD(entries));
|
||||
|
||||
|
||||
return new EXIFDirectory(ifds);
|
||||
}
|
||||
|
||||
@@ -176,7 +170,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.get(0), entry.getType()));
|
||||
}
|
||||
else {
|
||||
else {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.toArray(new IFD[subIFDs.size()]), entry.getType()));
|
||||
}
|
||||
@@ -213,9 +207,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
offsets = (long[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null
|
||||
? value.getClass()
|
||||
: null)));
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
|
||||
}
|
||||
|
||||
return offsets;
|
||||
@@ -226,6 +218,11 @@ public final class EXIFReader extends MetadataReader {
|
||||
int tagId = pInput.readUnsignedShort();
|
||||
short type = pInput.readShort();
|
||||
|
||||
// This isn't really an entry, and the directory entry count was wrong OR bad data...
|
||||
if (tagId == 0 && type == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int count = pInput.readInt(); // Number of values
|
||||
|
||||
// It's probably a spec violation to have count 0, but we'll be lenient about it
|
||||
@@ -234,33 +231,32 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
if (type <= 0 || type > 13) {
|
||||
pInput.skipBytes(4); // read Value
|
||||
|
||||
// Invalid tag, this is just for debugging
|
||||
long offset = pInput.getStreamPosition() - 12l;
|
||||
long offset = pInput.getStreamPosition() - 8l;
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
|
||||
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
}
|
||||
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -508,8 +504,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
//////////////////////
|
||||
// TODO: Stream based hex dump util?
|
||||
public static class HexDump {
|
||||
private HexDump() {
|
||||
}
|
||||
private HexDump() {}
|
||||
|
||||
private static final int WIDTH = 32;
|
||||
|
||||
@@ -523,7 +518,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % WIDTH == 0) {
|
||||
if (i > 0) {
|
||||
if (i > 0 ) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(String.format("%08x: ", i + off + offset));
|
||||
|
||||
@@ -158,10 +158,6 @@ public final class EXIFWriter extends MetadataWriter {
|
||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
||||
}
|
||||
|
||||
public long computeIFDOffsetSize(final Collection<Entry> directory) {
|
||||
return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
|
||||
}
|
||||
|
||||
private long computeDataSize(final Directory directory) {
|
||||
long dataSize = 0;
|
||||
|
||||
|
||||
@@ -49,22 +49,6 @@ public interface JPEG {
|
||||
/** Define Huffman Tables segment marker (DHT). */
|
||||
int DHT = 0xFFC4;
|
||||
|
||||
/** Comment (COM) */
|
||||
int COM = 0xFFFE;
|
||||
|
||||
/** Define Number of Lines (DNL). */
|
||||
int DNL = 0xFFDC;
|
||||
/** Define Restart Interval (DRI). */
|
||||
int DRI = 0xFFDD;
|
||||
/** Define Hierarchical Progression (DHP). */
|
||||
int DHP = 0xFFDE;
|
||||
/** Expand reference components (EXP). */
|
||||
int EXP = 0xFFDF;
|
||||
/** Temporary use in arithmetic coding (TEM). */
|
||||
int TEM = 0xFF01;
|
||||
/** Define Define Arithmetic Coding conditioning (DAC). */
|
||||
int DAC = 0xFFCC;
|
||||
|
||||
// App segment markers (APPn).
|
||||
int APP0 = 0xFFE0;
|
||||
int APP1 = 0xFFE1;
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.Arrays;
|
||||
public final class JPEGSegment implements Serializable {
|
||||
final int marker;
|
||||
final byte[] data;
|
||||
private final int length;
|
||||
final int length;
|
||||
|
||||
private transient String id;
|
||||
|
||||
@@ -53,15 +53,11 @@ public final class JPEGSegment implements Serializable {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public int segmentLength() {
|
||||
int segmentLength() {
|
||||
// This is the length field as read from the stream
|
||||
return length;
|
||||
}
|
||||
|
||||
public InputStream segmentData() {
|
||||
return data != null ? new ByteArrayInputStream(data) : null;
|
||||
}
|
||||
|
||||
public int marker() {
|
||||
return marker;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class JPEGSegmentUtil {
|
||||
|
||||
if (isRequested(segment, segmentIdentifiers)) {
|
||||
if (segments == Collections.EMPTY_LIST) {
|
||||
segments = new ArrayList<>();
|
||||
segments = new ArrayList<JPEGSegment>();
|
||||
}
|
||||
|
||||
segments.add(segment);
|
||||
|
||||