Merge branch 'master' of github.com:haraldk/TwelveMonkeys

This commit is contained in:
Harald Kuhr 2020-11-19 21:24:23 +01:00
commit 78af95d747
29 changed files with 597 additions and 546 deletions

506
README.md
View File

@ -1,15 +1,13 @@
## Latest [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys) [![StackOverflow](https://img.shields.io/badge/tag-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys)
[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/haraldk76/100)
Latest release is TwelveMonkeys ImageIO [3.6](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio%20AND%20v:3.6) (July. 10th, 2020).
[Release notes](https://github.com/haraldk/TwelveMonkeys/releases/latest).
## About ## About
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO. TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
These plugins extends the number of image file formats supported in Java, using the javax.imageio.* package. These plugins extend the number of image file formats supported in Java, using the `javax.imageio.*` package.
The main purpose of this project is to provide support for formats not covered by the JRE itself. The main purpose of this project is to provide support for formats not covered by the JRE itself.
Support for formats is important, both to be able to read data found Support for formats is important, both to be able to read data found
@ -19,246 +17,58 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b
---- ----
## Features ## File formats supported
Mainstream format support | Plugin | Format | Description | Read | Write | Metadata | Notes |
| ------ | -------- | ----------- |:----:|:-----:| -------- | ----- |
| Batik | **SVG** | Scalable Vector Graphics | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
| | WMF | MS Windows Metafile | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | ✔ | ✔ | Native & Standard |
| | CUR | MS Windows Cursor Format | ✔ | - | - |
| | ICO | MS Windows Icon Format | ✔ | ✔ | - |
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | Standard |
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - |
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | - |
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | Native & Standard |
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | Standard |
| | DCX | Multi-page PCX fax document | ✔ | - | Standard |
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple Mac Paint Picture Format | ✔ | - | - |
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | Standard |
| | PBM | NetPBM Portable Bit Map | ✔ | - | Standard |
| | PGM | NetPBM Portable Grey Map | ✔ | - | Standard |
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | Standard |
| | PFM | Portable Float Map | ✔ | - | Standard |
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | - | Native & Standard |
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native & Standard |
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | Standard |
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | Standard |
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | Native & Standard |
| | BigTIFF | | ✔ | - | Native & Standard |
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | Standard | In progress
| XWD | XWD | X11 Window Dump Format | ✔ | - | Standard |
#### BMP - MS Windows/IBM OS/2 Device Independent Bitmap
* Read support for all known versions of the DIB/BMP format
* Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE
* RGB, 16, 24 and 32 bit
* Embedded PNG and JPEG data
* Windows and OS/2 versions
* Native and standard metadata format
#### 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)
* JPEGs containing ICC profiles with interpretation other than 'Perceptual' or class other than 'Display'
* JPEGs containing ICC profiles that are incompatible with stream data, corrupted ICC profiles or corrupted `ICC_PROFILE` segments
* JPEGs using non-standard color spaces, unsupported by Java 2D
* JPEGs with APP14/Adobe segments with length other than 14 bytes
* 8 bit JPEGs with 16 bit DQT segments
* Issues warnings instead of throwing exceptions in cases of corrupted or non-conformant data where ever the image
data can still be read in a reasonable way
* Thumbnail support:
* JFIF thumbnails (even if stream contains "inconsistent metadata")
* JFXX thumbnails (JPEG, Indexed and RGB)
* EXIF thumbnails (JPEG, RGB and YCbCr)
* Metadata support:
* JPEG metadata in both standard and native formats (even if stream contains "inconsistent metadata")
* `javax_imageio_jpeg_image_1.0` format (currently as native format, may change in the future)
* Non-conforming combinations of JFIF, Exif and Adobe markers, using "unknown" segments in the
"MarkerSequence" tag for the unsupported segments (for `javax_imageio_jpeg_image_1.0` format)
* Extended write support:
* CMYK JPEGs
* YCCK JPEGs in progress
#### JPEG-2000
* Possibly coming in the future, pending some license issues.
If you are one of the authors, or know one of the authors and/or the current license holders of either the original
jj2000 package or the JAI ImageIO project, please contact me (I've tried to get in touch in various ways,
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
* Read support for the following file types:
* PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel
* PGM in 'P2' (ASCII) and 'P5' (binary) formats, up to 16/32 bits per pixel
* PPM in 'P3' (ASCII) and 'P6' (binary) formats, up to 16/32 bits per pixel component
* PAM in 'P7' (binary) format up to 32 bits per pixel component
* Limited support for PFM in 'Pf' (gray) and 'PF' (RGB) formats, 32 bits floating point
* Write support for the following formats:
* PPM in 'P6' (binary) format
* PAM in 'P7' (binary) format
* Standard metadata support
#### PSD - Adobe Photoshop Document
* Read support for the following file types:
* Monochrome, 1 channel, 1 bit
* Indexed, 1 channel, 8 bit
* Gray, 1 channel, 8, 16 and 32 bit
* Duotone, 1 channel, 8, 16 and 32 bit
* RGB, 3-4 channels, 8, 16 and 32 bit
* CMYK, 4-5 channels, 8, 16 and 32 bit
* Read support for the following compression types:
* Uncompressed
* 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
#### TIFF - Aldus/Adobe Tagged Image File Format
* Read support for the following "Baseline" TIFF file types:
* Class B (Bi-level), all relevant compression types, 1 bit per sample
* Class G (Gray), all relevant compression types, 2, 4, 8, 16 or 32 bits per sample, unsigned integer
* Class P (Palette/indexed color), all relevant compression types, 1, 2, 4, 8 or 16 bits per sample, unsigned integer
* 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)
* 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.
* 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
* 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.
* 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
* ILBM RGB, 24 and 32 bit interleaved bit planes
* ILBM HAM6 and HAM8
* PBM Indexed color, 1-8 bit,
* PBM Gray, 8 bit
* PBM RGB, 24 and 32 bit
* PBM HAM6 and HAM8
* Write support
* ILBM Indexed color, 1-8 bits per sample, 8 bit gray, 24 and 32 bit true color.
* Support for the following compression types (read/write):
* Uncompressed
* RLE (PackBits)
#### PCX - ZSoft Paintbrush Format
* Read support for the following file types:
* Indexed color, 1, 2, 4 or 8 bits per pixel, bit planes or interleaved
* Grayscale, 8 bits per pixel
* Color (RGB), 8 bits per pixel component
* Read support for DCX (multi-page) fax format, containing any of the above types
* Support for the following compression types:
* Uncompressed (experimental)
* RLE compressed
* Standard metadata support
#### PICT - Apple Mac Paint Picture Format
* Legacy format, especially useful for reading OS X clipboard data.
* Read support for the following file types:
* QuickDraw (format support is not complete, but supports most OS X clipboard data as well as RGB pixel data)
* QuickDraw bitmap
* QuickDraw pixmap
* QuickTime stills
* Write support for RGB pixel data:
* QuickDraw pixmap
#### SGI - Silicon Graphics Image Format
* Read support for the following file types:
* 1, 2, 3 or 4 channel image data
* 8 or 16 bits per pixel component
* Support for the following compression types:
* Uncompressed
* RLE compressed
* Standard metadata support
#### TGA - Truevision TGA Image Format
* Read support for the following file types:
* ColorMapped
* Monochrome
* TrueColor
* Support for the following compression types:
* Uncompressed
* RLE compressed
* Standard metadata support
* Write support
Icon/other formats
#### ICNS - Apple Icon Image
* 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)
* Write support for PNG encoded icons
#### ICO & CUR - MS Windows Icon and Cursor Formats
* 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
* Write support
* *3.1* Note: These formats are now part of the BMP plugin
#### Thumbs.db - MS Windows Thumbs DB
* Read support
Other formats, using 3rd party libraries
#### SVG - Scalable Vector Graphics
* Read-only support using Batik
#### WMF - MS Windows MetaFile
* 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+.*
**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, 1.8+ or later.*
## Basic usage ## Basic usage
Most of the time, all you need to do is simply include the plugins in your project and write: Most of the time, all you need to do is simply include the plugins in your project and write:
BufferedImage image = ImageIO.read(file); ```java
BufferedImage image = ImageIO.read(file);
```
This will load the first image of the file, entirely into memory. This will load the first image of the file, entirely into memory.
The basic and simplest form of writing is: The basic and simplest form of writing is:
if (!ImageIO.write(image, format, file)) { ```java
// Handle image not written case if (!ImageIO.write(image, format, file)) {
} // Handle image not written case
}
```
This will write the entire image into a single file, using the default settings for the given format. This will write the entire image into a single file, using the default settings for the given format.
@ -269,50 +79,50 @@ The plugins are discovered automatically at run time. See the [FAQ](#faq) for mo
If you need more control of read parameters and the reading process, the common idiom for reading is something like: If you need more control of read parameters and the reading process, the common idiom for reading is something like:
```java ```java
// Create input stream // Create input stream
ImageInputStream input = ImageIO.createImageInputStream(file); ImageInputStream input = ImageIO.createImageInputStream(file);
try {
// Get the reader
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) {
throw new IllegalArgumentException("No reader for: " + file);
}
ImageReader reader = readers.next();
try { try {
// Get the reader reader.setInput(input);
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) { // Optionally, listen for read warnings, progress, etc.
throw new IllegalArgumentException("No reader for: " + file); reader.addIIOReadWarningListener(...);
} reader.addIIOReadProgressListener(...);
ImageReader reader = readers.next(); ImageReadParam param = reader.getDefaultReadParam();
try { // Optionally, control read settings like sub sampling, source region or destination etc.
reader.setInput(input); param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Optionally, listen for read warnings, progress, etc. // Finally read the image, using settings from param
reader.addIIOReadWarningListener(...); BufferedImage image = reader.read(0, param);
reader.addIIOReadProgressListener(...);
ImageReadParam param = reader.getDefaultReadParam(); // Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// Optionally, control read settings like sub sampling, source region or destination etc. // ...
param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Finally read the image, using settings from param
BufferedImage image = reader.read(0, param);
// Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// ...
}
finally {
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
} }
finally { finally {
// Close stream in finally block to avoid resource leaks // Dispose reader in finally block to avoid memory leaks
input.close(); reader.dispose();
} }
}
finally {
// Close stream in finally block to avoid resource leaks
input.close();
}
``` ```
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
@ -324,62 +134,78 @@ It's also possible to read multiple images from the same file in a loop, using `
If you need more control of write parameters and the writing process, the common idiom for writing is something like: If you need more control of write parameters and the writing process, the common idiom for writing is something like:
```java ```java
// Get the writer // Get the writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format); Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
if (!writers.hasNext()) { if (!writers.hasNext()) {
throw new IllegalArgumentException("No writer for: " + format); throw new IllegalArgumentException("No writer for: " + format);
} }
ImageWriter writer = writers.next(); ImageWriter writer = writers.next();
try {
// Create output stream
ImageOutputStream output = ImageIO.createImageOutputStream(file);
try { try {
// Create output stream writer.setOutput(output);
ImageOutputStream output = ImageIO.createImageOutputStream(file);
try { // Optionally, listen to progress, warnings, etc.
writer.setOutput(output);
// Optionally, listen to progress, warnings, etc. ImageWriteParam param = writer.getDefaultWriteParam();
ImageWriteParam param = writer.getDefaultWriteParam(); // Optionally, control format specific settings of param (requires casting), or
// control generic write settings like sub sampling, source region, output type etc.
// Optionally, control format specific settings of param (requires casting), or // Optionally, provide thumbnails and image/stream metadata
// control generic write settings like sub sampling, source region, output type etc. writer.write(..., new IIOImage(..., image, ...), param);
// Optionally, provide thumbnails and image/stream metadata
writer.write(..., new IIOImage(..., image, ...), param);
}
finally {
// Close stream in finally block to avoid resource leaks
output.close();
}
} }
finally { finally {
// Dispose writer in finally block to avoid memory leaks // Close stream in finally block to avoid resource leaks
writer.dispose(); output.close();
} }
}
finally {
// Dispose writer in finally block to avoid memory leaks
writer.dispose();
}
``` ```
For more advanced usage, and information on how to use the ImageIO API, I suggest you read the For more advanced usage, and information on how to use the ImageIO API, I suggest you read the
[Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html) [Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
from Oracle. from Oracle.
#### Adobe Clipping Path support
```java
import com.twelvemonkeys.imageio.path.Paths;
...
try (ImageInputStream stream = ImageIO.createImageInputStream(new File("image_with_path.jpg")) {
BufferedImage image = Paths.readClipped(stream);
// Do something with the clipped image...
}
```
See [Adobe Clipping Path support on the Wiki](https://github.com/haraldk/TwelveMonkeys/wiki/Photoshop-Clipping-Path-support) for more details and example code.
#### Using the ResampleOp #### Using the ResampleOp
The library comes with a resampling (image resizing) operation, that contains many different algorithms The library comes with a resampling (image resizing) operation, that contains many different algorithms
to provide excellent results at reasonable speed. to provide excellent results at reasonable speed.
```java ```java
import com.twelvemonkeys.image.ResampleOp; import com.twelvemonkeys.image.ResampleOp;
... ...
BufferedImage input = ...; // Image to resample BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
BufferedImage output = resampler.filter(input, null); BufferedImage output = resampler.filter(input, null);
``` ```
#### Using the DiffusionDither #### Using the DiffusionDither
@ -388,14 +214,14 @@ The library comes with a dithering operation, that can be used to convert `Buffe
Floyd-Steinberg error-diffusion dither. Floyd-Steinberg error-diffusion dither.
```java ```java
import com.twelvemonkeys.image.DiffusionDither; import com.twelvemonkeys.image.DiffusionDither;
... ...
BufferedImage input = ...; // Image to dither BufferedImage input = ...; // Image to dither
BufferedImageOp ditherer = new DiffusionDither(); BufferedImageOp ditherer = new DiffusionDither();
BufferedImage output = ditherer.filter(input, null); BufferedImage output = ditherer.filter(input, null);
``` ```
## Building ## Building
@ -433,10 +259,10 @@ The ImageIO registry and service lookup mechanism will make sure the plugins are
To verify that the JPEG plugin is installed and used at run-time, you could use the following code: To verify that the JPEG plugin is installed and used at run-time, you could use the following code:
```java ```java
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG"); Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
while (readers.hasNext()) { while (readers.hasNext()) {
System.out.println("reader: " + readers.next()); System.out.println("reader: " + readers.next());
} }
``` ```
The first line should print: The first line should print:
@ -448,30 +274,30 @@ The first line should print:
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM: To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
```xml ```xml
...
<dependencies>
... ...
<dependencies> <dependency>
... <groupId>com.twelvemonkeys.imageio</groupId>
<dependency> <artifactId>imageio-jpeg</artifactId>
<groupId>com.twelvemonkeys.imageio</groupId> <version>3.6</version>
<artifactId>imageio-jpeg</artifactId> </dependency>
<version>3.6</version> <dependency>
</dependency> <groupId>com.twelvemonkeys.imageio</groupId>
<dependency> <artifactId>imageio-tiff</artifactId>
<groupId>com.twelvemonkeys.imageio</groupId> <version>3.6</version>
<artifactId>imageio-tiff</artifactId> </dependency>
<version>3.6</version>
</dependency>
<!-- <!--
Optional dependency. Needed only if you deploy `ImageIO` plugins as part of a web app. Optional dependency. Needed only if you deploy `ImageIO` plugins as part of a web app.
Make sure you add the `IIOProviderContextListener` to your `web.xml`, see above. Make sure you add the `IIOProviderContextListener` to your `web.xml`, see above.
--> -->
<dependency> <dependency>
<groupId>com.twelvemonkeys.servlet</groupId> <groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<version>3.6</version> <version>3.6</version>
</dependency> </dependency>
</dependencies> </dependencies>
``` ```
#### Manual dependency example #### Manual dependency example
@ -504,18 +330,18 @@ it is *strongly recommended* to use the `IIOProviderContextListener` that implem
dynamic loading and unloading of ImageIO plugins for web applications. dynamic loading and unloading of ImageIO plugins for web applications.
```xml ```xml
<web-app ...> <web-app ...>
... ...
<listener> <listener>
<display-name>ImageIO service provider loader/unloader</display-name> <display-name>ImageIO service provider loader/unloader</display-name>
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class> <listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
</listener> </listener>
... ...
</web-app> </web-app>
``` ```
Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly. Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly.
@ -527,10 +353,10 @@ Another safe option, is to place the JAR files in the application server's share
#### Including the plugins in a "fat" JAR #### Including the plugins in a "fat" JAR
The recommended way to use the plugins, is just to include the JARs as-is in your project. The recommended way to use the plugins, is just to include the JARs as-is in your project, through a Maven dependency or similar.
Re-packaging is not necessary to use the library, and not recommended. Re-packaging is not necessary to use the library, and not recommended.
If you like to create a "fat" However, if you like to create a "fat"
JAR, or otherwise like to re-package the JARs for some reason, it's important to remember that automatic discovery of JAR, or otherwise like to re-package the JARs for some reason, it's important to remember that automatic discovery of
the plugins by ImageIO depends on the the plugins by ImageIO depends on the
[Service Provider Interface (SPI)](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) mechanism. [Service Provider Interface (SPI)](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) mechanism.
@ -622,7 +448,7 @@ Servlet support
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause): The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
Copyright (c) 2008-2018, Harald Kuhr Copyright (c) 2008-2020, Harald Kuhr
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -35,7 +35,7 @@ public class EXIFUtilities {
* @throws IOException if an error occurs during reading. * @throws IOException if an error occurs during reading.
*/ */
public static IIOImage readWithOrientation(final URL input) throws IOException { public static IIOImage readWithOrientation(final URL input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) { try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream); return readWithOrientation(stream);
} }
} }
@ -48,7 +48,7 @@ public class EXIFUtilities {
* @throws IOException if an error occurs during reading. * @throws IOException if an error occurs during reading.
*/ */
public static IIOImage readWithOrientation(final InputStream input) throws IOException { public static IIOImage readWithOrientation(final InputStream input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) { try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream); return readWithOrientation(stream);
} }
} }
@ -61,7 +61,7 @@ public class EXIFUtilities {
* @throws IOException if an error occurs during reading. * @throws IOException if an error occurs during reading.
*/ */
public static IIOImage readWithOrientation(final File input) throws IOException { public static IIOImage readWithOrientation(final File input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) { try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream); return readWithOrientation(stream);
} }
} }

View File

@ -30,22 +30,6 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.*;
@ -56,6 +40,27 @@ import java.nio.ByteOrder;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.xml.XMLSerializer;
/** /**
* ImageReader for Microsoft Windows Bitmap (BMP) format. * ImageReader for Microsoft Windows Bitmap (BMP) format.
* *
@ -125,6 +130,10 @@ public final class BMPImageReader extends ImageReaderBase {
// Read DIB header // Read DIB header
header = DIBHeader.read(imageInput); header = DIBHeader.read(imageInput);
if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) {
throw new IIOException("Invalid pixel offset: " + pixelOffset);
}
} }
} }
@ -207,50 +216,55 @@ public final class BMPImageReader extends ImageReaderBase {
throw new IIOException("Multiple planes not supported"); throw new IIOException("Multiple planes not supported");
} }
switch (header.getBitCount()) { try {
case 1: switch (header.getBitCount()) {
case 2: case 1:
case 4: case 2:
case 8: case 4:
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap()); case 8:
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap());
case 16: case 16:
if (header.hasMasks()) { if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked( return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB), ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3], header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_USHORT, false DataBuffer.TYPE_USHORT, false
); );
} }
// Default if no mask is 555 // Default if no mask is 555
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
case 24: case 24:
if (header.getCompression() != DIB.COMPRESSION_RGB) { if (header.getCompression() != DIB.COMPRESSION_RGB) {
throw new IIOException("Unsupported compression for RGB: " + header.getCompression()); throw new IIOException("Unsupported compression for RGB: " + header.getCompression());
} }
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
case 32: case 32:
if (header.hasMasks()) { if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked( return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB), ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3], header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_INT, false DataBuffer.TYPE_INT, false
); );
} }
// Default if no mask // Default if no mask
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB); return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
case 0: case 0:
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) { if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
return initReaderDelegate(header.getCompression()).getRawImageType(0); return initReaderDelegate(header.getCompression()).getRawImageType(0);
} }
default: default:
throw new IIOException("Unsupported bit count: " + header.getBitCount()); throw new IIOException("Unsupported bit count: " + header.getBitCount());
}
}
catch (IllegalArgumentException e) {
throw new IIOException(e.getMessage(), e);
} }
} }

View File

@ -29,9 +29,10 @@
*/ */
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.lang.Validate; import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
/** /**
* Describes a bitmap structure. * Describes a bitmap structure.
@ -47,14 +48,11 @@ abstract class BitmapDescriptor {
protected BitmapMask mask; protected BitmapMask mask;
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) { public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
Validate.notNull(pEntry, "entry"); entry = notNull(pEntry, "entry");;
Validate.notNull(pHeader, "header"); header = notNull(pHeader, "header");
entry = pEntry;
header = pHeader;
} }
abstract public BufferedImage getImage(); abstract public BufferedImage getImage() throws IOException;
public final int getWidth() { public final int getWidth() {
return entry.getWidth(); return entry.getWidth();

View File

@ -163,6 +163,7 @@ class BitmapIndexed extends BitmapDescriptor {
return transparent; return transparent;
} }
@Override
public BufferedImage getImage() { public BufferedImage getImage() {
if (image == null) { if (image == null) {
image = createImageIndexed(); image = createImageIndexed();

View File

@ -46,6 +46,7 @@ class BitmapRGB extends BitmapDescriptor {
super(pEntry, pHeader); super(pEntry, pHeader);
} }
@Override
public BufferedImage getImage() { public BufferedImage getImage() {
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha) // Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
// might still have bitmask, but we don't read or use it. // might still have bitmask, but we don't read or use it.

View File

@ -31,6 +31,9 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.IIOException;
/** /**
* Represents bitmap structures we can't read. * Represents bitmap structures we can't read.
@ -42,13 +45,14 @@ import java.awt.image.BufferedImage;
class BitmapUnsupported extends BitmapDescriptor { class BitmapUnsupported extends BitmapDescriptor {
private String message; private String message;
public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) { public BitmapUnsupported(final DirectoryEntry pEntry, DIBHeader header, final String pMessage) {
super(pEntry, null); super(pEntry, header);
message = pMessage; message = pMessage;
} }
public BufferedImage getImage() { @Override
throw new IllegalStateException(message); public BufferedImage getImage() throws IOException {
throw new IIOException(message);
} }
} }

View File

@ -30,11 +30,12 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException;
import java.io.DataInput; import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import javax.imageio.IIOException;
/** /**
* Represents the DIB (Device Independent Bitmap) Information header structure. * Represents the DIB (Device Independent Bitmap) Information header structure.
* *
@ -213,7 +214,7 @@ abstract class DIBHeader {
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)! // NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
width = pStream.readUnsignedShort(); width = pStream.readUnsignedShort();
height = pStream.readUnsignedShort(); height = pStream.readShort();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
@ -240,6 +241,7 @@ abstract class DIBHeader {
* @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a> * @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a>
*/ */
static final class BitmapCoreHeaderV2 extends DIBHeader { static final class BitmapCoreHeaderV2 extends DIBHeader {
@SuppressWarnings("unused")
protected void read(final int pSize, final DataInput pStream) throws IOException { protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) { if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE)); throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));

View File

@ -30,16 +30,6 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
@ -48,8 +38,26 @@ import java.awt.image.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.*; import java.util.Map;
import java.util.WeakHashMap;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
/** /**
* ImageReader for Microsoft Windows ICO (icon) format. * ImageReader for Microsoft Windows ICO (icon) format.
@ -287,7 +295,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8 // TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
if (header.getCompression() != DIB.COMPRESSION_RGB) { if (header.getCompression() != DIB.COMPRESSION_RGB) {
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression())); descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported compression: %d", header.getCompression()));
} }
else { else {
int bitCount = header.getBitCount(); int bitCount = header.getBitCount();
@ -315,7 +323,7 @@ abstract class DIBImageReader extends ImageReaderBase {
break; break;
default: default:
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount)); descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported bit count %d", bitCount));
} }
} }
@ -355,7 +363,7 @@ abstract class DIBImageReader extends ImageReaderBase {
} }
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException { private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 3); int width = adjustToPadding((pBitmap.getWidth() + 7) >> 3);
byte[] row = new byte[width]; byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
@ -389,7 +397,7 @@ abstract class DIBImageReader extends ImageReaderBase {
} }
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException { private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 1); int width = adjustToPadding((pBitmap.getWidth() + 1) >> 1);
byte[] row = new byte[width]; byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
@ -457,13 +465,12 @@ abstract class DIBImageReader extends ImageReaderBase {
} }
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException { private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
// TODO: No idea if this actually works..
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()]; short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts // TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555 // Will create TYPE_USHORT_555
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F); DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferShort(pixels, pixels.length); DataBuffer buffer = new DataBufferUShort(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster( WritableRaster raster = Raster.createPackedRaster(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
); );

View File

@ -41,8 +41,8 @@ import java.util.Arrays;
* @version $Id: RLE4Decoder.java#1 $ * @version $Id: RLE4Decoder.java#1 $
*/ */
final class RLE4Decoder extends AbstractRLEDecoder { final class RLE4Decoder extends AbstractRLEDecoder {
final static int BIT_MASKS[] = {0xf0, 0x0f}; final static int[] BIT_MASKS = {0xf0, 0x0f};
final static int BIT_SHIFTS[] = {4, 0}; final static int[] BIT_SHIFTS = {4, 0};
public RLE4Decoder(final int width) { public RLE4Decoder(final int width) {
super(width, 4); super(width, 4);
@ -94,7 +94,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0; boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
int packed = 0; int packed = 0;
for (int i = 0; i < byte2; i++) { for (int i = 0; i < byte2 && srcX / 2 < row.length; i++) {
if (i % 2 == 0) { if (i % 2 == 0) {
packed = checkEOF(stream.read()); packed = checkEOF(stream.read());
} }
@ -111,7 +111,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
else { else {
// Encoded mode // Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says // Replicate the two samples in byte2 as many times as byte1 says
for (int i = 0; i < byte1; i++) { for (int i = 0; i < byte1 && srcX / 2 < row.length; i++) {
row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]); row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]);
srcX++; srcX++;
} }

View File

@ -94,7 +94,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// an additional padding byte is in the stream and must be skipped // an additional padding byte is in the stream and must be skipped
boolean paddingByte = (byte2 % 2) != 0; boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) { while (byte2-- > 0 && srcX < row.length) {
row[srcX++] = (byte) checkEOF(stream.read()); row[srcX++] = (byte) checkEOF(stream.read());
} }
@ -107,7 +107,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// Encoded mode // Encoded mode
// Replicate byte2 as many times as byte1 says // Replicate byte2 as many times as byte1 says
byte value = (byte) byte2; byte value = (byte) byte2;
while (byte1-- > 0) { while (byte1-- > 0 && srcX < row.length) {
row[srcX++] = value; row[srcX++] = value;
} }
} }

View File

@ -30,20 +30,14 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import static org.junit.Assert.*;
import com.twelvemonkeys.xml.XMLSerializer; import static org.junit.Assume.assumeNoException;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
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.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -55,11 +49,24 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static org.junit.Assert.*; import javax.imageio.IIOException;
import static org.junit.Assume.assumeNoException; import javax.imageio.ImageIO;
import static org.mockito.Matchers.anyInt; import javax.imageio.ImageReadParam;
import static org.mockito.Matchers.eq; import javax.imageio.ImageReader;
import static org.mockito.Mockito.*; 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 org.junit.Ignore;
import org.junit.Test;
import org.mockito.InOrder;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.xml.XMLSerializer;
/** /**
* BMPImageReaderTest * BMPImageReaderTest
@ -175,7 +182,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
ImageTypeSpecifier rawType = reader.getRawImageType(0); ImageTypeSpecifier rawType = reader.getRawImageType(0);
// As the JPEGImageReader we delegate to returns null for YCbCr, we'll have to do the same // As the JPEGImageReader we delegate to may return null for YCbCr, we'll have to do the same
if (rawType == null && data.getInput().toString().contains("jpeg")) { if (rawType == null && data.getInput().toString().contains("jpeg")) {
continue; continue;
} }

View File

@ -30,20 +30,16 @@
package com.twelvemonkeys.imageio; package com.twelvemonkeys.imageio;
import com.twelvemonkeys.image.BufferedImageIcon;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable; import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*; import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel; import java.awt.image.IndexColorModel;
import java.io.File; import java.io.File;
@ -52,6 +48,20 @@ import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import com.twelvemonkeys.image.BufferedImageIcon;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.util.IIOUtil;
/** /**
* Abstract base class for image readers. * Abstract base class for image readers.
* *
@ -314,12 +324,12 @@ public abstract class ImageReaderBase extends ImageReader {
long dimension = (long) destWidth * destHeight; long dimension = (long) destWidth * destHeight;
if (dimension > Integer.MAX_VALUE) { if (dimension > Integer.MAX_VALUE) {
throw new IllegalArgumentException(String.format("destination width * height > Integer.MAX_VALUE: %d", dimension)); throw new IIOException(String.format("destination width * height > Integer.MAX_VALUE: %d", dimension));
} }
long size = dimension * imageType.getSampleModel().getNumDataElements(); long size = dimension * imageType.getSampleModel().getNumDataElements();
if (size > Integer.MAX_VALUE) { if (size > Integer.MAX_VALUE) {
throw new IllegalArgumentException(String.format("destination width * height * samplesPerPixel > Integer.MAX_VALUE: %d", size)); throw new IIOException(String.format("destination width * height * samplesPerPixel > Integer.MAX_VALUE: %d", size));
} }
// Create a new image based on the type specifier // Create a new image based on the type specifier

View File

@ -35,7 +35,6 @@ import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
/** /**
@ -56,7 +55,7 @@ public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class); super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
} }
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) throws IOException { public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
if (pInput instanceof byte[]) { if (pInput instanceof byte[]) {
return new ByteArrayImageInputStream((byte[]) pInput); return new ByteArrayImageInputStream((byte[]) pInput);
} }

View File

@ -114,6 +114,11 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi {
} }
} }
@Override
public boolean canUseCacheFile() {
return true;
}
public String getDescription(final Locale pLocale) { public String getDescription(final Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a URL"; return "Service provider that instantiates an ImageInputStream from a URL";
} }

View File

@ -30,11 +30,11 @@
package com.twelvemonkeys.imageio; package com.twelvemonkeys.imageio;
import org.junit.Test; import static java.util.Collections.singleton;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -44,8 +44,11 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static java.util.Collections.singleton; import javax.imageio.IIOException;
import static org.junit.Assert.*; import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import org.junit.Test;
/** /**
* ImageReaderBaseTest * ImageReaderBaseTest
@ -212,19 +215,19 @@ public class ImageReaderBaseTest {
assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType()); assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType());
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IIOException.class)
public void testGetDestinationParamDestinationExceedsIntegerMax() throws IIOException { public void testGetDestinationParamDestinationExceedsIntegerMax() throws IIOException {
ImageReadParam param = new ImageReadParam(); ImageReadParam param = new ImageReadParam();
param.setSourceRegion(new Rectangle(3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE)); // 6 442 057 734 pixels param.setSourceRegion(new Rectangle(3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE)); // 6 442 057 734 pixels
ImageReaderBase.getDestination(param, TYPES.iterator(), 6 * Short.MAX_VALUE, 4 * Short.MAX_VALUE); // 25 768 230 936 pixels ImageReaderBase.getDestination(param, TYPES.iterator(), 6 * Short.MAX_VALUE, 4 * Short.MAX_VALUE); // 25 768 230 936 pixels
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IIOException.class)
public void testGetDestinationDimensionExceedsIntegerMax() throws IIOException { public void testGetDestinationDimensionExceedsIntegerMax() throws IIOException {
ImageReaderBase.getDestination(null, TYPES.iterator(), 3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE); // 6 442 057 734 pixels ImageReaderBase.getDestination(null, TYPES.iterator(), 3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE); // 6 442 057 734 pixels
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IIOException.class)
public void testGetDestinationStorageExceedsIntegerMax() throws IIOException { public void testGetDestinationStorageExceedsIntegerMax() throws IIOException {
Set<ImageTypeSpecifier> byteTypes = singleton(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); Set<ImageTypeSpecifier> byteTypes = singleton(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
ImageReaderBase.getDestination(null, byteTypes.iterator(), Short.MAX_VALUE, Short.MAX_VALUE); // 1 073 676 289 pixels ImageReaderBase.getDestination(null, byteTypes.iterator(), Short.MAX_VALUE, Short.MAX_VALUE); // 1 073 676 289 pixels

View File

@ -45,7 +45,7 @@ import static org.junit.Assert.*;
*/ */
public class ProviderInfoTest { public class ProviderInfoTest {
@Test @Test
public void testCreateNorma() { public void testCreateNormal() {
new ProviderInfo(Package.getPackage("java.util")); new ProviderInfo(Package.getPackage("java.util"));
} }

View File

@ -0,0 +1,16 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
public class ByteArrayImageInputStreamSpiTest extends ImageInputStreamSpiTest<byte[]> {
@Override
protected ImageInputStreamSpi createProvider() {
return new ByteArrayImageInputStreamSpi();
}
@Override
protected byte[] createInput() {
return new byte[0];
}
}

View File

@ -0,0 +1,84 @@
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.spi.ImageInputStreamSpi;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.util.Locale;
import static org.junit.Assert.*;
abstract class ImageInputStreamSpiTest<T> {
private final ImageInputStreamSpi provider = createProvider();
@SuppressWarnings("unchecked")
private final Class<T> inputClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
protected abstract ImageInputStreamSpi createProvider();
protected abstract T createInput();
@Test
public void testInputClass() {
assertEquals(inputClass, provider.getInputClass());
}
@Test
public void testVendorName() {
assertNotNull(provider.getVendorName());
assertEquals("TwelveMonkeys", provider.getVendorName());
}
@Test
public void testVersion() {
assertNotNull(provider.getVersion());
}
@Test
public void testDescription() {
assertNotNull(provider.getDescription(null));
assertNotNull(provider.getDescription(Locale.ENGLISH));
}
@Test(expected = IllegalArgumentException.class)
public void createNull() throws IOException {
provider.createInputStreamInstance(null);
}
@Test(expected = IllegalArgumentException.class)
public void createNullCached() throws IOException {
provider.createInputStreamInstance(null, true, ImageIO.getCacheDirectory());
}
@Test
public void createCachedNullCache() throws IOException {
try {
provider.createInputStreamInstance(createInput(), true, null);
}
catch (IllegalArgumentException expected) {
// All good
assertFalse(provider.needsCacheFile());
}
}
@Test
public void create() throws IOException {
assertNotNull(provider.createInputStreamInstance(createInput()));
}
@Test
public void createCached() throws IOException {
if (provider.canUseCacheFile()) {
assertNotNull(provider.createInputStreamInstance(createInput(), true, ImageIO.getCacheDirectory()));
}
}
@Test
public void createNonCached() throws IOException {
if (!provider.needsCacheFile()) {
assertNotNull(provider.createInputStreamInstance(createInput(), false, ImageIO.getCacheDirectory()));
}
}
}

View File

@ -0,0 +1,23 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class StreamProviderInfoTest {
private final ProviderInfo providerInfo = new StreamProviderInfo();
@Test
public void testVendorName() {
assertNotNull(providerInfo.getVendorName());
assertEquals("TwelveMonkeys", providerInfo.getVendorName());
}
@Test
public void testVersion() {
assertNotNull(providerInfo.getVersion());
}
}

View File

@ -0,0 +1,16 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
import java.net.URL;
public class URLImageInputStreamSpiTest extends ImageInputStreamSpiTest<URL> {
@Override
protected ImageInputStreamSpi createProvider() {
return new URLImageInputStreamSpi();
}
@Override
protected URL createInput() {
return getClass().getResource("/empty-stream.txt");
}
}

View File

@ -30,6 +30,37 @@
package com.twelvemonkeys.imageio.plugins.jpeg; package com.twelvemonkeys.imageio.plugins.jpeg;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter; import com.twelvemonkeys.imageio.color.YCbCrConverter;
@ -49,24 +80,6 @@ import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.xml.XMLSerializer; import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
import java.util.List;
import java.util.*;
/** /**
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader}, * A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
* that adds support and properly handles cases where the JRE version throws exceptions. * that adds support and properly handles cases where the JRE version throws exceptions.
@ -896,16 +909,16 @@ public final class JPEGImageReader extends ImageReaderBase {
if (!exifSegments.isEmpty()) { if (!exifSegments.isEmpty()) {
Application exif = exifSegments.get(0); Application exif = exifSegments.get(0);
InputStream data = exif.data(); int offset = exif.identifier.length() + 2; // Incl. pad
if (data.read() == -1) { // Read pad if (exif.data.length <= offset) {
processWarningOccurred("Exif chunk has no data."); processWarningOccurred("Exif chunk has no data.");
} }
else { else {
ImageInputStream stream = new MemoryCacheImageInputStream(data); // TODO: Consider returning ByteArrayImageInputStream from Segment.data()
return (CompoundDirectory) new TIFFReader().read(stream); try (ImageInputStream stream = new ByteArrayImageInputStream(exif.data, offset, exif.data.length - offset)) {
return (CompoundDirectory) new TIFFReader().read(stream);
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the TIFFReader... }
} }
} }

View File

@ -30,12 +30,12 @@
package com.twelvemonkeys.imageio.metadata; package com.twelvemonkeys.imageio.metadata;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.CollectionUtil;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Arrays; import java.util.Arrays;
import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.util.CollectionUtil;
/** /**
* AbstractEntry * AbstractEntry
* *
@ -84,17 +84,21 @@ public abstract class AbstractEntry implements Entry {
} }
public String getValueAsString() { public String getValueAsString() {
if (valueCount() > 1) { int count = valueCount();
if (valueCount() < 16) {
if (count == 0 && value != null && value.getClass().isArray() && Array.getLength(value) == 0) {
return "";
}
if (count > 1) {
if (count < 16) {
return arrayToString(value); return arrayToString(value);
} }
else { else {
String first = arrayToString(CollectionUtil.subArray(value, 0, 4)); String first = arrayToString(CollectionUtil.subArray(value, 0, 4));
String last = arrayToString(CollectionUtil.subArray(value, valueCount() - 4, 4)); String last = arrayToString(CollectionUtil.subArray(value, count - 4, 4));
return String.format("%s ... %s (%d)", first.substring(0, first.length() - 1), last.substring(1), valueCount()); return String.format("%s ... %s (%d)", first.substring(0, first.length() - 1), last.substring(1), count);
} }
} }
if (value != null && value.getClass().isArray() && Array.getLength(value) == 1) { if (value != null && value.getClass().isArray() && Array.getLength(value) == 1) {
return String.valueOf(Array.get(value, 0)); return String.valueOf(Array.get(value, 0));
} }

View File

@ -41,7 +41,6 @@ package com.twelvemonkeys.imageio.metadata.exif;
* *
* @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.Rational instead. * @deprecated Use com.twelvemonkeys.imageio.metadata.tiff.Rational instead.
*/ */
@SuppressWarnings("deprecation")
public final class Rational extends Number implements Comparable<Rational> { public final class Rational extends Number implements Comparable<Rational> {
private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate; private final com.twelvemonkeys.imageio.metadata.tiff.Rational delegate;

View File

@ -143,7 +143,7 @@ public final class Rational extends Number implements Comparable<Rational> {
double thisVal = doubleValue(); double thisVal = doubleValue();
double otherVal = pOther.doubleValue(); double otherVal = pOther.doubleValue();
return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1; return Double.compare(thisVal, otherVal);
} }
/// Object overrides /// Object overrides

View File

@ -30,15 +30,8 @@
package com.twelvemonkeys.imageio.metadata.tiff; package com.twelvemonkeys.imageio.metadata.tiff;
import com.twelvemonkeys.imageio.metadata.Directory; import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -46,7 +39,15 @@ import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength; import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.Validate;
/** /**
* TIFFReader * TIFFReader
@ -336,7 +337,8 @@ public final class TIFFReader extends MetadataReader {
else { else {
long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= offsetSize long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= offsetSize
if (length > 0 && length < valueOffset + valueLength) { // Note: This a precaution
if (count >= Integer.MAX_VALUE || length > 0 && length < valueOffset + valueLength) {
value = new EOFException(String.format("TIFF value offset or size too large: %d/%d bytes (length: %d bytes)", valueOffset, valueLength, length)); value = new EOFException(String.format("TIFF value offset or size too large: %d/%d bytes (length: %d bytes)", valueOffset, valueLength, length));
} }
else { else {
@ -367,7 +369,7 @@ public final class TIFFReader extends MetadataReader {
long pos = pInput.getStreamPosition(); long pos = pInput.getStreamPosition();
try { try {
pInput.seek(pOffset); pInput.seek(pOffset);
return readValue(pInput, pType, pCount); return readValue(pInput, pType, pCount, longOffsets);
} }
catch (EOFException e) { catch (EOFException e) {
// TODO: Add warning listener API and report problem to client code // TODO: Add warning listener API and report problem to client code
@ -379,10 +381,10 @@ public final class TIFFReader extends MetadataReader {
} }
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
return readValue(pInput, pType, pCount); return readValue(pInput, pType, pCount, longOffsets);
} }
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount, boolean bigTIFF) throws IOException {
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code? // TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
// TODO: New strategy: Leave data as is, instead perform the widening in TIFFEntry.getValue. // TODO: New strategy: Leave data as is, instead perform the widening in TIFFEntry.getValue.
// TODO: Add getValueByte/getValueUnsignedByte/getValueShort/getValueUnsignedShort/getValueInt/etc... in API. // TODO: Add getValueByte/getValueUnsignedByte/getValueShort/getValueUnsignedShort/getValueInt/etc... in API.
@ -513,24 +515,24 @@ public final class TIFFReader extends MetadataReader {
case TIFF.TYPE_LONG8: case TIFF.TYPE_LONG8:
case TIFF.TYPE_SLONG8: case TIFF.TYPE_SLONG8:
case TIFF.TYPE_IFD8: case TIFF.TYPE_IFD8:
// TODO: Assert BigTiff (version == 43) if (bigTIFF) {
if (pCount == 1) {
long val = pInput.readLong();
if (pType != TIFF.TYPE_SLONG8 && val < 0) {
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
}
if (pCount == 1) { return val;
long val = pInput.readLong();
if (pType != TIFF.TYPE_SLONG8 && val < 0) {
throw new IIOException(String.format("Value > %s", Long.MAX_VALUE));
} }
return val; long[] longs = new long[pCount];
} for (int i = 0; i < pCount; i++) {
longs[i] = pInput.readLong();
}
long[] longs = new long[pCount]; return longs;
for (int i = 0; i < pCount; i++) {
longs[i] = pInput.readLong();
} }
return longs;
default: default:
// Spec says skip unknown values // Spec says skip unknown values
return new Unknown(pType, pCount, pos); return new Unknown(pType, pCount, pos);

View File

@ -30,6 +30,20 @@
package com.twelvemonkeys.imageio.metadata.tiff; package com.twelvemonkeys.imageio.metadata.tiff;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import org.junit.Test;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.Entry;
@ -38,18 +52,6 @@ import com.twelvemonkeys.imageio.metadata.exif.EXIF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
/** /**
* TIFFReaderTest * TIFFReaderTest
* *
@ -348,6 +350,21 @@ public class TIFFReaderTest extends MetadataReaderAbstractTest {
} }
} }
@Test
public void testReadWithoutOOME() throws IOException {
// This EXIF segment from a JPEG contains an Interop IFD, containing a weird value that could be interpreted
// as a huge SLONG8 field (valid for BigTIFF only).
// OutOfMemoryError would only happen if length of stream is not known (ie. reading from underlying stream).
try (InputStream stream = getResource("/exif/exif-bad-interop-oome.bin").openStream()) {
CompoundDirectory directory = (CompoundDirectory) createReader().read(new MemoryCacheImageInputStream(stream));
assertEquals(2, directory.directoryCount());
assertEquals(11, directory.getDirectory(0).size());
assertEquals(6, directory.getDirectory(1).size());
assertEquals("Picasa", directory.getDirectory(0).getEntryById(TIFF.TAG_SOFTWARE).getValue());
assertEquals("2020:11:17 16:05:37", directory.getDirectory(0).getEntryById(TIFF.TAG_DATE_TIME).getValueAsString());
}
}
@Test(timeout = 200) @Test(timeout = 200)
public void testReadCyclicExifWithoutLoopOrOOME() throws IOException { public void testReadCyclicExifWithoutLoopOrOOME() throws IOException {
// This EXIF segment has an interesting bug... // This EXIF segment has an interesting bug...