Compare commits

..

16 Commits

Author SHA1 Message Date
Harald Kuhr caaececa06 [maven-release-plugin] prepare for next development iteration 2015-08-14 12:11:07 +02:00
Harald Kuhr ffd4731334 [maven-release-plugin] prepare release twelvemonkeys-3.1.2 2015-08-14 12:11:00 +02:00
Harald Kuhr 7c53c5b7b5 TMI-154: Fix for AccessControlException when reading "Generic CMYK.icc" 2015-08-14 11:51:27 +02:00
Harald Kuhr c9cfe04dc9 TMI-JPEG: Reading inverted Adobe JPEGs 2015-07-14 20:50:53 +02:00
Harald Kuhr 9da25168ee TMI-150: Fix for 24 bit images with bitmask. 2015-07-11 13:20:04 +02:00
Harald Kuhr b0c928d278 TMI-146: Now correctly sets ExtraSamples only when there are more components in the raster than color components in the color space. 2015-06-17 13:26:45 +02:00
Harald Kuhr e221c7374b [maven-release-plugin] prepare for next development iteration 2015-06-11 11:53:40 +02:00
Harald Kuhr 258a6cf4f4 [maven-release-plugin] prepare release twelvemonkeys-3.1.1 2015-06-11 11:53:35 +02:00
Harald Kuhr ab5cbb15ed TMI-142: Added missing resource file, now making sure the TIFFImageWriter is registered. 2015-06-08 15:37:20 +02:00
Harald Kuhr 02d65481c5 TMI-140: JPEG with corrupted ICC profile (new kind) can now be read. 2015-06-08 15:35:27 +02:00
Harald Kuhr 081c65b314 Preparing JPEGImageReader for extension. 2015-06-08 15:35:10 +02:00
Harald Kuhr 7a78d398c3 Fixed JavaDoc (you're welcome Sigi ;-)). 2015-06-08 15:31:55 +02:00
Harald Kuhr 493c304722 TM-138: DateUtil doesn't support sub-hour offset timezones. 2015-06-08 15:31:22 +02:00
Harald Kuhr 8d62d74860 TM-138: DateUtil doesn't support sub-hour offset timezones. 2015-06-08 15:31:06 +02:00
Harald Kuhr d51ac8cb83 TMI-134: Cannot read PSD images with PSD Layer Mask data size 28 2015-06-08 15:30:19 +02:00
Harald Kuhr 646ac719c1 Updating POM to version 3.1.1-SNAPSHOT 2015-06-08 14:18:49 +02:00
302 changed files with 2583 additions and 16052 deletions
-6
View File
@@ -1,6 +0,0 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling
# - openjdk7
+35 -97
View File
@@ -1,8 +1,6 @@
## Latest
Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](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.0.2 is released.
## About
@@ -22,7 +20,7 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b
Mainstream format support
#### BMP - MS Windows/IBM OS/2 Device Independent Bitmap
#### MS Windows/IBM OS/2 Device Independent Bitmap (BMP) *3.1*
* Read support for all known versions of the DIB/BMP format
* Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE
@@ -34,8 +32,6 @@ Mainstream format support
#### JPEG
* Read support for the following JPEG "flavors":
* All JFIF compliant JPEGs
* All Exif compliant JPEGs
* YCbCr JPEGs without JFIF segment (converted to RGB, using embedded ICC profile)
* CMYK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
* Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
@@ -69,7 +65,7 @@ without success so far).
Alternatively, if you have or know of a JPEG-2000 implementation in Java with a suitable license, get in touch. :-)
#### PNM - NetPBM Portable Any Map
#### NetPBM Portable Any Map (PNM) *3.1*
* Read support for the following file types:
* PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel
@@ -82,7 +78,7 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* PAM in 'P7' (binary) format
* Standard metadata support
#### PSD - Adobe Photoshop Document
#### Adobe Photoshop Document (PSD)
* Read support for the following file types:
* Monochrome, 1 channel, 1 bit
@@ -93,16 +89,16 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* CMYK, 4-5 channels, 8, 16 and 32 bit
* Read support for the following compression types:
* Uncompressed
* RLE (PackBits)<
* RLE (PackBits)
* Layer support
* Image layers only, in all of the above types
* Thumbnail support
* JPEG
* RAW (RGB)
* Support for "Large Document Format" (PSB)
* Native and Standard metadata support
* Native metadata support
#### TIFF - Aldus/Adobe Tagged Image File Format
#### Aldus/Adobe Tagged Image File Format (TIFF)
* Read support for the following "Baseline" TIFF file types:
* Class B (Bi-level), all relevant compression types, 1 bit per sample
@@ -111,42 +107,30 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* Class R (RGB), all relevant compression types, 8 or 16 bits per sample, unsigned integer
* Read support for the following TIFF extensions:
* Tiling
* Class F (Facsimile), CCITT Modified Huffman RLE, T4 and T6 (type 2, 3 and 4) compressions.
* LZW Compression (type 5)
* "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined
* JPEG Compression (type 7)
* ZLib (aka Adobe-style Deflate) Compression (type 8)
* Deflate Compression (type 32946)
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression
* Alpha channel (ExtraSamples type 1/Associated Alpha and type 2/Unassociated Alpha)
* Alpha channel (ExtraSamples type 1/Associated Alpha)
* CMYK data (PhotometricInterpretation type 5/Separated)
* YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
* CIELab data in TIFF, ITU and ICC variants (PhotometricInterpretation type 9, 10 and 11)
* Planar data (PlanarConfiguration type 2/Planar)
* ICC profiles (ICCProfile)
* BitsPerSample values up to 16 for most PhotometricInterpretations
* Multiple images (pages) in one file
* Write support for most "Baseline" TIFF options
* Uncompressed, PackBits, ZLib and Deflate
* Additional support for CCITT T4 and and T6 compressions.
* Currently missing the CCITT fax encodings
* Additional support for LZW and JPEG (type 7) compressions
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate
* Native and Standard metadata support
Legacy formats
#### HDR - Radiance High Dynamic Range RGBE Format
#### Commodore Amiga/Electronic Arts Interchange File Format (IFF)
* Read support for the most common RGBE (.hdr) format
* Samples are converted to 32 bit floating point (`float`) and normalized using a global tone mapper by default.
* Support for custom global tone mappers
* Alternatively, use a "null-tone mapper", for unnormalized data (allows local tone mapping)
* Unconverted RGBE samples accessible using `readRaster`
* Standard metadata support
#### IFF - Commodore Amiga/Electronic Arts Interchange File Format
* Legacy format, allows reading popular image format from the Commodore Amiga computer.
* Legacy format, allows reading popular image from the Commodore Amiga computer.
* Read support for the following file types:
* ILBM Indexed color, 1-8 interleaved bit planes, including 6 bit EHB
* ILBM Gray, 8 bit interleaved bit planes
@@ -162,7 +146,7 @@ Legacy formats
* Uncompressed
* RLE (PackBits)
#### PCX - ZSoft Paintbrush Format
#### ZSoft Paintbrush Format (PCX) *3.1*
* Read support for the following file types:
* Indexed color, 1, 2, 4 or 8 bits per pixel, bit planes or interleaved
@@ -174,7 +158,7 @@ Legacy formats
* RLE compressed
* Standard metadata support
#### PICT - Apple Mac Paint Picture Format
#### Apple Mac Paint Picture Format (PICT)
* Legacy format, especially useful for reading OS X clipboard data.
* Read support for the following file types:
@@ -185,7 +169,7 @@ Legacy formats
* Write support for RGB pixel data:
* QuickDraw pixmap
#### SGI - Silicon Graphics Image Format
#### Silicon Graphics Image Format (SGI) *3.1*
* Read support for the following file types:
* 1, 2, 3 or 4 channel image data
@@ -195,7 +179,7 @@ Legacy formats
* RLE compressed
* Standard metadata support
#### TGA - Truevision TGA Image Format
#### Truevision TGA Image Format (TGA) *3.1*
* Read support for the following file types:
* ColorMapped
@@ -208,39 +192,35 @@ Legacy formats
Icon/other formats
#### ICNS - Apple Icon Image
#### Apple Icon Image (ICNS)
* Read support for the following icon types:
* All known "native" icon types
* Large PNG encoded icons
* Large JPEG 2000 encoded icons (requires JPEG 2000 ImageIO plugin or fallback to `sips` command line tool)
#### ICO & CUR - MS Windows Icon and Cursor Formats
#### MS Windows Icon and Cursor Formats (ICO & CUR)
* Read support for the following file types:
* ICO Indexed color, 1, 4 and 8 bit
* ICO RGB, 16, 24 and 32 bit
* CUR Indexed color, 1, 4 and 8 bit
* CUR RGB, 16, 24 and 32 bit
* *3.1* Note: These formats are now part of the BMP plugin
#### Thumbs.db - MS Windows Thumbs DB
#### MS Windows Thumbs DB (Thumbs.db)
* Read support
Other formats, using 3rd party libraries
#### SVG - Scalable Vector Graphics
#### Scalable Vector Graphics (SVG)
* Read-only support using Batik
#### WMF - MS Windows MetaFile
#### MS Windows MetaFile (WMF)
* Limited read-only support using Batik
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](http://xmlgraphics.apache.org/security.html), and make sure you use
either version 1.6.1, 1.7.1 or 1.8+.*
## Basic usage
@@ -439,9 +419,9 @@ Build the project (using [Maven](http://maven.apache.org/download.cgi)):
$ mvn package
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
It's possible to build using OpenJDK, but some tests will fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether. To build using JDK 8, you need to pass `-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider` to revert to the color manangement system used in Java 7.
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
@@ -479,12 +459,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.0.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.0.2</version> <!-- Alternatively, build your own version -->
</dependency>
</dependencies>
@@ -492,66 +472,25 @@ 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.0.2.jar
twelvemonkeys-common-io-3.0.2.jar
twelvemonkeys-common-image-3.0.2.jar
twelvemonkeys-imageio-core-3.0.2.jar
twelvemonkeys-imageio-metadata-3.0.2.jar
twelvemonkeys-imageio-jpeg-3.0.2.jar
twelvemonkeys-imageio-tiff-3.0.2.jar
### Links to prebuilt binaries
##### Latest version (3.2.x)
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)
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 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 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)
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)
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)
##### Old version (3.0.x)
Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8*.
Common dependencies
* [common-lang-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar)
* [common-io-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar)
* [common-image-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar)
ImageIO dependencies
* [imageio-core-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar)
* [imageio-metadata-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar)
ImageIO plugins
* [imageio-jpeg-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar)
* [imageio-tiff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar)
@@ -561,15 +500,14 @@ ImageIO plugins
* [imageio-icns-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar)
* [imageio-ico-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar)
* [imageio-thumbsdb-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar)
* [imageio-jmagick-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar)
Servlet support
* [servlet-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
## License
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
+1 -6
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
@@ -58,11 +58,6 @@
<artifactId>imageio-bmp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-hdr</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-icns</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.1.3-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);
@@ -1465,7 +1465,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
for (int i = 0; i < dstHeight; i++) {
//contribY[i].n = 0;
contribY[i].p = new Contributor[(int) (width * 2.0 + 1 + 0.5)];
contribY[i].p = new Contributor[(int) (width * 2.0 + 1)];
double center = (double) i / yscale;
int left = (int) Math.ceil(center - width);
@@ -1516,7 +1516,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
else {
for (int i = 0; i < dstHeight; ++i) {
//contribY[i].n = 0;
contribY[i].p = new Contributor[(int) (fwidth * 2 + 1 + 0.5)];
contribY[i].p = new Contributor[(int) (fwidth * 2 + 1)];
double center = (double) i / yscale;
double left = 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
@@ -69,7 +69,7 @@ public class ResampleOpTestCase {
}
private void assertResampleBufferedImageTypes(final int pFilterType) {
List<String> exceptions = new ArrayList<>();
List<String> exceptions = new ArrayList<String>();
// Test all image types in BufferedImage
for (int type = BufferedImage.TYPE_INT_ARGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) {
@@ -304,30 +304,6 @@ public class ResampleOpTestCase {
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
}
// https://github.com/haraldk/TwelveMonkeys/issues/195
@Test
public void testAIOOBEHeight() {
BufferedImage myImage = new BufferedImage(100, 354, BufferedImage.TYPE_INT_ARGB);
for (int i = 19; i > 0; i--) {
ResampleOp resampler = new ResampleOp(100, i, ResampleOp.FILTER_LANCZOS);
BufferedImage resizedImage = resampler.filter(myImage, null);
assertNotNull(resizedImage);
}
}
// 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() {
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -22,7 +22,7 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
@@ -32,7 +32,7 @@
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
-72
View File
@@ -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</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);
}
}
+8 -39
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -23,54 +23,27 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<groupId>batik</groupId>
<artifactId>batik-rasterizer-ext</artifactId>
<version>${batik.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extensions</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extension</artifactId>
<version>${batik.version}</version>
<version>1.6-1</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>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<groupId>batik</groupId>
<artifactId>batik-svggen</artifactId>
<version>${batik.version}</version>
<version>1.6-1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<groupId>batik</groupId>
<artifactId>batik-transcoder</artifactId>
<version>${batik.version}</version>
<version>1.6-1</version>
<scope>provided</scope>
<!--
@@ -86,8 +59,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) {
@@ -34,7 +34,6 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Locale;
@@ -52,7 +51,6 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates an {@code SVGImageReaderSpi}.
*/
@SuppressWarnings("WeakerAccess")
public SVGImageReaderSpi() {
super(new SVGProviderInfo());
}
@@ -61,10 +59,10 @@ 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.
// however it requires the doctype to be "svg", which may not be correct
// in all cases...
try {
pInput.mark();
@@ -76,80 +74,54 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
// Skip over leading WS
}
// If it's not a tag, this can't be valid XML
if (b != '<') {
if (!((b == '<') && (pInput.read() == '?') && (pInput.read() == 'x') && (pInput.read() == 'm')
&& (pInput.read() == 'l'))) {
return false;
}
// Algorithm for detecting SVG:
// - Skip until begin tag '<' and read 4 bytes
// - if next is "?" skip until "?>" and start over
// - else if next is "!--" skip until "-->" and start over
// - else if next is "!DOCTYPE " skip any whitespace
// - compare next 3 bytes against "svg", return result
// - else
// - compare next 3 bytes against "svg", return result
byte[] buffer = new byte[4];
while (true) {
pInput.readFully(buffer);
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
}
// Okay, we have XML. But, is it really SVG?
boolean docTypeFound = false;
while (!docTypeFound) {
while (pInput.read() != '<') {
// Skip over, until begin tag
}
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
}
}
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
&& pInput.read() == 'T' && pInput.read() == 'Y'
&& pInput.read() == 'P' && pInput.read() == 'E') {
// This is the DOCTYPE declaration
while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over WS
}
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
// It's SVG, identified by DOCTYPE
return true;
}
// DOCTYPE found, but not SVG
return false;
}
// Something else, we'll skip
}
else {
// This is a normal tag
if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g'
&& (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) {
// It's SVG, identified by root tag
if ((b = pInput.read()) != '!') {
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g'
&& (Character.isWhitespace((char) (b = pInput.read())) || b == ':')) {
// TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)!
return true;
}
// If the tag is not "svg", this isn't SVG
// If this is not a comment, or the DOCTYPE declaration, the doc has no DOCTYPE and it can't be svg
return false;
}
while ((pInput.readByte() & 0xFF) != '<') {
// Skip over, until next begin tag or EOF
// There might be comments before the doctype, unfortunately...
// If next is "--", this is a comment
if ((b = pInput.read()) == '-' && pInput.read() == '-') {
while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
// Skip until end of comment
}
}
// If we are lucky, this is DOCTYPE declaration
if (b == 'D' && pInput.read() == 'O' && pInput.read() == 'C' && pInput.read() == 'T'
&& pInput.read() == 'Y' && pInput.read() == 'P' && pInput.read() == 'E') {
docTypeFound = true;
while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over WS
}
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
return true;
}
}
}
}
catch (EOFException ignore) {
// Possible for small files...
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));
}
}
}
}
@@ -59,8 +59,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
return Arrays.asList(
new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)),
new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400))
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100))
);
}
@@ -120,13 +119,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();
}
}
@@ -1,18 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-147 -70 294 345">
<g fill="#a4c639">
<use stroke-width="14.4" xlink:href="#b" stroke="#FFF"/>
<use xlink:href="#a" transform="scale(-1,1)"/>
<g id="a" stroke="#FFF" stroke-width="7.2">
<rect rx="6.5" transform="rotate(29)" height="86" width="13" y="-86" x="14"/>
<rect id="c" rx="24" height="133" width="48" y="41" x="-143"/>
<use y="97" x="85" xlink:href="#c"/>
</g>
<g id="b">
<ellipse cy="41" rx="91" ry="84"/>
<rect rx="22" height="182" width="182" y="20" x="-91"/>
</g>
</g>
<g stroke="#FFF" stroke-width="7.2" fill="#FFF">
<path d="m-95 44.5h190"/><circle cx="-42" r="4"/><circle cx="42" r="4"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 706 B

+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>
@@ -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);
}
}
@@ -28,16 +28,17 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
/**
* BMPMetadata.
*/
final class BMPMetadata extends AbstractMetadata {
final class BMPMetadata extends IIOMetadata {
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */
public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
@@ -45,13 +46,46 @@ final class BMPMetadata extends AbstractMetadata {
private final int[] colorMap;
BMPMetadata(final DIBHeader header, final int[] colorMap) {
super(true, nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", null, null);
this.header = Validate.notNull(header, "header");
this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap;
standardFormatSupported = true;
}
@Override public boolean isReadOnly() {
return true;
}
@Override public Node getAsTree(final String formatName) {
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
return getStandardTree();
}
else if (nativeMetadataFormatName.equals(formatName)) {
return getNativeTree();
}
else {
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
}
}
@Override public void mergeTree(final String formatName, final Node root) {
if (isReadOnly()) {
throw new IllegalStateException("Metadata is read-only");
}
}
@Override public void reset() {
if (isReadOnly()) {
throw new IllegalStateException("Metadata is read-only");
}
}
@Override
protected Node getNativeTree() {
public String getNativeMetadataFormatName() {
return nativeMetadataFormatName;
}
private Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
addChildNode(root, "BMPVersion", header.getBMPVersion());
@@ -136,8 +170,7 @@ final class BMPMetadata extends AbstractMetadata {
return child;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
@Override protected IIOMetadataNode getStandardChromaNode() {
// NOTE: BMP files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again...
@@ -164,8 +197,7 @@ final class BMPMetadata extends AbstractMetadata {
return null;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
@Override protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
compressionTypeName.setAttribute("value", "NONE");
@@ -197,8 +229,7 @@ final class BMPMetadata extends AbstractMetadata {
// }
}
@Override
protected IIOMetadataNode getStandardDataNode() {
@Override protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
@@ -263,8 +294,7 @@ final class BMPMetadata extends AbstractMetadata {
return buffer.toString();
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
@Override protected IIOMetadataNode getStandardDimensionNode() {
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
@@ -272,16 +302,16 @@ final class BMPMetadata extends AbstractMetadata {
addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null);
addChildNode(dimension, "VerticalPhysicalPixelSpacing", null);
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
//
// if (header.topDown) {
// imageOrientation.setAttribute("value", "FlipH");
// }
// else {
// imageOrientation.setAttribute("value", "Normal");
// }
//
// dimension.appendChild(imageOrientation);
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
//
// if (header.topDown) {
// imageOrientation.setAttribute("value", "FlipH");
// }
// else {
// imageOrientation.setAttribute("value", "Normal");
// }
//
// dimension.appendChild(imageOrientation);
return dimension;
}
@@ -295,8 +325,7 @@ final class BMPMetadata extends AbstractMetadata {
// No tiling
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
@Override protected IIOMetadataNode getStandardTransparencyNode() {
return null;
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
@@ -321,6 +321,8 @@ abstract class DIBImageReader extends ImageReaderBase {
descriptors.put(pEntry, descriptor);
}
System.err.println("descriptor: " + descriptor);
return descriptor.getImage();
}
@@ -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();
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
@@ -20,7 +20,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -1,151 +0,0 @@
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.lang.Validate;
/**
* Converts between CIE L*a*b* and sRGB color spaces.
*/
// Code adapted from ImageJ's Color_Space_Converter.java (Public Domain):
// http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
public final class CIELabColorConverter {
// TODO: Create interface in the color package?
// TODO: Make YCbCr/YCCK -> RGB/CMYK implement same interface?
public enum Illuminant {
D50(new float[] {96.4212f, 100.0f, 82.5188f}),
D65(new float[] {95.0429f, 100.0f, 108.8900f});
private final float[] whitePoint;
Illuminant(final float[] wp) {
whitePoint = Validate.isTrue(wp != null && wp.length == 3, wp, "Bad white point definition: %s");
}
public float[] getWhitePoint() {
return whitePoint;
}
}
private final float[] whitePoint;
public CIELabColorConverter(final Illuminant illuminant) {
whitePoint = Validate.notNull(illuminant, "illuminant").getWhitePoint();
}
private float clamp(float x) {
if (x < 0.0f) {
return 0.0f;
}
else if (x > 255.0f) {
return 255.0f;
}
else {
return x;
}
}
public void toRGB(float l, float a, float b, float[] rgbResult) {
XYZtoRGB(LABtoXYZ(l, a, b, rgbResult), rgbResult);
}
/**
* Convert LAB to XYZ.
* @param L
* @param a
* @param b
* @return XYZ values
*/
private float[] LABtoXYZ(float L, float a, float b, float[] xyzResult) {
// Significant speedup: Removing Math.pow
float y = (L + 16.0f) / 116.0f;
float y3 = y * y * y; // Math.pow(y, 3.0);
float x = (a / 500.0f) + y;
float x3 = x * x * x; // Math.pow(x, 3.0);
float z = y - (b / 200.0f);
float z3 = z * z * z; // Math.pow(z, 3.0);
if (y3 > 0.008856f) {
y = y3;
}
else {
y = (y - (16.0f / 116.0f)) / 7.787f;
}
if (x3 > 0.008856f) {
x = x3;
}
else {
x = (x - (16.0f / 116.0f)) / 7.787f;
}
if (z3 > 0.008856f) {
z = z3;
}
else {
z = (z - (16.0f / 116.0f)) / 7.787f;
}
xyzResult[0] = x * whitePoint[0];
xyzResult[1] = y * whitePoint[1];
xyzResult[2] = z * whitePoint[2];
return xyzResult;
}
/**
* Convert XYZ to RGB
* @param xyz
* @return RGB values
*/
private float[] XYZtoRGB(final float[] xyz, final float[] rgbResult) {
return XYZtoRGB(xyz[0], xyz[1], xyz[2], rgbResult);
}
private float[] XYZtoRGB(final float X, final float Y, final float Z, float[] rgbResult) {
float x = X / 100.0f;
float y = Y / 100.0f;
float z = Z / 100.0f;
float r = x * 3.2406f + y * -1.5372f + z * -0.4986f;
float g = x * -0.9689f + y * 1.8758f + z * 0.0415f;
float b = x * 0.0557f + y * -0.2040f + z * 1.0570f;
// assume sRGB
if (r > 0.0031308f) {
r = ((1.055f * (float) pow(r, 1.0 / 2.4)) - 0.055f);
}
else {
r = (r * 12.92f);
}
if (g > 0.0031308f) {
g = ((1.055f * (float) pow(g, 1.0 / 2.4)) - 0.055f);
}
else {
g = (g * 12.92f);
}
if (b > 0.0031308f) {
b = ((1.055f * (float) pow(b, 1.0 / 2.4)) - 0.055f);
}
else {
b = (b * 12.92f);
}
// convert 0..1 into 0..255
rgbResult[0] = clamp(r * 255);
rgbResult[1] = clamp(g * 255);
rgbResult[2] = clamp(b * 255);
return rgbResult;
}
// TODO: Test, to figure out if accuracy is good enough.
// Visual inspection looks good! The author claims 5-12% error, worst case up to 25%...
// http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
static double pow(final double a, final double b) {
long tmp = Double.doubleToLongBits(a);
long tmp2 = (long) (b * (tmp - 4606921280493453312L)) + 4606921280493453312L;
return Double.longBitsToDouble(tmp2);
}
}
@@ -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);
}
}
@@ -1,94 +0,0 @@
package com.twelvemonkeys.imageio.color;
/**
* Fast YCbCr to RGB conversion.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author Original code by Werner Randelshofer (used by permission).
*/
public final class YCbCrConverter {
/**
* Define tables for YCC->RGB color space conversion.
*/
private final static int SCALEBITS = 16;
private final static int MAXJSAMPLE = 255;
private final static int CENTERJSAMPLE = 128;
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
/**
* Initializes tables for YCC->RGB color space conversion.
*/
private static void buildYCCtoRGBtable() {
if (ColorSpaces.DEBUG) {
System.err.println("Building YCC conversion table");
}
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
// Cr=>R value is nearest int to 1.40200 * x
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cb=>B value is nearest int to 1.77200 * x
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cr=>G value is scaled-up -0.71414 * x
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
// Cb=>G value is scaled-up -0.34414 * x
// We also add in ONE_HALF so that need not do it in inner loop
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
}
}
static {
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]);
}
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 green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
rgb[offset] = clamp(red);
rgb[offset + 2] = clamp(blue);
rgb[offset + 1] = clamp(green);
}
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
int y = yCbCr[offset] & 0xff;
int cr = yCbCr[offset + 2] & 0xff;
int cb = yCbCr[offset + 1] & 0xff;
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
}
private static byte clamp(int val) {
return (byte) Math.max(0, Math.min(255, val));
}
}
@@ -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,68 +0,0 @@
package com.twelvemonkeys.imageio.color;
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
/**
* CIELabColorConverterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: CIELabColorConverterTest.java,v 1.0 22/10/15 harald.kuhr Exp$
*/
public class CIELabColorConverterTest {
@Test(expected = IllegalArgumentException.class)
public void testNoIllumninant() {
new CIELabColorConverter(null);
}
@Test
public void testD50() {
CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D50);
float[] rgb = new float[3];
converter.toRGB(100, -128, -128, rgb);
assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
converter.toRGB(100, 0, 0, rgb);
assertArrayEquals(new float[] {255, 252, 220}, rgb, 5);
converter.toRGB(0, 0, 0, rgb);
assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
converter.toRGB(100, 0, 127, rgb);
assertArrayEquals(new float[] {255, 249, 0}, rgb, 5);
converter.toRGB(50, -128, 127, rgb);
assertArrayEquals(new float[] {0, 152, 0}, rgb, 2);
converter.toRGB(50, 127, -128, rgb);
assertArrayEquals(new float[] {222, 0, 255}, rgb, 2);
}
@Test
public void testD65() {
CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D65);
float[] rgb = new float[3];
converter.toRGB(100, -128, -128, rgb);
assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
converter.toRGB(100, 0, 0, rgb);
assertArrayEquals(new float[] {255, 252, 255}, rgb, 5);
converter.toRGB(0, 0, 0, rgb);
assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
converter.toRGB(100, 0, 127, rgb);
assertArrayEquals(new float[] {255, 250, 0}, rgb, 5);
converter.toRGB(50, -128, 127, rgb);
assertArrayEquals(new float[] {0, 152, 0}, rgb, 3);
converter.toRGB(50, 127, -128, rgb);
assertArrayEquals(new float[] {184, 0, 255}, rgb, 5);
}
}
@@ -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++) {
@@ -63,7 +63,6 @@ public abstract class ImageWriterAbstractTestCase {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
protected abstract ImageWriter createImageWriter();
@@ -121,20 +120,23 @@ public abstract class ImageWriterAbstractTestCase {
for (RenderedImage testData : getTestData()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
writer.setOutput(stream);
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
try {
writer.write(drawSomething((BufferedImage) testData));
}
catch (IOException e) {
fail(e.getMessage());
}
finally {
stream.close(); // Force data to be written
}
assertTrue("No image data written", buffer.size() > 0);
}
}
@SuppressWarnings("ConstantConditions")
@Test
public void testWriteNull() throws IOException {
ImageWriter writer = createImageWriter();
-30
View File
@@ -1,30 +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.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
<description>
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
/**
* HDR.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
interface HDR {
byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
}
@@ -1,123 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* HDRHeader.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
final class HDRHeader {
private static final String KEY_FORMAT = "FORMAT=";
private static final String KEY_PRIMARIES = "PRIMARIES=";
private static final String KEY_EXPOSURE = "EXPOSURE=";
private static final String KEY_GAMMA = "GAMMA=";
private static final String KEY_SOFTWARE = "SOFTWARE=";
private int width;
private int height;
private String software;
public static HDRHeader read(final ImageInputStream stream) throws IOException {
HDRHeader header = new HDRHeader();
while (true) {
String line = stream.readLine().trim();
if (line.isEmpty()) {
// This is the last line before the dimensions
break;
}
if (line.startsWith("#?")) {
// Program specifier, don't need that...
}
else if (line.startsWith("#")) {
// Comment (ignore)
}
else if (line.startsWith(KEY_FORMAT)) {
String format = line.substring(KEY_FORMAT.length()).trim();
if (!format.equals("32-bit_rle_rgbe")) {
throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
}
// TODO: Support the 32-bit_rle_xyze format
}
else if (line.startsWith(KEY_PRIMARIES)) {
// TODO: We are going to need these values...
// Should contain 8 (RGB + white point) coordinates
}
else if (line.startsWith(KEY_EXPOSURE)) {
// TODO: We are going to need these values...
}
else if (line.startsWith(KEY_GAMMA)) {
// TODO: We are going to need these values...
}
else if (line.startsWith(KEY_SOFTWARE)) {
header.software = line.substring(KEY_SOFTWARE.length()).trim();
}
else {
// ...ignore
}
}
// TODO: Proper parsing of width/height and orientation!
String dimensionsLine = stream.readLine().trim();
String[] dims = dimensionsLine.split("\\s");
if (dims[0].equals("-Y") && dims[2].equals("+X")) {
header.height = Integer.parseInt(dims[1]);
header.width = Integer.parseInt(dims[3]);
return header;
}
else {
throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
}
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getSoftware() {
return software;
}
}
@@ -1,55 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
import javax.imageio.ImageReadParam;
/**
* HDRImageReadParam.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
*/
public final class HDRImageReadParam extends ImageReadParam {
static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
public ToneMapper getToneMapper() {
return toneMapper;
}
public void setToneMapper(final ToneMapper toneMapper) {
this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
}
}
@@ -1,258 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
/**
* HDRImageReader.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
public final class HDRImageReader extends ImageReaderBase {
// Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
private HDRHeader header;
protected HDRImageReader(final ImageReaderSpi provider) {
super(provider);
}
@Override
protected void resetMembers() {
header = null;
}
private void readHeader() throws IOException {
if (header == null) {
header = HDRHeader.read(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
}
imageInput.seek(imageInput.getFlushedPosition());
}
@Override
public int getWidth(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
return header.getWidth();
}
@Override
public int getHeight(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
return header.getHeight();
}
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
}
@Override
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
checkBounds(imageIndex);
readHeader();
int width = getWidth(imageIndex);
int height = getHeight(imageIndex);
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
Rectangle srcRegion = new Rectangle();
Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, destination, srcRegion, destRegion);
WritableRaster raster = destination.getRaster()
.createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
int xSub = param != null ? param.getSourceXSubsampling() : 1;
int ySub = param != null ? param.getSourceYSubsampling() : 1;
// Allow pluggable tone mapper via ImageReadParam
ToneMapper toneMapper = param instanceof HDRImageReadParam
? ((HDRImageReadParam) param).getToneMapper()
: HDRImageReadParam.DEFAULT_TONE_MAPPER;
byte[] rowRGBE = new byte[width * 4];
float[] rgb = new float[3];
processImageStarted(imageIndex);
// Process one scanline of RGBE data at a time
for (int srcY = 0; srcY < height; srcY++) {
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
if (dstY >= destRegion.height) {
break;
}
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
if (srcY % ySub == 0 && dstY >= destRegion.y) {
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
if (dstX >= destRegion.width) {
break;
}
RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
// Map/clamp RGB values into visible range, normally [0...1]
toneMapper.map(rgb);
raster.setDataElements(dstX, dstY, rgb);
}
}
processImageProgress(srcY * 100f / height);
if (abortRequested()) {
processReadAborted();
break;
}
}
processImageComplete();
return destination;
}
@Override
public boolean canReadRaster() {
return true;
}
@Override
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
checkBounds(imageIndex);
readHeader();
int width = getWidth(imageIndex);
int height = getHeight(imageIndex);
Rectangle srcRegion = new Rectangle();
Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, null, srcRegion, destRegion);
destRegion = srcRegion; // We don't really care about destination for raster
BufferedImage destination = new BufferedImage(srcRegion.width, srcRegion.height, BufferedImage.TYPE_4BYTE_ABGR);
WritableRaster raster = destination.getRaster();
int xSub = param != null ? param.getSourceXSubsampling() : 1;
int ySub = param != null ? param.getSourceYSubsampling() : 1;
byte[] rowRGBE = new byte[width * 4];
byte[] pixelRGBE = new byte[width];
processImageStarted(imageIndex);
// Process one scanline of RGBE data at a time
for (int srcY = 0; srcY < height; srcY++) {
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
if (dstY >= destRegion.height) {
break;
}
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
if (srcY % ySub == 0 && dstY >= destRegion.y) {
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
if (dstX >= destRegion.width) {
break;
}
System.arraycopy(rowRGBE, srcX * 4, pixelRGBE, 0, 4);
raster.setDataElements(dstX, dstY, pixelRGBE);
}
}
processImageProgress(srcY * 100f / height);
if (abortRequested()) {
processReadAborted();
break;
}
}
processImageComplete();
return destination.getRaster();
}
@Override
public ImageReadParam getDefaultReadParam() {
return new HDRImageReadParam();
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
return new HDRMetadata(header);
}
public static void main(final String[] args) throws IOException {
File file = new File(args[0]);
BufferedImage image = ImageIO.read(file);
showIt(image, file.getName());
}
}
@@ -1,84 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
/**
* HDRImageReaderSpi.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
public final class HDRImageReaderSpi extends ImageReaderSpiBase {
public HDRImageReaderSpi() {
super(new HDRProviderInfo());
}
@Override
public boolean canDecodeInput(final Object source) throws IOException {
if (!(source instanceof ImageInputStream)) {
return false;
}
ImageInputStream stream = (ImageInputStream) source;
stream.mark();
try {
// NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
// although some sources claim that #?RGBE is also used.
byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
stream.readFully(magic);
return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
|| Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
}
finally {
stream.reset();
}
}
@Override
public ImageReader createReaderInstance(Object extension) throws IOException {
return new HDRImageReader(this);
}
@Override
public String getDescription(final Locale locale) {
return "Radiance RGBE High Dynaimc Range (HDR) image reader";
}
}
@@ -1,127 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.metadata.IIOMetadataNode;
final class HDRMetadata extends AbstractMetadata {
private final HDRHeader header;
HDRMetadata(final HDRHeader header) {
this.header = header;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
csType.setAttribute("name", "RGB");
// TODO: Support XYZ
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", "3");
chroma.appendChild(numChannels);
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
blackIsZero.setAttribute("value", "TRUE");
chroma.appendChild(blackIsZero);
return chroma;
}
// No compression
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "RLE");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", "UnsignedIntegral");
node.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
bitsPerSample.setAttribute("value", "8 8 8 8");
node.appendChild(bitsPerSample);
return node;
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
// TODO: Support other orientations
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "Normal");
dimension.appendChild(imageOrientation);
return dimension;
}
// No document node
@Override
protected IIOMetadataNode getStandardTextNode() {
if (header.getSoftware() != null) {
IIOMetadataNode text = new IIOMetadataNode("Text");
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
textEntry.setAttribute("keyword", "Software");
textEntry.setAttribute("value", header.getSoftware());
text.appendChild(textEntry);
return text;
}
return null;
}
// No tiling
// No transparency
}
@@ -1,55 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
/**
* HDRProviderInfo.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
final class HDRProviderInfo extends ReaderWriterProviderInfo {
protected HDRProviderInfo() {
super(
HDRProviderInfo.class,
new String[] {"HDR", "hdr", "RGBE", "rgbe"},
new String[] {"hdr", "rgbe", "xyze", "pic"},
new String[] {"image/vnd.radiance"},
"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
null,
null,
false, null, null, null, null,
true, null, null, null, null
);
}
}
@@ -1,494 +0,0 @@
package com.twelvemonkeys.imageio.plugins.hdr;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This file contains code to read and write four byte rgbe file format
* developed by Greg Ward. It handles the conversions between rgbe and
* pixels consisting of floats. The data is assumed to be an array of floats.
* By default there are three floats per pixel in the order red, green, blue.
* (RGBE_DATA_??? values control this.) Only the mimimal header reading and
* writing is implemented. Each routine does error checking and will return
* a status value as defined below. This code is intended as a skeleton so
* feel free to modify it to suit your needs. <P>
* <p/>
* Ported to Java and restructured by Kenneth Russell. <BR>
* posted to http://www.graphics.cornell.edu/~bjw/ <BR>
* written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 <BR>
* based on code written by Greg Ward <BR>
* <p/>
* Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
*/
final class RGBE {
// Flags indicating which fields in a Header are valid
private static final int VALID_PROGRAMTYPE = 0x01;
private static final int VALID_GAMMA = 0x02;
private static final int VALID_EXPOSURE = 0x04;
private static final String gammaString = "GAMMA=";
private static final String exposureString = "EXPOSURE=";
private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
public static class Header {
// Indicates which fields are valid
private int valid;
// Listed at beginning of file to identify it after "#?".
// Defaults to "RGBE"
private String programType;
// Image has already been gamma corrected with given gamma.
// Defaults to 1.0 (no correction)
private float gamma;
// A value of 1.0 in an image corresponds to <exposure>
// watts/steradian/m^2. Defaults to 1.0.
private float exposure;
// Width and height of image
private int width;
private int height;
private Header(int valid,
String programType,
float gamma,
float exposure,
int width,
int height) {
this.valid = valid;
this.programType = programType;
this.gamma = gamma;
this.exposure = exposure;
this.width = width;
this.height = height;
}
public boolean isProgramTypeValid() {
return ((valid & VALID_PROGRAMTYPE) != 0);
}
public boolean isGammaValid() {
return ((valid & VALID_GAMMA) != 0);
}
public boolean isExposureValid() {
return ((valid & VALID_EXPOSURE) != 0);
}
public String getProgramType() {
return programType;
}
public float getGamma() {
return gamma;
}
public float getExposure() {
return exposure;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String toString() {
StringBuffer buf = new StringBuffer();
if (isProgramTypeValid()) {
buf.append(" Program type: ");
buf.append(getProgramType());
}
buf.append(" Gamma");
if (isGammaValid()) {
buf.append(" [valid]");
}
buf.append(": ");
buf.append(getGamma());
buf.append(" Exposure");
if (isExposureValid()) {
buf.append(" [valid]");
}
buf.append(": ");
buf.append(getExposure());
buf.append(" Width: ");
buf.append(getWidth());
buf.append(" Height: ");
buf.append(getHeight());
return buf.toString();
}
}
public static Header readHeader(final DataInput in) throws IOException {
int valid = 0;
String programType = null;
float gamma = 1.0f;
float exposure = 1.0f;
int width = 0;
int height = 0;
String buf = in.readLine();
if (buf == null) {
throw new IOException("Unexpected EOF reading magic token");
}
if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
valid |= VALID_PROGRAMTYPE;
programType = buf.substring(2);
buf = in.readLine();
if (buf == null) {
throw new IOException("Unexpected EOF reading line after magic token");
}
}
boolean foundFormat = false;
boolean done = false;
while (!done) {
if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
foundFormat = true;
}
else if (buf.startsWith(gammaString)) {
valid |= VALID_GAMMA;
gamma = Float.parseFloat(buf.substring(gammaString.length()));
}
else if (buf.startsWith(exposureString)) {
valid |= VALID_EXPOSURE;
exposure = Float.parseFloat(buf.substring(exposureString.length()));
}
else {
Matcher m = widthHeightPattern.matcher(buf);
if (m.matches()) {
width = Integer.parseInt(m.group(2));
height = Integer.parseInt(m.group(1));
done = true;
}
}
if (!done) {
buf = in.readLine();
if (buf == null) {
throw new IOException("Unexpected EOF reading header");
}
}
}
if (!foundFormat) {
throw new IOException("No FORMAT specifier found");
}
return new Header(valid, programType, gamma, exposure, width, height);
}
/**
* Simple read routine. Will not correctly handle run length encoding.
*/
public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
byte[] rgbe = new byte[4];
float[] rgb = new float[3];
int offset = 0;
while (numpixels-- > 0) {
in.readFully(rgbe);
rgbe2float(rgb, rgbe, 0);
data[offset++] = rgb[0];
data[offset++] = rgb[1];
data[offset++] = rgb[2];
}
}
public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
int numExpected = 4 * numpixels;
in.readFully(data, offset, numExpected);
}
public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
int scanline_width, int num_scanlines) throws IOException {
byte[] rgbe = new byte[4];
byte[] scanline_buffer = null;
int ptr, ptr_end;
int count;
byte[] buf = new byte[2];
if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
// run length encoding is not allowed so read flat
readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
}
// read in each successive scanline
while (num_scanlines > 0) {
in.readFully(rgbe);
if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
// this file is not run length encoded
data[offset++] = rgbe[0];
data[offset++] = rgbe[1];
data[offset++] = rgbe[2];
data[offset++] = rgbe[3];
readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
}
if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
throw new IOException("Wrong scanline width " +
(((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
", expected " + scanline_width);
}
if (scanline_buffer == null) {
scanline_buffer = new byte[4 * scanline_width];
}
ptr = 0;
// read each of the four channels for the scanline into the buffer
for (int i = 0; i < 4; i++) {
ptr_end = (i + 1) * scanline_width;
while (ptr < ptr_end) {
in.readFully(buf);
if ((buf[0] & 0xFF) > 128) {
// a run of the same value
count = (buf[0] & 0xFF) - 128;
if ((count == 0) || (count > ptr_end - ptr)) {
throw new IOException("Bad scanline data");
}
while (count-- > 0) {
scanline_buffer[ptr++] = buf[1];
}
}
else {
// a non-run
count = buf[0] & 0xFF;
if ((count == 0) || (count > ptr_end - ptr)) {
throw new IOException("Bad scanline data");
}
scanline_buffer[ptr++] = buf[1];
if (--count > 0) {
in.readFully(scanline_buffer, ptr, count);
ptr += count;
}
}
}
}
// copy byte data to output
for (int i = 0; i < scanline_width; i++) {
data[offset++] = scanline_buffer[i];
data[offset++] = scanline_buffer[i + scanline_width];
data[offset++] = scanline_buffer[i + 2 * scanline_width];
data[offset++] = scanline_buffer[i + 3 * scanline_width];
}
num_scanlines--;
}
}
/**
* Standard conversion from float pixels to rgbe pixels.
*/
public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
float v;
int e;
v = red;
if (green > v) {
v = green;
}
if (blue > v) {
v = blue;
}
if (v < 1e-32f) {
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
}
else {
FracExp fe = frexp(v);
v = (float) (fe.getFraction() * 256.0 / v);
rgbe[0] = (byte) (red * v);
rgbe[1] = (byte) (green * v);
rgbe[2] = (byte) (blue * v);
rgbe[3] = (byte) (fe.getExponent() + 128);
}
}
/**
* Standard conversion from rgbe to float pixels. Note: Ward uses
* ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
* range [0,1] to map back into the range [0,1].
*/
public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
float f;
if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
}
else {
rgb[0] = 0;
rgb[1] = 0;
rgb[2] = 0;
}
}
public static double ldexp(double value, int exp) {
if (!finite(value) || value == 0.0) {
return value;
}
value = scalbn(value, exp);
// No good way to indicate errno (want to avoid throwing
// exceptions because don't know about stability of calculations)
// if(!finite(value)||value==0.0) errno = ERANGE;
return value;
}
//----------------------------------------------------------------------
// Internals only below this point
//
//----------------------------------------------------------------------
// Math routines, some fdlibm-derived
//
static class FracExp {
private double fraction;
private int exponent;
public FracExp(double fraction, int exponent) {
this.fraction = fraction;
this.exponent = exponent;
}
public double getFraction() {
return fraction;
}
public int getExponent() {
return exponent;
}
}
private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
private static final double huge = 1.0e+300;
private static final double tiny = 1.0e-300;
private static int hi(double x) {
long bits = Double.doubleToRawLongBits(x);
return (int) (bits >>> 32);
}
private static int lo(double x) {
long bits = Double.doubleToRawLongBits(x);
return (int) bits;
}
private static double fromhilo(int hi, int lo) {
return Double.longBitsToDouble((((long) hi) << 32) |
(((long) lo) & 0xFFFFFFFFL));
}
private static FracExp frexp(double x) {
int hx = hi(x);
int ix = 0x7fffffff & hx;
int lx = lo(x);
int e = 0;
if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
return new FracExp(x, e); // 0,inf,nan
}
if (ix < 0x00100000) { // subnormal
x *= two54;
hx = hi(x);
ix = hx & 0x7fffffff;
e = -54;
}
e += (ix >> 20) - 1022;
hx = (hx & 0x800fffff) | 0x3fe00000;
lx = lo(x);
return new FracExp(fromhilo(hx, lx), e);
}
private static boolean finite(double x) {
int hx;
hx = hi(x);
return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
}
/**
* copysign(double x, double y) <BR>
* copysign(x,y) returns a value with the magnitude of x and
* with the sign bit of y.
*/
private static double copysign(double x, double y) {
return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
}
/**
* scalbn (double x, int n) <BR>
* scalbn(x,n) returns x* 2**n computed by exponent
* manipulation rather than by actually performing an
* exponentiation or a multiplication.
*/
private static double scalbn(double x, int n) {
int hx = hi(x);
int lx = lo(x);
int k = (hx & 0x7ff00000) >> 20; // extract exponent
if (k == 0) { // 0 or subnormal x
if ((lx | (hx & 0x7fffffff)) == 0) {
return x; // +-0
}
x *= two54;
hx = hi(x);
k = ((hx & 0x7ff00000) >> 20) - 54;
if (n < -50000) {
return tiny * x; // underflow
}
}
if (k == 0x7ff) {
return x + x; // NaN or Inf
}
k = k + n;
if (k > 0x7fe) {
return huge * copysign(huge, x); // overflow
}
if (k > 0) {
// normal result
return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
}
if (k <= -54) {
if (n > 50000) {
// in case integer overflow in n+k
return huge * copysign(huge, x); // overflow
}
else {
return tiny * copysign(tiny, x); // underflow
}
}
k += 54; // subnormal result
x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
return x * twom54;
}
//----------------------------------------------------------------------
// Test harness
//
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
Header header = RGBE.readHeader(in);
System.err.println("Header for file \"" + args[i] + "\":");
System.err.println(" " + header);
byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
in.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
@@ -1,63 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
/**
* DefaultToneMapper.
* <p/>
* Normalizes values to range [0...1] using:
*
* <p><em>V<sub>out</sub> = V<sub>in</sub> / (V<sub>in</sub> + C)</em></p>
*
* Where <em>C</em> is constant.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
*/
public final class DefaultToneMapper implements ToneMapper {
private final float constant;
public DefaultToneMapper() {
this(1);
}
public DefaultToneMapper(final float constant) {
this.constant = constant;
}
@Override
public void map(final float[] rgb) {
// Default Vo = Vi / (Vi + 1)
for (int i = 0; i < rgb.length; i++) {
rgb[i] = rgb[i] / (rgb[i] + constant);
}
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
/**
* GammaToneMapper.
* <p/>
* Normalizes values to range [0...1] using:
*
* <p><em>V<sub>out</sub> = A V<sub>in</sub><sup>\u03b3</sup></em></p>
*
* Where <em>A</em> is constant and <em>\u03b3</em> is the gamma.
* Values > 1 are clamped.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
*/
public final class GammaToneMapper implements ToneMapper {
private final float constant;
private final float gamma;
public GammaToneMapper() {
this(0.5f, .25f);
}
public GammaToneMapper(final float constant, final float gamma) {
this.constant = constant;
this.gamma = gamma;
}
@Override
public void map(final float[] rgb) {
// Gamma Vo = A * Vi^y
for (int i = 0; i < rgb.length; i++) {
rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
}
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
/**
* NullToneMapper.
* <p/>
* This {@code ToneMapper} does *not* normalize or clamp values
* to range [0...1], but leaves the values as-is.
* Useful for applications that implements custom tone mapping.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
*/
public final class NullToneMapper implements ToneMapper {
@Override
public void map(float[] rgb) {
}
}
@@ -1,40 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
/**
* ToneMapper.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
*/
public interface ToneMapper {
void map(float[] rgb);
}
@@ -1 +0,0 @@
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
@@ -1,85 +0,0 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* TGAImageReaderTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
*/
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
);
}
@Override
protected ImageReaderSpi createProvider() {
return new HDRImageReaderSpi();
}
@Override
protected Class<HDRImageReader> getReaderClass() {
return HDRImageReader.class;
}
@Override
protected HDRImageReader createReader() {
return new HDRImageReader(createProvider());
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("hdr", "rgbe", "xyze");
}
@Override
protected List<String> getMIMETypes() {
return Collections.singletonList(
"image/vnd.radiance"
);
}
}
@@ -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();
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>
@@ -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();
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>
@@ -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 -3
View File
@@ -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
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.1.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -20,7 +20,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
@@ -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,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.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(int marker, final String identifier, final byte[] data) {
super(marker);
this.identifier = Validate.notEmpty(identifier, "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);
}
}
}
@@ -1,63 +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;
import java.nio.charset.StandardCharsets;
/**
* 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;
private Comment(final String comment) {
super(JPEG.COM);
this.comment = comment;
}
@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
}
}
@@ -28,7 +28,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
@@ -62,7 +61,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,14 +95,14 @@ 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) {
thumbnail = readJPEG();
}
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
return thumbnail;
}
@@ -133,10 +132,14 @@ final class EXIFThumbnailReader extends ThumbnailReader {
input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
try {
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input);
try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) {
try {
return readJPEGThumbnail(reader, stream);
}
finally {
stream.close();
}
}
finally {
input.close();
@@ -192,15 +195,15 @@ final class EXIFThumbnailReader extends ThumbnailReader {
break;
case 6:
// YCbCr
for (int i = 0; i < thumbSize; i += 3) {
YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
}
break;
default:
throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation);
}
return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h);
return ThumbnailReader.readRawThumbnail(thumbData, thumbData.length, 0, w, h);
}
throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail");
@@ -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,172 +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 javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
final int l[][][] = new int[4][2][16];
final int th[] = new int[4]; // 1: this table is presented
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is presented
public static final int MSB = 0x80000000;
private HuffmanTable() {
super(JPEG.DHT);
tc[0][0] = 0;
tc[1][0] = 0;
tc[2][0] = 0;
tc[3][0] = 0;
tc[0][1] = 0;
tc[1][1] = 0;
tc[2][1] = 0;
tc[3][1] = 0;
th[0] = 0;
th[1] = 0;
th[2] = 0;
th[3] = 0;
}
protected void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) {
if (tc[i][j] != 0) {
buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]);
}
}
}
}
// 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 currentTable, temp;
int k;
temp = 256;
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;
}
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() {
// TODO: Id and class for tables
return "DHT[]";
}
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);
}
}
}
@@ -30,7 +30,6 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
@@ -109,7 +108,7 @@ 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; //createSegmentIds();
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
private static Map<Integer, List<String>> createSegmentIds() {
Map<Integer, List<String>> map = new LinkedHashMap<>();
@@ -134,10 +133,6 @@ public class JPEGImageReader extends ImageReaderBase {
map.put(JPEG.SOF14, null);
map.put(JPEG.SOF15, null);
map.put(JPEG.DQT, null);
map.put(JPEG.DHT, null);
map.put(JPEG.SOS, null);
return Collections.unmodifiableMap(map);
}
@@ -154,7 +149,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);
@@ -203,12 +198,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);
}
@@ -218,46 +207,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);
}
@@ -311,15 +267,10 @@ public class JPEGImageReader extends ImageReaderBase {
@Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
// If delegate can determine the spec, we'll just go with that
try {
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
if (rawType != null) {
return rawType;
}
}
catch (IIOException | NullPointerException ignore) {
// Fall through
if (rawType != null) {
return rawType;
}
// Otherwise, consult the image metadata
@@ -361,48 +312,52 @@ public class JPEGImageReader extends ImageReaderBase {
assertInput();
checkBounds(imageIndex);
Frame sof = getSOF();
ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCT adobeDCT = getAdobeDCT();
boolean bogusAdobeDCT = false;
// CompoundDirectory exif = getExif();
// if (exif != null) {
// System.err.println("exif: " + exif);
// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
//
// if (exifIFDEntry != null) {
// Directory exifIFD = (Directory) exifIFDEntry.getValue();
// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
// }
// }
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() != 3 ||
adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() != 4)) {
SOFSegment sof = getSOF();
ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCTSegment adobeDCT = getAdobeDCT();
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()
));
bogusAdobeDCT = true;
adobeDCT = null;
}
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().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() && (
bogusAdobeDCT ||
if (delegate.canReadRaster() && (
sourceCSType == JPEGColorSpace.CMYK ||
sourceCSType == JPEGColorSpace.YCCK ||
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
profile != null && !ColorSpaces.isCS_sRGB(profile)) ||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null) { // TODO: Issue warning?
if (DEBUG) {
System.out.println("Reading using raster and extra conversion");
System.out.println("ICC color profile: " + profile);
}
// 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) {
@@ -412,7 +367,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);
@@ -468,10 +423,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
@@ -516,13 +468,14 @@ public class JPEGImageReader extends ImageReaderBase {
// Apply source color conversion from implicit color space
if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.YCbCrA) {
convertYCbCr2RGB(raster);
YCbCrConverter.convertYCbCr2RGB(raster);
}
else if (csType == JPEGColorSpace.YCCK) {
// TODO: Need to rethink this (non-) inversion, see #147
// TODO: Allow param to specify inversion, or possibly the PDF decode array
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
convertYCCK2CMYK(raster);
boolean invert = true;// || (adobeDCT.flags0 & 0x8000) == 0;
YCbCrConverter.convertYCCK2CMYK(raster, invert);
}
else if (csType == JPEGColorSpace.CMYK) {
invertCMYK(raster);
@@ -547,7 +500,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:
@@ -589,27 +542,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
@@ -618,7 +571,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:
@@ -636,7 +589,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;
}
@@ -664,7 +617,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;
}
@@ -711,27 +664,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 " +segment.identifier() + " segment, ignoring");
continue;
}
throw e;
}
}
this.segments = segments;
readSegments();
if (DEBUG) {
System.out.println("Read metadata in " + (System.currentTimeMillis() - start) + " ms");
@@ -739,13 +672,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) {
@@ -757,62 +690,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
@@ -848,13 +844,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();
@@ -921,8 +915,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.
@@ -938,15 +931,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().readRaster(segments, imageInput);
}
public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException {
return delegate.readRaster(imageIndex, param);
}
@@ -980,18 +965,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:
@@ -1000,9 +985,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) {
@@ -1087,29 +1072,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;
@@ -1133,32 +1113,130 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
public static void convertYCbCr2RGB(final Raster raster) {
final int height = raster.getHeight();
final int width = raster.getWidth();
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
/**
* Static inner class for lazy-loading of conversion tables.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author Original code by Werner Randelshofer
*/
static final class YCbCrConverter {
/** Define tables for YCC->RGB color space conversion. */
private final static int SCALEBITS = 16;
private final static int MAXJSAMPLE = 255;
private final static int CENTERJSAMPLE = 128;
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
YCbCrConverter.convertYCbCr2RGB(data, data, (x + y * width) * 3);
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
/**
* Initializes tables for YCC->RGB color space conversion.
*/
private static void buildYCCtoRGBtable() {
if (DEBUG) {
System.err.println("Building YCC conversion table");
}
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
// Cr=>R value is nearest int to 1.40200 * x
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cb=>B value is nearest int to 1.77200 * x
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cr=>G value is scaled-up -0.71414 * x
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
// Cb=>G value is scaled-up -0.34414 * x
// We also add in ONE_HALF so that need not do it in inner loop
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
}
}
}
public static void convertYCCK2CMYK(final Raster raster) {
final int height = raster.getHeight();
final int width = raster.getWidth();
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
static {
buildYCCtoRGBtable();
}
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int offset = (x + y * width) * 4;
// YCC -> CMY
YCbCrConverter.convertYCbCr2RGB(data, data, offset);
// Inverse K
data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
static void convertYCbCr2RGB(final Raster raster) {
final int height = raster.getHeight();
final int width = raster.getWidth();
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
convertYCbCr2RGB(data, data, (x + y * width) * 3);
}
}
}
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
int y = yCbCr[offset ] & 0xff;
int cr = yCbCr[offset + 2] & 0xff;
int cb = yCbCr[offset + 1] & 0xff;
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
}
static void convertYCCK2CMYK(final Raster raster, final boolean invert) {
final int height = raster.getHeight();
final int width = raster.getWidth();
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
if (invert) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
convertYCCK2CMYKInverted(data, data, (x + y * width) * 4);
}
}
}
else {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
convertYCCK2CMYK(data, data, (x + y * width) * 4);
}
}
}
}
private static void convertYCCK2CMYKInverted(byte[] ycck, byte[] cmyk, int offset) {
// Inverted
int y = 255 - ycck[offset ] & 0xff;
int cb = 255 - ycck[offset + 1] & 0xff;
int cr = 255 - ycck[offset + 2] & 0xff;
int k = 255 - ycck[offset + 3] & 0xff;
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
cmyk[offset ] = clamp(cmykC);
cmyk[offset + 1] = clamp(cmykM);
cmyk[offset + 2] = clamp(cmykY);
cmyk[offset + 3] = (byte) k; // K passes through unchanged
}
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
int y = ycck[offset ] & 0xff;
int cb = ycck[offset + 1] & 0xff;
int cr = ycck[offset + 2] & 0xff;
int k = ycck[offset + 3] & 0xff;
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
cmyk[offset ] = clamp(cmykC);
cmyk[offset + 1] = clamp(cmykM);
cmyk[offset + 2] = clamp(cmykY);
cmyk[offset + 3] = (byte) k; // K passes through unchanged
}
private static byte clamp(int val) {
return (byte) Math.max(0, Math.min(255, val));
}
}
private class ProgressDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
@@ -1393,8 +1471,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,686 +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.List;
final class JPEGLosslessDecoder {
private final ImageInputStream input;
private final Frame frame;
private final HuffmanTable huffTable;
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) {
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
huffTable = get(segments, HuffmanTable.class); // For non-lossless there can be multiple of DHTs
RestartInterval dri = get(segments, RestartInterval.class);
restartInterval = dri != null ? dri.interval : 0;
input = data;
}
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");
}
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;
dcTab[i] = HuffTab[scanComps[i].dcTabSel][0];
acTab[i] = HuffTab[scanComps[i].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
final int temp[] = new int[1]; // to store remainder bits
final int index[] = new int[1];
temp[0] = 0;
index[0] = 0;
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)) {
//empty
}
else {
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 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++) {
final int value = getHuffmanValue(dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
final int n = getn(prev, value, temp, index);
final 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,170 +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 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 {
/**
* 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
* @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, input);
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);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded");
}
}
// 3 components, assumed to be RGB
if (decoder.getNumComponents() == 3) {
switch (decoder.getPrecision()) {
case 8:
return to24Bit3ComponentRGB(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded");
}
}
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded");
}
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) {
@@ -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,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,37 +28,39 @@
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;
import java.util.Arrays;
/**
* 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;
* 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 RestartInterval(int interval) {
super(JPEG.DRI);
this.interval = interval;
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 "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());
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) {

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