mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-18 00:00:03 -04:00
Compare commits
262 Commits
3.1-bugfix
...
3.3-bugfix
| Author | SHA1 | Date | |
|---|---|---|---|
| 66eae4ae00 | |||
| c8745f4df7 | |||
| e8d427ae5f | |||
| c181f74fb0 | |||
| c0f66b5584 | |||
| 69dcfe9713 | |||
| 5aac07f221 | |||
| aa3e2cc019 | |||
| b0108fe39b | |||
| 3f6a27b75e | |||
| cb2cf0721c | |||
| f49b57b3de | |||
| 22593e37f7 | |||
| ad86bcda7e | |||
| 41e6b041c9 | |||
| 4e5127404f | |||
| 2cede0e8cc | |||
| e189b5e14f | |||
| f0316f7ec5 | |||
| 654e111605 | |||
| 6d043d0208 | |||
| e4b91ab34c | |||
| 34eb084d24 | |||
| 3f6bc722bc | |||
| 5ab3fdd1d3 | |||
| 610f9bec9f | |||
| 17eeda210e | |||
| b116b4b5a7 | |||
| afd8b28617 | |||
| d4afbee0f5 | |||
| 288ad54c42 | |||
| 5f12c88609 | |||
| 51afe2d2e8 | |||
| 7ac1589186 | |||
| 7d35400595 | |||
| 673f3e5b53 | |||
| 15ce9d6b64 | |||
| b1ac99ba1a | |||
| ad269053ed | |||
| 13bea23550 | |||
| c7208c2c97 | |||
| 44401d9a0d | |||
| c18893184b | |||
| 04a39158e5 | |||
| 6673bb3536 | |||
| 7efd5ca0d8 | |||
| fd285ca5ee | |||
| a29960e8ee | |||
| 00c1285fe0 | |||
| 478ed62cd1 | |||
| 0db676f1be | |||
| 054499b78a | |||
| b0eb668ed4 | |||
| 1e3c8b26f6 | |||
| 458ef92af5 | |||
| ea5b9c5606 | |||
| 788b11e4fa | |||
| 775cede14d | |||
| f4b61820ac | |||
| 9a6096664e | |||
| c2aa7e3150 | |||
| 2c4c6d5a48 | |||
| a4a314a0f9 | |||
| b368da2154 | |||
| e9388e55ec | |||
| 8dd84930be | |||
| 53487e916b | |||
| 621313e905 | |||
| 7773e7ba70 | |||
| a6cd991722 | |||
| 3d68b61f72 | |||
| 454ee32791 | |||
| c29f895337 | |||
| 77bbc066e3 | |||
| b129117ee9 | |||
| 3d36159982 | |||
| ceb2c82e5c | |||
| d4c9d53ea5 | |||
| f8350623ea | |||
| 723632addf | |||
| 21ff12219a | |||
| eed4242aa4 | |||
| ff0389055b | |||
| 70bb5de4de | |||
| 69d939042a | |||
| 89dc36d3b7 | |||
| c373223ca3 | |||
| fa0341f302 | |||
| 099dbb0073 | |||
| e0434a1dcb | |||
| f382f4b5f9 | |||
| d9324ef634 | |||
| ed434dfb1d | |||
| f9229028ee | |||
| 047884e3d9 | |||
| 2cec177c6d | |||
| 592ed04cfa | |||
| e0e6e263ac | |||
| ac49e206a1 | |||
| 8387a9ad37 | |||
| 2955054a72 | |||
| ae548557bc | |||
| 2258b4def2 | |||
| d64eb40211 | |||
| 6796910091 | |||
| a1de9ff448 | |||
| eeeb22666c | |||
| 1449155987 | |||
| 156fe8bb25 | |||
| 98493b6c16 | |||
| 672aa1a048 | |||
| a709381825 | |||
| 87db620dac | |||
| 73f3290e22 | |||
| 8807ccd367 | |||
| 0997f5199a | |||
| e4f193400d | |||
| e145de01f3 | |||
| a9428a1ecf | |||
| fc80ac2ee9 | |||
| c6e8711639 | |||
| 342dbe7d83 | |||
| 10b8bef387 | |||
| d5a35fa818 | |||
| 5c63dd2168 | |||
| 9c011adfc2 | |||
| d9ba98fb8e | |||
| 8c2af2c3c5 | |||
| 04a4c6e3ae | |||
| 9e5204ddbb | |||
| 3b9d51dcc8 | |||
| 3214b3e595 | |||
| ad0d265f4f | |||
| 57402957ad | |||
| 6db06414af | |||
| d097742002 | |||
| 4e765fa61d | |||
| fd73ae09bd | |||
| f6847e39c8 | |||
| 9c38ff9bdb | |||
| be90082bd3 | |||
| 7d531e3836 | |||
| 5b6c57d934 | |||
| 3aa86bccf3 | |||
| 0feec02bc5 | |||
| 1ce2a06859 | |||
| 9c9d2c700e | |||
| d3cbeae9ba | |||
| 062413d2d8 | |||
| e62922eb95 | |||
| 302035443d | |||
| 8a38b2fde6 | |||
| 30a7283b35 | |||
| 4b363a0d64 | |||
| 1096f9bedd | |||
| 73a609a2cb | |||
| 8379f08d78 | |||
| c33cfea02c | |||
| b85d0f7d6b | |||
| b6e44c5bff | |||
| e5c0fead38 | |||
| 306a8ae166 | |||
| 6c702c447b | |||
| 237079bcc9 | |||
| 07617b49ce | |||
| c8621439c0 | |||
| 585b5faa62 | |||
| 39b92ab19f | |||
| 8e11d95fd6 | |||
| 867ca61755 | |||
| 2bdfa4fccb | |||
| d4e34d6109 | |||
| 25bef72ac0 | |||
| e957120480 | |||
| 35edfc519f | |||
| df735b9a45 | |||
| 99c5fea005 | |||
| 4b70b0cf1c | |||
| 22e490ca40 | |||
| 211b31e154 | |||
| f7d9a64e41 | |||
| 5c76899c04 | |||
| d792f0b7c4 | |||
| 077e40acf2 | |||
| 40b11710ea | |||
| d86af8bd82 | |||
| 60e3d6e5af | |||
| 6ef5a5a7ee | |||
| 61b07cb9cb | |||
| c913ef445b | |||
| 517fc770bd | |||
| 998851c9fb | |||
| 40555641af | |||
| e1b5e6bfc9 | |||
| bef0b762fa | |||
| 9bb67742b7 | |||
| fd4745f6a6 | |||
| 4eb7426596 | |||
| 1ee22e120d | |||
| d8d2764eb1 | |||
| 94f2b81d2a | |||
| d7c8df184e | |||
| d1cb941f06 | |||
| eef4c72daa | |||
| bad9aebdee | |||
| 6facef3142 | |||
| 1e6227bee5 | |||
| 62862d835a | |||
| 37e1723891 | |||
| 2b22b02523 | |||
| a2042e75bf | |||
| 1a43958aeb | |||
| f4cc310096 | |||
| 7e65164b87 | |||
| 51ca99d433 | |||
| c80f514131 | |||
| da8a013575 | |||
| 3bdc117aa3 | |||
| f645e46495 | |||
| 8052fbc5b8 | |||
| b2142cb449 | |||
| 821c20c09a | |||
| 194f34894f | |||
| f6d5a60600 | |||
| bbaa3e1186 | |||
| c97ebae303 | |||
| 1ed5b3899b | |||
| 203b330c99 | |||
| 9b71a0cba7 | |||
| 1a8948ece9 | |||
| 051a1dcb5b | |||
| 2db58dc73d | |||
| 835d8a11dd | |||
| f3b6d40f19 | |||
| 7f34f83768 | |||
| 7d0e02fce8 | |||
| 429923ec0d | |||
| 4dbe10846f | |||
| 475fdec33f | |||
| e14cbbd853 | |||
| a4ca20e2f6 | |||
| 0518cc813b | |||
| 90758ba882 | |||
| a03d1951a3 | |||
| 1402e78144 | |||
| 113480b9e7 | |||
| 0a7f250566 | |||
| 1e03850960 | |||
| cb276c8aff | |||
| c5929d9479 | |||
| f28ad2d396 | |||
| a8e5906569 | |||
| 5bd8c37c2d | |||
| e72700b032 | |||
| 2f06f2de6d | |||
| 8137165bac | |||
| 3628f3b392 | |||
| be959ce3f3 | |||
| d6e508662c | |||
| 0d8920aec4 | |||
| 03d8e06819 | |||
| 5131e02e6c |
@@ -0,0 +1,6 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling
|
||||
# - openjdk7
|
||||
@@ -1,6 +1,8 @@
|
||||
## Latest
|
||||
|
||||
TwelveMonkeys ImageIO 3.0.2 is released.
|
||||
Master branch build status: [](https://travis-ci.org/haraldk/TwelveMonkeys)
|
||||
|
||||
TwelveMonkeys ImageIO [3.2.1](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.2.1%22) is released (Dec. 11th, 2015).
|
||||
|
||||
## About
|
||||
|
||||
@@ -20,7 +22,7 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b
|
||||
|
||||
Mainstream format support
|
||||
|
||||
#### MS Windows/IBM OS/2 Device Independent Bitmap (BMP) *3.1*
|
||||
#### 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
|
||||
@@ -32,6 +34,8 @@ 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)
|
||||
@@ -65,7 +69,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. :-)
|
||||
|
||||
#### NetPBM Portable Any Map (PNM) *3.1*
|
||||
#### PNM - NetPBM Portable Any Map
|
||||
|
||||
* Read support for the following file types:
|
||||
* PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel
|
||||
@@ -78,7 +82,7 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
|
||||
* PAM in 'P7' (binary) format
|
||||
* Standard metadata support
|
||||
|
||||
#### Adobe Photoshop Document (PSD)
|
||||
#### PSD - Adobe Photoshop Document
|
||||
|
||||
* Read support for the following file types:
|
||||
* Monochrome, 1 channel, 1 bit
|
||||
@@ -89,16 +93,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 metadata support
|
||||
* Native and Standard metadata support
|
||||
|
||||
#### Aldus/Adobe Tagged Image File Format (TIFF)
|
||||
#### 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
|
||||
@@ -107,30 +111,42 @@ 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)
|
||||
* 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
|
||||
* Currently missing the CCITT fax encodings
|
||||
* 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
|
||||
|
||||
#### Commodore Amiga/Electronic Arts Interchange File Format (IFF)
|
||||
#### HDR - Radiance High Dynamic Range RGBE Format
|
||||
|
||||
* Legacy format, allows reading popular image from the Commodore Amiga computer.
|
||||
* 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
|
||||
@@ -146,7 +162,7 @@ Legacy formats
|
||||
* Uncompressed
|
||||
* RLE (PackBits)
|
||||
|
||||
#### ZSoft Paintbrush Format (PCX) *3.1*
|
||||
#### 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
|
||||
@@ -158,7 +174,7 @@ Legacy formats
|
||||
* RLE compressed
|
||||
* Standard metadata support
|
||||
|
||||
#### Apple Mac Paint Picture Format (PICT)
|
||||
#### PICT - Apple Mac Paint Picture Format
|
||||
|
||||
* Legacy format, especially useful for reading OS X clipboard data.
|
||||
* Read support for the following file types:
|
||||
@@ -169,7 +185,7 @@ Legacy formats
|
||||
* Write support for RGB pixel data:
|
||||
* QuickDraw pixmap
|
||||
|
||||
#### Silicon Graphics Image Format (SGI) *3.1*
|
||||
#### SGI - Silicon Graphics Image Format
|
||||
|
||||
* Read support for the following file types:
|
||||
* 1, 2, 3 or 4 channel image data
|
||||
@@ -179,7 +195,7 @@ Legacy formats
|
||||
* RLE compressed
|
||||
* Standard metadata support
|
||||
|
||||
#### Truevision TGA Image Format (TGA) *3.1*
|
||||
#### TGA - Truevision TGA Image Format
|
||||
|
||||
* Read support for the following file types:
|
||||
* ColorMapped
|
||||
@@ -192,35 +208,39 @@ Legacy formats
|
||||
|
||||
Icon/other formats
|
||||
|
||||
#### Apple Icon Image (ICNS)
|
||||
#### 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)
|
||||
|
||||
#### MS Windows Icon and Cursor Formats (ICO & CUR)
|
||||
#### 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
|
||||
* *3.1* Note: These formats are now part of the BMP plugin
|
||||
|
||||
#### MS Windows Thumbs DB (Thumbs.db)
|
||||
#### Thumbs.db - MS Windows Thumbs DB
|
||||
|
||||
* Read support
|
||||
|
||||
Other formats, using 3rd party libraries
|
||||
|
||||
#### Scalable Vector Graphics (SVG)
|
||||
#### SVG - Scalable Vector Graphics
|
||||
|
||||
* Read-only support using Batik
|
||||
|
||||
#### MS Windows MetaFile (WMF)
|
||||
#### 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+.*
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
@@ -419,9 +439,9 @@ Build the project (using [Maven](http://maven.apache.org/download.cgi)):
|
||||
|
||||
$ mvn package
|
||||
|
||||
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
|
||||
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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`.
|
||||
@@ -459,12 +479,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<version>3.0.2</version> <!-- Alternatively, build your own version -->
|
||||
<version>3.2.1</version> <!-- Alternatively, build your own version -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>3.0.2</version> <!-- Alternatively, build your own version -->
|
||||
<version>3.2.1</version> <!-- Alternatively, build your own version -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -472,25 +492,66 @@ 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.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
|
||||
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
|
||||
|
||||
### 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)
|
||||
@@ -500,14 +561,15 @@ 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):
|
||||
|
||||
+6
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
@@ -58,6 +58,11 @@
|
||||
<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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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,8 +358,9 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
* @param pImage the image to clone.
|
||||
*
|
||||
@@ -378,7 +379,7 @@ public final class ImageUtil {
|
||||
cm.createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
|
||||
drawOnto(pImage, img);
|
||||
drawOnto(img, pImage);
|
||||
|
||||
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)];
|
||||
contribX.p = new Contributor[(int) (width * 2.0 + 1.0 + 0.5)];
|
||||
|
||||
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)];
|
||||
contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0 + 0.5)];
|
||||
|
||||
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)];
|
||||
contribY[i].p = new Contributor[(int) (width * 2.0 + 1 + 0.5)];
|
||||
|
||||
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)];
|
||||
contribY[i].p = new Contributor[(int) (fwidth * 2 + 1 + 0.5)];
|
||||
|
||||
double center = (double) i / yscale;
|
||||
double left = Math.ceil(center - fwidth);
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
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,11 +103,6 @@ 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<String>();
|
||||
List<String> exceptions = new ArrayList<>();
|
||||
|
||||
// Test all image types in BufferedImage
|
||||
for (int type = BufferedImage.TYPE_INT_ARGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) {
|
||||
@@ -304,6 +304,30 @@ 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() {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -22,7 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class DateUtil {
|
||||
* @param pTime time
|
||||
* @return the time rounded to the closest second.
|
||||
*/
|
||||
public static long roundToSecond(long pTime) {
|
||||
public static long roundToSecond(final long pTime) {
|
||||
return (pTime / SECOND) * SECOND;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ public final class DateUtil {
|
||||
* @param pTime time
|
||||
* @return the time rounded to the closest minute.
|
||||
*/
|
||||
public static long roundToMinute(long pTime) {
|
||||
public static long roundToMinute(final long pTime) {
|
||||
return (pTime / MINUTE) * MINUTE;
|
||||
}
|
||||
|
||||
@@ -162,9 +162,20 @@ public final class DateUtil {
|
||||
* @param pTime time
|
||||
* @return the time rounded to the closest hour.
|
||||
*/
|
||||
public static long roundToHour(long pTime) {
|
||||
// TODO: What if timezone offset is sub hour? Are there any? I think so...
|
||||
return ((pTime / HOUR) * HOUR);
|
||||
public static long roundToHour(final long pTime) {
|
||||
return roundToHour(pTime, TimeZone.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds the given time down to the closest hour, using the given timezone.
|
||||
*
|
||||
* @param pTime time
|
||||
* @param pTimeZone the timezone to use when rounding
|
||||
* @return the time rounded to the closest hour.
|
||||
*/
|
||||
public static long roundToHour(final long pTime, final TimeZone pTimeZone) {
|
||||
int offset = pTimeZone.getOffset(pTime);
|
||||
return ((pTime / HOUR) * HOUR) - offset;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +184,7 @@ public final class DateUtil {
|
||||
* @param pTime time
|
||||
* @return the time rounded to the closest day.
|
||||
*/
|
||||
public static long roundToDay(long pTime) {
|
||||
public static long roundToDay(final long pTime) {
|
||||
return roundToDay(pTime, TimeZone.getDefault());
|
||||
}
|
||||
|
||||
@@ -184,7 +195,7 @@ public final class DateUtil {
|
||||
* @param pTimeZone the timezone to use when rounding
|
||||
* @return the time rounded to the closest day.
|
||||
*/
|
||||
public static long roundToDay(long pTime, TimeZone pTimeZone) {
|
||||
public static long roundToDay(final long pTime, final TimeZone pTimeZone) {
|
||||
int offset = pTimeZone.getOffset(pTime);
|
||||
return (((pTime + offset) / DAY) * DAY) - offset;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,15 @@
|
||||
package com.twelvemonkeys.lang;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* DateUtilTest
|
||||
@@ -42,9 +46,30 @@ import static org.junit.Assert.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: DateUtilTest.java,v 1.0 11.04.12 16:21 haraldk Exp$
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class DateUtilTest {
|
||||
private static Calendar getCalendar(long time) {
|
||||
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
|
||||
|
||||
private final TimeZone timeZone;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static List<Object[]> timeZones() {
|
||||
return Arrays.asList(new Object[][] {
|
||||
{TimeZone.getTimeZone("UTC")},
|
||||
{TimeZone.getTimeZone("CET")},
|
||||
{TimeZone.getTimeZone("IST")}, // 30 min off
|
||||
});
|
||||
}
|
||||
|
||||
public DateUtilTest(final TimeZone timeZone) {
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
|
||||
private Calendar getCalendar(long time) {
|
||||
return getCalendar(time, TimeZone.getDefault());
|
||||
}
|
||||
|
||||
private Calendar getCalendar(long time, final TimeZone timeZone) {
|
||||
Calendar calendar = Calendar.getInstance(timeZone);
|
||||
calendar.setTimeInMillis(time);
|
||||
|
||||
return calendar;
|
||||
@@ -74,6 +99,15 @@ public class DateUtilTest {
|
||||
assertEquals(0, calendar.get(Calendar.MINUTE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoundToHourTZ() {
|
||||
Calendar calendar = getCalendar(DateUtil.roundToHour(System.currentTimeMillis(), timeZone), timeZone);
|
||||
|
||||
assertEquals(0, calendar.get(Calendar.MILLISECOND));
|
||||
assertEquals(0, calendar.get(Calendar.SECOND));
|
||||
assertEquals(0, calendar.get(Calendar.MINUTE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoundToDay() {
|
||||
Calendar calendar = getCalendar(DateUtil.roundToDay(System.currentTimeMillis()));
|
||||
@@ -84,6 +118,16 @@ public class DateUtilTest {
|
||||
assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRoundToDayTZ() {
|
||||
Calendar calendar = getCalendar(DateUtil.roundToDay(System.currentTimeMillis(), timeZone), timeZone);
|
||||
|
||||
assertEquals(0, calendar.get(Calendar.MILLISECOND));
|
||||
assertEquals(0, calendar.get(Calendar.SECOND));
|
||||
assertEquals(0, calendar.get(Calendar.MINUTE));
|
||||
assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCurrentTimeSecond() {
|
||||
Calendar calendar = getCalendar(DateUtil.currentTimeSecond());
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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.3-SNAPSHOT</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>
|
||||
@@ -0,0 +1,603 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -23,27 +23,54 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-rasterizer-ext</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<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>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<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>
|
||||
<artifactId>batik-svggen</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-transcoder</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
|
||||
<!--
|
||||
@@ -59,4 +86,8 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<batik.version>1.8</batik.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
+3
-3
@@ -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);
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
|
||||
+62
-34
@@ -34,6 +34,7 @@ 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;
|
||||
|
||||
@@ -51,6 +52,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
/**
|
||||
* Creates an {@code SVGImageReaderSpi}.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public SVGImageReaderSpi() {
|
||||
super(new SVGProviderInfo());
|
||||
}
|
||||
@@ -59,10 +61,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 requires the doctype to be "svg", which may not be correct
|
||||
// in all cases...
|
||||
// however it may not recognize all kinds of SVG documents.
|
||||
try {
|
||||
pInput.mark();
|
||||
|
||||
@@ -74,54 +76,80 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
// Skip over leading WS
|
||||
}
|
||||
|
||||
if (!((b == '<') && (pInput.read() == '?') && (pInput.read() == 'x') && (pInput.read() == 'm')
|
||||
&& (pInput.read() == 'l'))) {
|
||||
// If it's not a tag, this can't be valid XML
|
||||
if (b != '<') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Okay, we have XML. But, is it really SVG?
|
||||
boolean docTypeFound = false;
|
||||
while (!docTypeFound) {
|
||||
while (pInput.read() != '<') {
|
||||
// Skip over, until begin tag
|
||||
}
|
||||
// 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
|
||||
|
||||
if ((b = pInput.read()) != '!') {
|
||||
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g'
|
||||
&& (Character.isWhitespace((char) (b = pInput.read())) || b == ':')) {
|
||||
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
|
||||
}
|
||||
}
|
||||
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
|
||||
// TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)!
|
||||
return true;
|
||||
}
|
||||
|
||||
// If this is not a comment, or the DOCTYPE declaration, the doc has no DOCTYPE and it can't be svg
|
||||
// If the tag is not "svg", this isn't SVG
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
while ((pInput.readByte() & 0xFF) != '<') {
|
||||
// Skip over, until next begin tag or EOF
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (EOFException ignore) {
|
||||
// Possible for small files...
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
//noinspection ThrowFromFinallyBlock
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -59,7 +59,8 @@ 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/blue-square.svg"), new Dimension(100, 100)),
|
||||
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,6 +120,13 @@ 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);
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -51,8 +51,7 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Collections.singletonList(
|
||||
// TODO: Dimensions does not look right...
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(841, 673))
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(133, 106))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,4 +86,11 @@ 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();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+11
-7
@@ -160,13 +160,15 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
// There might be more entries in the color map, but we ignore these for reading
|
||||
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
|
||||
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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,12 +692,14 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-53
@@ -28,17 +28,16 @@
|
||||
|
||||
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 IIOMetadata {
|
||||
final class BMPMetadata extends AbstractMetadata {
|
||||
/** 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";
|
||||
|
||||
@@ -46,46 +45,13 @@ final class BMPMetadata extends IIOMetadata {
|
||||
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
|
||||
public String getNativeMetadataFormatName() {
|
||||
return nativeMetadataFormatName;
|
||||
}
|
||||
|
||||
private Node getNativeTree() {
|
||||
protected Node getNativeTree() {
|
||||
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
|
||||
|
||||
addChildNode(root, "BMPVersion", header.getBMPVersion());
|
||||
@@ -170,7 +136,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
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...
|
||||
@@ -197,7 +164,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
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");
|
||||
@@ -229,7 +197,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
// }
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
@@ -294,7 +263,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
@@ -302,16 +272,16 @@ final class BMPMetadata extends IIOMetadata {
|
||||
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;
|
||||
}
|
||||
@@ -325,7 +295,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
return null;
|
||||
|
||||
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
+14
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
|
||||
protected final DIBHeader header;
|
||||
|
||||
protected BufferedImage image;
|
||||
protected BitmapMask mask;
|
||||
|
||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
Validate.notNull(pEntry, "entry");
|
||||
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
|
||||
protected final int getBitCount() {
|
||||
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
||||
}
|
||||
|
||||
public final void setMask(final BitmapMask mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
public final boolean hasMask() {
|
||||
return header.getHeight() == getHeight() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-34
@@ -28,8 +28,6 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
protected final int[] bits;
|
||||
protected final int[] colors;
|
||||
|
||||
private BitmapMask mask;
|
||||
|
||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
super(pEntry, pHeader);
|
||||
bits = new int[getWidth() * getHeight()];
|
||||
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
// This is slightly obscure, and should probably be moved..
|
||||
Hashtable<String, Object> properties = null;
|
||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||
properties = new Hashtable<String, Object>(1);
|
||||
properties = new Hashtable<>(1);
|
||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||
}
|
||||
|
||||
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
|
||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
||||
|
||||
//System.out.println("Image: " + image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
IndexColorModel createColorModel() {
|
||||
// NOTE: This is a hack to make room for transparent pixel for mask
|
||||
int bits = getBitCount();
|
||||
|
||||
|
||||
int colors = this.colors.length;
|
||||
int trans = -1;
|
||||
int transparent = -1;
|
||||
|
||||
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
||||
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
||||
if (colors > (1 << getBitCount())) {
|
||||
int index = findTransIndexMaybeRemap(this.colors, this.bits);
|
||||
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||
|
||||
if (index == -1) {
|
||||
// No duplicate found, increase bitcount
|
||||
bits++;
|
||||
trans = this.colors.length - 1;
|
||||
transparent = this.colors.length - 1;
|
||||
}
|
||||
else {
|
||||
// Found a duplicate, use it as trans
|
||||
trans = index;
|
||||
// Found a duplicate, use it as transparent
|
||||
transparent = index;
|
||||
colors--;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||
return new InverseColorMapIndexColorModel(
|
||||
bits, colors, this.colors, 0, true, trans,
|
||||
return new IndexColorModel(
|
||||
bits, colors, this.colors, 0, true, transparent,
|
||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
||||
);
|
||||
}
|
||||
|
||||
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
|
||||
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||
// Look for unused colors, to use as transparent
|
||||
final boolean[] used = new boolean[pColors.length - 1];
|
||||
for (int pBit : pBits) {
|
||||
if (!used[pBit]) {
|
||||
used[pBit] = true;
|
||||
boolean[] used = new boolean[colors.length - 1];
|
||||
for (int bit : bits) {
|
||||
if (!used[bit]) {
|
||||
used[bit] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
// Try to find duplicates in colormap, and remap
|
||||
int trans = -1;
|
||||
int transparent = -1;
|
||||
int duplicate = -1;
|
||||
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
|
||||
for (int j = i + 1; j < pColors.length - 1; j++) {
|
||||
if (pColors[i] == pColors[j]) {
|
||||
trans = j;
|
||||
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
|
||||
for (int j = i + 1; j < colors.length - 1; j++) {
|
||||
if (colors[i] == colors[j]) {
|
||||
transparent = j;
|
||||
duplicate = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trans != -1) {
|
||||
if (transparent != -1) {
|
||||
// Remap duplicate
|
||||
for (int i = 0; i < pBits.length; i++) {
|
||||
if (pBits[i] == trans) {
|
||||
pBits[i] = duplicate;
|
||||
for (int i = 0; i < bits.length; i++) {
|
||||
if (bits[i] == transparent) {
|
||||
bits[i] = duplicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trans;
|
||||
return transparent;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
if (image == null) {
|
||||
image = createImageIndexed();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setMask(final BitmapMask pMask) {
|
||||
mask = pMask;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
|
||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class BitmapMask extends BitmapDescriptor {
|
||||
protected final BitmapIndexed mask;
|
||||
protected final BitmapIndexed bitMask;
|
||||
|
||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
||||
super(pParent, pHeader);
|
||||
mask = new BitmapIndexed(pParent, pHeader);
|
||||
bitMask = new BitmapIndexed(pParent, pHeader);
|
||||
}
|
||||
|
||||
boolean isTransparent(final int pX, final int pY) {
|
||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||
return mask.bits[pX + pY * getWidth()] != 0;
|
||||
return bitMask.bits[pX + pY * getWidth()] != 0;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return mask.getImage();
|
||||
return bitMask.getImage();
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -28,7 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.WritableRaster;
|
||||
|
||||
/**
|
||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
||||
// might still have bitmask, but we don't read or use it.
|
||||
if (mask != null) {
|
||||
image = createMaskedImage();
|
||||
mask = null;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private BufferedImage createMaskedImage() {
|
||||
BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||
|
||||
Graphics2D graphics = masked.createGraphics();
|
||||
try {
|
||||
graphics.drawImage(image, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||
|
||||
byte[] trans = {0x0};
|
||||
for (int y = 0; y < getHeight(); y++) {
|
||||
for (int x = 0; x < getWidth(); x++) {
|
||||
if (mask.isTransparent(x, y)) {
|
||||
alphaRaster.setDataElements(x, y, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return masked;
|
||||
}
|
||||
}
|
||||
|
||||
+35
-14
@@ -66,14 +66,15 @@ import java.util.List;
|
||||
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||
// TODO: Support loading icons from DLLs, see
|
||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry
|
||||
// (seem impossible as the PNGs are all true color)
|
||||
abstract class DIBImageReader extends ImageReaderBase {
|
||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||
private Directory directory;
|
||||
|
||||
// TODO: Review these, make sure we don't have a memory leak
|
||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||
|
||||
private ImageReader pngImageReader;
|
||||
|
||||
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
return getImageTypesPNG(entry);
|
||||
}
|
||||
|
||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
||||
List<ImageTypeSpecifier> types = new ArrayList<>();
|
||||
DIBHeader header = getHeader(entry);
|
||||
|
||||
// Use data from header to create specifier
|
||||
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
||||
break;
|
||||
case 16:
|
||||
// TODO: May have mask?!
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||
break;
|
||||
case 24:
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
specifier = new BitmapRGB(entry, header).hasMask()
|
||||
? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
|
||||
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
break;
|
||||
case 32:
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
Graphics2D g = destination.createGraphics();
|
||||
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.drawImage(image, 0, 0, null);
|
||||
@@ -335,7 +340,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||
readBitmapIndexed1(mask.mask, true);
|
||||
readBitmapIndexed1(mask.bitMask, true);
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
|
||||
@@ -370,7 +375,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: If we are reading the mask, we don't abort or progress
|
||||
// NOTE: If we are reading the mask, we don't abort or report progress
|
||||
if (!pAsMask) {
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -455,7 +460,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// 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);
|
||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
@@ -480,6 +485,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// TODO: Might be mask!?
|
||||
}
|
||||
|
||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||
@@ -494,16 +501,19 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
|
||||
int scanlineStride = pBitmap.getWidth() * 3;
|
||||
// BMP rows are padded to 4 byte boundary
|
||||
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||
|
||||
WritableRaster raster = Raster.createInterleavedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||
);
|
||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
||||
|
||||
// TODO: Possibly read padding byte here!
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
||||
imageInput.readFully(pixels, offset, scanlineStride);
|
||||
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -512,6 +522,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// 24 bit icons usually have a bit mask
|
||||
if (pBitmap.hasMask()) {
|
||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||
readBitmapIndexed1(mask.bitMask, true);
|
||||
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||
@@ -535,6 +553,9 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// There might be a mask here as well, but we'll ignore it,
|
||||
// and use the 8 bit alpha channel in the ARGB pixel data
|
||||
}
|
||||
|
||||
private Directory getDirectory() throws IOException {
|
||||
|
||||
+57
-5
@@ -1,20 +1,19 @@
|
||||
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.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
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.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -108,7 +107,8 @@ 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/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/24bitpalette.bmp"), new Dimension(320, 200))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,6 +173,58 @@ 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
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+7
@@ -119,4 +119,11 @@ 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();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -39,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
|
||||
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
||||
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
||||
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
||||
)
|
||||
),
|
||||
// Problematic icon that reports 24 bit in the descriptor, but has separate 1 bit ''mask (height 2 x icon height)!
|
||||
new TestData(getClassLoaderResource("/ico/rgb24bitmask.ico"), new Dimension(32, 32))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,4 +76,11 @@ 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();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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.
|
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
+32
-30
@@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -42,13 +42,15 @@ import java.util.Arrays;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
|
||||
*/
|
||||
abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
// TODO: Move to core...
|
||||
public abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
protected AbstractMetadata(final boolean standardFormatSupported,
|
||||
final String nativeFormatName, final String nativeFormatClassName,
|
||||
final String[] extraFormatNames, final String[] extraFormatClassNames) {
|
||||
super(standardFormatSupported, nativeFormatName, nativeFormatClassName, extraFormatNames, extraFormatClassNames);
|
||||
}
|
||||
|
||||
protected AbstractMetadata(final boolean pStandardFormatSupported,
|
||||
final String pNativeFormatName, final String pNativeFormatClassName,
|
||||
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
|
||||
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
|
||||
protected AbstractMetadata() {
|
||||
super(true, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,36 +65,38 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getAsTree(final String pFormatName) {
|
||||
validateFormatName(pFormatName);
|
||||
public Node getAsTree(final String formatName) {
|
||||
validateFormatName(formatName);
|
||||
|
||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||
if (formatName.equals(nativeMetadataFormatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
|
||||
// TODO: What about extra formats??
|
||||
throw new AssertionError("Unreachable");
|
||||
// Subclasses that supports extra formats need to check for these formats themselves...
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation that throws {@code UnsupportedOperationException}.
|
||||
* Subclasses that supports formats other than standard metadata should override this method.
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
protected Node getNativeTree() {
|
||||
throw new UnsupportedOperationException("getNativeTree");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||
public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||
assertMutable();
|
||||
|
||||
validateFormatName(pFormatName);
|
||||
validateFormatName(formatName);
|
||||
|
||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||
}
|
||||
|
||||
Node node = pRoot.getFirstChild();
|
||||
while (node != null) {
|
||||
// TODO: Merge values from node into this
|
||||
|
||||
// Move to the next sibling
|
||||
node = node.getNextSibling();
|
||||
if (!root.getNodeName().equals(formatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,21 +116,19 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Node getNativeTree();
|
||||
|
||||
protected final void validateFormatName(final String pFormatName) {
|
||||
protected final void validateFormatName(final String formatName) {
|
||||
String[] metadataFormatNames = getMetadataFormatNames();
|
||||
|
||||
if (metadataFormatNames != null) {
|
||||
for (String metadataFormatName : metadataFormatNames) {
|
||||
if (metadataFormatName.equals(pFormatName)) {
|
||||
if (metadataFormatName.equals(formatName)) {
|
||||
return; // Found, we're ok!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames))
|
||||
);
|
||||
}
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+30
-4
@@ -52,7 +52,7 @@ import java.util.Properties;
|
||||
* <p />
|
||||
* Color profiles may be configured by placing a property-file
|
||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||
* on the classpath, specifying the full path to the profile.
|
||||
* on the classpath, specifying the full path to the profiles.
|
||||
* ICC color profiles are probably already present on your system, or
|
||||
* can be downloaded from
|
||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||
@@ -100,7 +100,8 @@ 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 IllegalArgumentException if {@code profile} is {@code null}.
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
@@ -161,6 +162,11 @@ public final class ColorSpaces {
|
||||
|
||||
if (cs == null) {
|
||||
cs = new ICC_ColorSpace(profile);
|
||||
|
||||
// Validate the color space, to avoid caching bad color spaces
|
||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||
cs.fromRGB(new float[] {1f, 0f, 0f});
|
||||
|
||||
cache.put(key, cs);
|
||||
}
|
||||
|
||||
@@ -195,7 +201,7 @@ public final class ColorSpaces {
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
public static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
@@ -213,6 +219,26 @@ public final class ColorSpaces {
|
||||
return data[ICC_Profile.icHdrRenderingIntent] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p />
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
createColorSpace(profile); // Creating a color space will fail if the profile is bad
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color space specified by the given color space constant.
|
||||
* <p />
|
||||
@@ -317,7 +343,7 @@ public final class ColorSpaces {
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch (SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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 nativeStreameMetadataFormatName;
|
||||
private final String nativeStreamMetadataFormatName;
|
||||
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.nativeStreameMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
||||
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
||||
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
||||
@@ -149,7 +149,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatName() {
|
||||
return nativeStreameMetadataFormatName;
|
||||
return nativeStreamMetadataFormatName;
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatClassName() {
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@ import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SubImageOutputStream.
|
||||
* 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.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
|
||||
+60
-5
@@ -28,14 +28,19 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* Factory class for creating {@code ImageTypeSpecifier}s.
|
||||
* In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
|
||||
* 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}.
|
||||
*
|
||||
* @see javax.imageio.ImageTypeSpecifier
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -54,6 +59,20 @@ 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);
|
||||
}
|
||||
|
||||
@@ -79,7 +98,11 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
@@ -89,7 +112,11 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
@@ -98,6 +125,34 @@ 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) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
-11
@@ -28,7 +28,6 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -46,6 +45,7 @@ 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;
|
||||
}
|
||||
|
||||
private static void failBecause(String message, Throwable exception) {
|
||||
protected static void failBecause(String message, Throwable exception) {
|
||||
AssertionError error = new AssertionError(message);
|
||||
error.initCause(exception);
|
||||
throw error;
|
||||
@@ -471,7 +471,6 @@ 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();
|
||||
@@ -479,28 +478,66 @@ 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);
|
||||
}
|
||||
|
||||
BufferedImage expected = ImageUtil.toBuffered(IIOUtil.fakeSubsampling(image, param));
|
||||
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// TODO: Subsample all test data
|
||||
// TODO: Subsample with varying ratios and offsets
|
||||
|
||||
assertImageDataEquals("Subsampled image data does not match expected", expected, subsampled);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final void assertImageDataEquals(String message, BufferedImage expected, BufferedImage actual) {
|
||||
@@ -1602,6 +1639,21 @@ 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;
|
||||
|
||||
+78
-29
@@ -1,12 +1,11 @@
|
||||
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.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class ImageTypeSpecifiersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked() {
|
||||
public void testCreatePacked32() {
|
||||
// TYPE_INT_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
@@ -61,31 +60,70 @@ 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(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
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 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)
|
||||
// );
|
||||
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
|
||||
|
||||
// TYPE_USHORT_565_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
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(
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
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(
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
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
|
||||
@@ -337,11 +375,7 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
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),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
|
||||
);
|
||||
}
|
||||
@@ -400,19 +434,11 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
|
||||
);
|
||||
assertEquals(
|
||||
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),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
|
||||
);
|
||||
}
|
||||
@@ -437,6 +463,30 @@ 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) {
|
||||
@@ -562,7 +612,6 @@ public class ImageTypeSpecifiersTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static byte[] createByteLut(final int count) {
|
||||
byte[] lut = new byte[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
+4
-6
@@ -63,6 +63,7 @@ public abstract class ImageWriterAbstractTestCase {
|
||||
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
protected abstract ImageWriter createImageWriter();
|
||||
@@ -120,23 +121,20 @@ public abstract class ImageWriterAbstractTestCase {
|
||||
|
||||
for (RenderedImage testData : getTestData()) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
||||
writer.setOutput(stream);
|
||||
|
||||
try {
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||
writer.setOutput(stream);
|
||||
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();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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.3-SNAPSHOT</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>
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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'};
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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";
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+1
-1
@@ -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.ics.ICNImageReaderSpi"},
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
|
||||
null, null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* ICNSProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICNSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICNSProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new ICNSProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
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,4 +1,6 @@
|
||||
Copyright (c) 2013, Harald Kuhr
|
||||
Copyright (c) 2016, Harald Kuhr
|
||||
Copyright (C) 2015, Michael Martinez (JPEG Lossless decoder)
|
||||
Copyright (C) 2004, Helmut Dersch (Java JPEG decoder)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>3.3.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>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
+27
-22
@@ -28,6 +28,11 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* AdobeDCTSegment
|
||||
*
|
||||
@@ -35,44 +40,44 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
||||
*/
|
||||
class AdobeDCTSegment {
|
||||
public static final int Unknown = 0;
|
||||
public static final int YCC = 1;
|
||||
public static final int YCCK = 2;
|
||||
final class AdobeDCT extends Application {
|
||||
static final int Unknown = 0;
|
||||
static final int YCC = 1;
|
||||
static final int YCCK = 2;
|
||||
|
||||
final int version;
|
||||
final int flags0;
|
||||
final int flags1;
|
||||
final int transform;
|
||||
|
||||
AdobeDCTSegment(int version, int flags0, int flags1, 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});
|
||||
|
||||
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]",
|
||||
getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform()
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Application.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Application.java,v 1.0 22/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class Application extends Segment {
|
||||
|
||||
final String identifier;
|
||||
final byte[] data;
|
||||
|
||||
Application(final int marker, final String identifier, final byte[] data) {
|
||||
super(marker);
|
||||
|
||||
this.identifier = identifier; // NOTE: Some JPEGs contain APP segments without NULL-terminated identifier
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
InputStream data() {
|
||||
int offset = identifier.length() + 1;
|
||||
return new ByteArrayInputStream(data, offset, data.length - offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "APP" + (marker & 0x0f) + "/" + identifier + "[length: " + data.length + "]";
|
||||
}
|
||||
|
||||
public static Application read(final int marker, final String identifier, final DataInput data, final int length) throws IOException {
|
||||
switch (marker) {
|
||||
case JPEG.APP0:
|
||||
// JFIF
|
||||
if ("JFIF".equals(identifier)) {
|
||||
return JFIF.read(data, length);
|
||||
}
|
||||
case JPEG.APP1:
|
||||
// JFXX
|
||||
if ("JFXX".equals(identifier)) {
|
||||
return JFXX.read(data, length);
|
||||
}
|
||||
// TODO: Exif?
|
||||
case JPEG.APP2:
|
||||
// ICC_PROFILE
|
||||
if ("ICC_PROFILE".equals(identifier)) {
|
||||
return ICCProfile.read(data, length);
|
||||
}
|
||||
case JPEG.APP14:
|
||||
// Adobe
|
||||
if ("Adobe".equals(identifier)) {
|
||||
return AdobeDCT.read(data, length);
|
||||
}
|
||||
|
||||
default:
|
||||
// Generic APPn segment
|
||||
byte[] bytes = new byte[length - 2];
|
||||
data.readFully(bytes);
|
||||
return new Application(marker, identifier, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-28
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,39 +28,36 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import java.util.Arrays;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
* Comment.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: Comment.java,v 1.0 23/08/16 harald.kuhr Exp$
|
||||
*/
|
||||
class Comment extends Segment {
|
||||
final String comment;
|
||||
|
||||
SOFSegment(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;
|
||||
private Comment(final String comment) {
|
||||
super(JPEG.COM);
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
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)
|
||||
);
|
||||
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
|
||||
}
|
||||
}
|
||||
+8
-11
@@ -28,6 +28,7 @@
|
||||
|
||||
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;
|
||||
@@ -61,7 +62,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
|
||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||
|
||||
public EXIFThumbnailReader(ThumbnailReadProgressListener progressListener, ImageReader jpegReader, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
|
||||
EXIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final Directory ifd, final ImageInputStream stream) {
|
||||
super(progressListener, imageIndex, thumbnailIndex);
|
||||
this.reader = Validate.notNull(jpegReader);
|
||||
this.ifd = ifd;
|
||||
@@ -95,14 +96,14 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException {
|
||||
private BufferedImage readJPEGCached(final boolean pixelsExposed) throws IOException {
|
||||
BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null;
|
||||
|
||||
if (thumbnail == null) {
|
||||
thumbnail = readJPEG();
|
||||
}
|
||||
|
||||
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
|
||||
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
@@ -132,14 +133,10 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
|
||||
|
||||
try {
|
||||
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input);
|
||||
|
||||
try {
|
||||
try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) {
|
||||
return readJPEGThumbnail(reader, stream);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
@@ -195,15 +192,15 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
break;
|
||||
case 6:
|
||||
// YCbCr
|
||||
for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
|
||||
JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
|
||||
for (int i = 0; i < thumbSize; i += 3) {
|
||||
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, thumbData.length, 0, w, h);
|
||||
return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h);
|
||||
}
|
||||
|
||||
throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail");
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (C) 2015, Michael Martinez
|
||||
* Copyright (C) 2004, Helmut Dersch
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
final class HuffmanTable extends Segment {
|
||||
|
||||
private final int l[][][] = new int[4][2][16];
|
||||
private final int th[] = new int[4]; // 1: this table is present
|
||||
final int v[][][][] = new int[4][2][16][200]; // tables
|
||||
final int[][] tc = new int[4][2]; // 1: this table is present
|
||||
|
||||
static final int MSB = 0x80000000;
|
||||
|
||||
private HuffmanTable() {
|
||||
super(JPEG.DHT);
|
||||
}
|
||||
|
||||
void buildHuffTables(final int[][][] HuffTab) throws IOException {
|
||||
for (int t = 0; t < 4; t++) {
|
||||
for (int c = 0; c < 2; c++) {
|
||||
if (tc[t][c] != 0) {
|
||||
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build_HuffTab()
|
||||
// Parameter: t table ID
|
||||
// c table class ( 0 for DC, 1 for AC )
|
||||
// L[i] # of codewords which length is i
|
||||
// V[i][j] Huffman Value (length=i)
|
||||
// Effect:
|
||||
// build up HuffTab[t][c] using L and V.
|
||||
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
|
||||
int temp = 256;
|
||||
int k = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) { // i+1 is Code length
|
||||
for (int j = 0; j < L[i]; j++) {
|
||||
for (int n = 0; n < (temp >> (i + 1)); n++) {
|
||||
tab[k] = V[i][j] | ((i + 1) << 8);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; k < 256; i++, k++) {
|
||||
tab[k] = i | MSB;
|
||||
}
|
||||
|
||||
int currentTable = 1;
|
||||
k = 0;
|
||||
|
||||
for (int i = 8; i < 16; i++) { // i+1 is Code length
|
||||
for (int j = 0; j < L[i]; j++) {
|
||||
for (int n = 0; n < (temp >> (i - 7)); n++) {
|
||||
tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
|
||||
k++;
|
||||
}
|
||||
if (k >= 256) {
|
||||
if (k > 256) {
|
||||
throw new IIOException("JPEG Huffman Table error");
|
||||
}
|
||||
|
||||
k = 0;
|
||||
currentTable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder("DHT[");
|
||||
|
||||
for (int t = 0; t < tc.length; t++) {
|
||||
for (int c = 0; c < tc[t].length; c++) {
|
||||
if (tc[t][c] != 0) {
|
||||
if (builder.length() > 4) {
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
builder.append("id: ");
|
||||
builder.append(t);
|
||||
|
||||
builder.append(", class: ");
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(']');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static Segment read(final DataInput data, final int length) throws IOException {
|
||||
int count = 2;
|
||||
|
||||
HuffmanTable table = new HuffmanTable();
|
||||
|
||||
while (count < length) {
|
||||
int temp = data.readUnsignedByte();
|
||||
count++;
|
||||
int t = temp & 0x0F;
|
||||
if (t > 3) {
|
||||
throw new IIOException("Unexpected JPEG Huffman Table Id (> 3):" + t);
|
||||
}
|
||||
|
||||
int c = temp >> 4;
|
||||
if (c > 2) {
|
||||
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
|
||||
}
|
||||
|
||||
table.th[t] = 1;
|
||||
table.tc[t][c] = 1;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
table.l[t][c][i] = data.readUnsignedByte();
|
||||
count++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < table.l[t][c][i]; j++) {
|
||||
if (count > length) {
|
||||
throw new IIOException("JPEG Huffman Table format error");
|
||||
}
|
||||
table.v[t][c][i][j] = data.readUnsignedByte();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count != length) {
|
||||
throw new IIOException("JPEG Huffman Table format error, bad segment length: " + length);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
+40
-16
@@ -28,9 +28,10 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* JFIFSegment
|
||||
@@ -39,7 +40,7 @@ import java.io.InputStream;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
|
||||
*/
|
||||
class JFIFSegment {
|
||||
final class JFIF extends Application {
|
||||
final int majorVersion;
|
||||
final int minorVersion;
|
||||
final int units;
|
||||
@@ -49,7 +50,9 @@ class JFIFSegment {
|
||||
final int yThumbnail;
|
||||
final byte[] thumbnail;
|
||||
|
||||
private JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, 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);
|
||||
|
||||
this.majorVersion = majorVersion;
|
||||
this.minorVersion = minorVersion;
|
||||
this.units = units;
|
||||
@@ -86,20 +89,41 @@ class JFIFSegment {
|
||||
return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
|
||||
}
|
||||
|
||||
public static JFIFSegment read(final InputStream data) throws IOException {
|
||||
DataInputStream stream = new DataInputStream(data);
|
||||
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);
|
||||
|
||||
int x, y;
|
||||
|
||||
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)
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
private static byte[] getBytes(ByteBuffer buffer, int len) {
|
||||
if (len == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] dst = new byte[len];
|
||||
buffer.get(dst);
|
||||
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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 JFIFSegment segment;
|
||||
private final JFIF segment;
|
||||
|
||||
public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
|
||||
JFIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final JFIF segment) {
|
||||
super(progressListener, imageIndex, thumbnailIndex);
|
||||
this.segment = segment;
|
||||
}
|
||||
|
||||
+15
-9
@@ -28,9 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* JFXXSegment
|
||||
@@ -39,7 +39,7 @@ import java.io.InputStream;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
|
||||
*/
|
||||
class JFXXSegment {
|
||||
final class JFXX extends Application {
|
||||
public static final int JPEG = 0x10;
|
||||
public static final int INDEXED = 0x11;
|
||||
public static final int RGB = 0x13;
|
||||
@@ -47,7 +47,9 @@ class JFXXSegment {
|
||||
final int extensionCode;
|
||||
final byte[] thumbnail;
|
||||
|
||||
private JFXXSegment(int extensionCode, byte[] thumbnail) {
|
||||
private JFXX(final int extensionCode, final byte[] thumbnail, final byte[] data) {
|
||||
super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", data);
|
||||
|
||||
this.extensionCode = extensionCode;
|
||||
this.thumbnail = thumbnail;
|
||||
}
|
||||
@@ -70,12 +72,16 @@ class JFXXSegment {
|
||||
}
|
||||
}
|
||||
|
||||
public static JFXXSegment read(InputStream data, int length) throws IOException {
|
||||
DataInputStream stream = new DataInputStream(data);
|
||||
public static JFXX read(final DataInput data, final int length) throws IOException {
|
||||
data.readFully(new byte[5]);
|
||||
|
||||
return new JFXXSegment(
|
||||
stream.readUnsignedByte(),
|
||||
JPEGImageReader.readFully(stream, length - 1)
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -50,11 +50,11 @@ import java.lang.ref.SoftReference;
|
||||
final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
|
||||
private final ImageReader reader;
|
||||
private final JFXXSegment segment;
|
||||
private final JFXX segment;
|
||||
|
||||
private transient SoftReference<BufferedImage> cachedThumbnail;
|
||||
|
||||
protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
|
||||
JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXX 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 JFXXSegment.JPEG:
|
||||
case JFXX.JPEG:
|
||||
thumbnail = readJPEGCached(true);
|
||||
break;
|
||||
case JFXXSegment.INDEXED:
|
||||
case JFXX.INDEXED:
|
||||
thumbnail = readIndexed();
|
||||
break;
|
||||
case JFXXSegment.RGB:
|
||||
case JFXX.RGB:
|
||||
thumbnail = readRGB();
|
||||
break;
|
||||
default:
|
||||
@@ -85,7 +85,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public IIOMetadata readMetadata() throws IOException {
|
||||
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 JFXXSegment.RGB:
|
||||
case JFXXSegment.INDEXED:
|
||||
case JFXX.RGB:
|
||||
case JFXX.INDEXED:
|
||||
return segment.thumbnail[0] & 0xff;
|
||||
case JFXXSegment.JPEG:
|
||||
case JFXX.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 JFXXSegment.RGB:
|
||||
case JFXXSegment.INDEXED:
|
||||
case JFXX.RGB:
|
||||
case JFXX.INDEXED:
|
||||
return segment.thumbnail[1] & 0xff;
|
||||
case JFXXSegment.JPEG:
|
||||
case JFXX.JPEG:
|
||||
return readJPEGCached(false).getHeight();
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+40
-46
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -11,9 +10,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -39,7 +36,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<JPEGSegment> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
|
||||
List<Application> 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
|
||||
@@ -70,11 +67,11 @@ final class JPEGImage10MetadataCleaner {
|
||||
IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
|
||||
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
|
||||
|
||||
JFIFSegment jfifSegment = reader.getJFIF();
|
||||
JFXXSegment jfxxSegment = reader.getJFXX();
|
||||
AdobeDCTSegment adobeDCT = reader.getAdobeDCT();
|
||||
JFIF jfifSegment = reader.getJFIF();
|
||||
JFXX jfxx = reader.getJFXX();
|
||||
AdobeDCT adobeDCT = reader.getAdobeDCT();
|
||||
ICC_Profile embeddedICCProfile = reader.getEmbeddedICCProfile(true);
|
||||
SOFSegment sof = reader.getSOF();
|
||||
Frame sof = reader.getSOF();
|
||||
|
||||
boolean hasRealJFIF = false;
|
||||
boolean hasRealJFXX = false;
|
||||
@@ -104,17 +101,17 @@ final class JPEGImage10MetadataCleaner {
|
||||
hasRealICC = true;
|
||||
}
|
||||
|
||||
if (jfxxSegment != null) {
|
||||
if (jfxx != null) {
|
||||
IIOMetadataNode JFXX = new IIOMetadataNode("JFXX");
|
||||
jfif.appendChild(JFXX);
|
||||
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
|
||||
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxxSegment.extensionCode));
|
||||
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode));
|
||||
|
||||
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxxSegment);
|
||||
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxx);
|
||||
IIOMetadataNode jfifThumb;
|
||||
|
||||
switch (jfxxSegment.extensionCode) {
|
||||
case JFXXSegment.JPEG:
|
||||
switch (jfxx.extensionCode) {
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.JPEG:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
|
||||
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
|
||||
IIOMetadata thumbMeta = thumbnailReader.readMetadata();
|
||||
@@ -123,14 +120,14 @@ final class JPEGImage10MetadataCleaner {
|
||||
app0JFXX.appendChild(jfifThumb);
|
||||
break;
|
||||
|
||||
case JFXXSegment.INDEXED:
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.INDEXED:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbPalette");
|
||||
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
|
||||
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
|
||||
app0JFXX.appendChild(jfifThumb);
|
||||
break;
|
||||
|
||||
case JFXXSegment.RGB:
|
||||
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.RGB:
|
||||
jfifThumb = new IIOMetadataNode("JFIFthumbRGB");
|
||||
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
|
||||
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
|
||||
@@ -138,7 +135,7 @@ final class JPEGImage10MetadataCleaner {
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxxSegment.extensionCode));
|
||||
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxx.extensionCode));
|
||||
}
|
||||
|
||||
JFXX.appendChild(app0JFXX);
|
||||
@@ -156,12 +153,12 @@ final class JPEGImage10MetadataCleaner {
|
||||
}
|
||||
|
||||
// Special case: Broken AdobeDCT segment, inconsistent with SOF, use values from SOF
|
||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() < 4 ||
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() < 3)) {
|
||||
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() < 4 ||
|
||||
adobeDCT.transform == AdobeDCT.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.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
|
||||
sof.marker & 0xf, sof.componentsInFrame()
|
||||
));
|
||||
|
||||
@@ -176,46 +173,43 @@ final class JPEGImage10MetadataCleaner {
|
||||
}
|
||||
|
||||
Node next = null;
|
||||
for (JPEGSegment segment : appSegments) {
|
||||
for (Application 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));
|
||||
|
||||
DataInputStream stream = new DataInputStream(segment.data());
|
||||
unknown.setUserObject(segment.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();
|
||||
}
|
||||
// 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);
|
||||
// }
|
||||
|
||||
if (next == null) {
|
||||
// To be semi-compatible with the functionality in mergeTree,
|
||||
@@ -271,12 +265,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 = 0; i < dhtables.getLength(); i++) {
|
||||
for (int i = dhtables.getLength() - 1; i >= 0 ; i--) {
|
||||
Element dhtable = (Element) dhtables.item(i);
|
||||
String tableClass = dhtable.getAttribute("class");
|
||||
if ("1".equals(tableClass)) {
|
||||
dht.removeChild(dhtable);
|
||||
acTables.appendChild(dhtable);
|
||||
acTables.insertBefore(dhtable, acTables.getFirstChild());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+341
-336
File diff suppressed because it is too large
Load Diff
+13
-3
@@ -29,6 +29,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
@@ -48,14 +49,14 @@ import java.util.Locale;
|
||||
* @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$
|
||||
*/
|
||||
public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
private ImageReaderSpi delegateProvider;
|
||||
protected ImageReaderSpi delegateProvider;
|
||||
|
||||
/**
|
||||
* Constructor for use by {@link javax.imageio.spi.IIORegistry} only.
|
||||
* The instance created will not work without being properly registered.
|
||||
*/
|
||||
public JPEGImageReaderSpi() {
|
||||
super(new JPEGProviderInfo());
|
||||
this(new JPEGProviderInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,15 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
this.delegateProvider = Validate.notNull(delegateProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for subclasses.
|
||||
*
|
||||
* @param info
|
||||
*/
|
||||
protected JPEGImageReaderSpi(final ReaderWriterProviderInfo info) {
|
||||
super(info);
|
||||
}
|
||||
|
||||
static ImageReaderSpi lookupDelegateProvider(final ServiceRegistry registry) {
|
||||
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, true);
|
||||
|
||||
@@ -83,7 +93,7 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
if (delegateProvider == null) {
|
||||
|
||||
+743
@@ -0,0 +1,743 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Harald Kuhr
|
||||
* Copyright (C) 2015, Michael Martinez
|
||||
* Copyright (C) 2004, Helmut Dersch
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class JPEGLosslessDecoder {
|
||||
|
||||
private final ImageInputStream input;
|
||||
private final JPEGImageReader listenerDelegate;
|
||||
|
||||
private final Frame frame;
|
||||
private final List<HuffmanTable> huffTables;
|
||||
private final QuantizationTable quantTable;
|
||||
private Scan scan;
|
||||
|
||||
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
|
||||
private final int IDCT_Source[] = new int[64];
|
||||
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan
|
||||
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan
|
||||
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan
|
||||
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
|
||||
|
||||
private boolean restarting;
|
||||
private int marker;
|
||||
private int markerIndex;
|
||||
private int numComp;
|
||||
private int restartInterval;
|
||||
private int selection;
|
||||
private int xDim, yDim;
|
||||
private int xLoc;
|
||||
private int yLoc;
|
||||
private int mask;
|
||||
private int[] outputData;
|
||||
private int[] outputRedData;
|
||||
private int[] outputGreenData;
|
||||
private int[] outputBlueData;
|
||||
|
||||
private static final int IDCT_P[] = {
|
||||
0, 5, 40, 16, 45, 2, 7, 42,
|
||||
21, 56, 8, 61, 18, 47, 1, 4,
|
||||
41, 23, 58, 13, 32, 24, 37, 10,
|
||||
63, 17, 44, 3, 6, 43, 20, 57,
|
||||
15, 34, 29, 48, 53, 26, 39, 9,
|
||||
60, 19, 46, 22, 59, 12, 33, 31,
|
||||
50, 55, 25, 36, 11, 62, 14, 35,
|
||||
28, 49, 52, 27, 38, 30, 51, 54
|
||||
};
|
||||
private static final int TABLE[] = {
|
||||
0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63
|
||||
};
|
||||
|
||||
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
|
||||
private static final int RESTART_MARKER_END = 0xFFD7;
|
||||
private static final int MAX_HUFFMAN_SUBTREE = 50;
|
||||
private static final int MSB = 0x80000000;
|
||||
|
||||
int getDimX() {
|
||||
return xDim;
|
||||
}
|
||||
|
||||
int getDimY() {
|
||||
return yDim;
|
||||
}
|
||||
|
||||
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
|
||||
Validate.notNull(segments);
|
||||
|
||||
frame = get(segments, Frame.class);
|
||||
scan = get(segments, Scan.class);
|
||||
|
||||
QuantizationTable qt = get(segments, QuantizationTable.class);
|
||||
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
|
||||
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
|
||||
|
||||
RestartInterval dri = get(segments, RestartInterval.class);
|
||||
restartInterval = dri != null ? dri.interval : 0;
|
||||
|
||||
input = data;
|
||||
this.listenerDelegate = listenerDelegate;
|
||||
}
|
||||
|
||||
private <T> List<T> getAll(final List<Segment> segments, final Class<T> type) {
|
||||
ArrayList<T> list = new ArrayList<>();
|
||||
|
||||
for (Segment segment : segments) {
|
||||
if (type.isInstance(segment)) {
|
||||
list.add(type.cast(segment));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private <T> T get(final List<Segment> segments, final Class<T> type) {
|
||||
for (Segment segment : segments) {
|
||||
if (type.isInstance(segment)) {
|
||||
return type.cast(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
int[][] decode() throws IOException {
|
||||
int current, scanNum = 0;
|
||||
final int pred[] = new int[10];
|
||||
int[][] outputRef;
|
||||
|
||||
xLoc = 0;
|
||||
yLoc = 0;
|
||||
current = input.readUnsignedShort();
|
||||
|
||||
if (current != JPEG.SOI) { // SOI
|
||||
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
|
||||
}
|
||||
|
||||
for (HuffmanTable huffTable : huffTables) {
|
||||
huffTable.buildHuffTables(HuffTab);
|
||||
}
|
||||
|
||||
quantTable.enhanceTables(TABLE);
|
||||
|
||||
current = input.readUnsignedShort();
|
||||
|
||||
do {
|
||||
// Skip until first SOS
|
||||
while (current != JPEG.SOS) {
|
||||
input.skipBytes(input.readUnsignedShort() - 2);
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
|
||||
int precision = frame.samplePrecision;
|
||||
|
||||
if (precision == 8) {
|
||||
mask = 0xFF;
|
||||
}
|
||||
else {
|
||||
mask = 0xFFFF;
|
||||
}
|
||||
|
||||
Frame.Component[] components = frame.components;
|
||||
|
||||
scan = readScan();
|
||||
numComp = scan.components.length;
|
||||
selection = scan.spectralSelStart;
|
||||
|
||||
final Scan.Component[] scanComps = scan.components;
|
||||
final int[][] quantTables = quantTable.quantTables;
|
||||
|
||||
for (int i = 0; i < numComp; i++) {
|
||||
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
|
||||
qTab[i] = quantTables[component.qtSel];
|
||||
nBlock[i] = component.vSub * component.hSub;
|
||||
|
||||
int dcTabSel = scanComps[i].dcTabSel;
|
||||
int acTabSel = scanComps[i].acTabSel;
|
||||
|
||||
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
|
||||
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
|
||||
if (useACForDC(dcTabSel)) {
|
||||
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
|
||||
|
||||
dcTab[i] = HuffTab[dcTabSel][1];
|
||||
acTab[i] = HuffTab[acTabSel][0];
|
||||
}
|
||||
else {
|
||||
// All good
|
||||
dcTab[i] = HuffTab[dcTabSel][0];
|
||||
acTab[i] = HuffTab[acTabSel][1];
|
||||
}
|
||||
}
|
||||
|
||||
xDim = frame.samplesPerLine;
|
||||
yDim = frame.lines;
|
||||
|
||||
outputRef = new int[numComp][];
|
||||
|
||||
// TODO: Support 4 components (RGBA/YCCA/CMYK/YCCK), others?
|
||||
if (numComp == 1) {
|
||||
outputData = new int[xDim * yDim];
|
||||
outputRef[0] = outputData;
|
||||
}
|
||||
else {
|
||||
outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
|
||||
outputGreenData = new int[xDim * yDim];
|
||||
outputBlueData = new int[xDim * yDim];
|
||||
|
||||
outputRef[0] = outputRedData;
|
||||
outputRef[1] = outputGreenData;
|
||||
outputRef[2] = outputBlueData;
|
||||
}
|
||||
|
||||
scanNum++;
|
||||
|
||||
while (true) { // Decode one scan
|
||||
int temp[] = new int[1]; // to store remainder bits
|
||||
int index[] = new int[1];
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
pred[i] = (1 << (precision - 1));
|
||||
}
|
||||
|
||||
if (restartInterval == 0) {
|
||||
current = decode(pred, temp, index);
|
||||
|
||||
while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) {
|
||||
output(pred);
|
||||
current = decode(pred, temp, index);
|
||||
}
|
||||
|
||||
break; //current=MARKER
|
||||
}
|
||||
|
||||
for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) {
|
||||
restarting = (mcuNum == 0);
|
||||
current = decode(pred, temp, index);
|
||||
output(pred);
|
||||
|
||||
if (current != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current == 0) {
|
||||
if (markerIndex != 0) {
|
||||
current = (0xFF00 | marker);
|
||||
markerIndex = 0;
|
||||
}
|
||||
else {
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
}
|
||||
|
||||
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
|
||||
break; //current=MARKER
|
||||
}
|
||||
}
|
||||
|
||||
if ((current == JPEG.DNL) && (scanNum == 1)) { //DNL
|
||||
readNumber();
|
||||
current = input.readUnsignedShort();
|
||||
}
|
||||
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
|
||||
|
||||
return outputRef;
|
||||
}
|
||||
|
||||
private void processWarningOccured(String warning) {
|
||||
listenerDelegate.processWarningOccurred(warning);
|
||||
}
|
||||
|
||||
private boolean useACForDC(final int dcTabSel) {
|
||||
if (isLossless()) {
|
||||
for (HuffmanTable huffTable : huffTables) {
|
||||
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isLossless() {
|
||||
switch (frame.marker) {
|
||||
case JPEG.SOF3:
|
||||
case JPEG.SOF7:
|
||||
case JPEG.SOF11:
|
||||
case JPEG.SOF15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
|
||||
for (Frame.Component component : components) {
|
||||
if (component.id == sel) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("No such component id: " + sel);
|
||||
}
|
||||
|
||||
private Scan readScan() throws IOException {
|
||||
int length = input.readUnsignedShort();
|
||||
return Scan.read(input, length);
|
||||
}
|
||||
|
||||
private int decode(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
if (numComp == 1) {
|
||||
return decodeSingle(prev, temp, index);
|
||||
}
|
||||
else if (numComp == 3) {
|
||||
return decodeRGB(prev, temp, index);
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
// At the beginning of the first line and
|
||||
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
|
||||
if (restarting) {
|
||||
restarting = false;
|
||||
prev[0] = (1 << (frame.samplePrecision - 1));
|
||||
}
|
||||
else {
|
||||
switch (selection) {
|
||||
case 2:
|
||||
prev[0] = getPreviousY(outputData);
|
||||
break;
|
||||
case 3:
|
||||
prev[0] = getPreviousXY(outputData);
|
||||
break;
|
||||
case 4:
|
||||
prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
|
||||
break;
|
||||
case 5:
|
||||
prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
|
||||
break;
|
||||
case 6:
|
||||
prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
|
||||
break;
|
||||
case 7:
|
||||
prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
|
||||
break;
|
||||
default:
|
||||
prev[0] = getPreviousX(outputData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < nBlock[0]; i++) {
|
||||
int value = getHuffmanValue(dcTab[0], temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int n = getn(prev, value, temp, index);
|
||||
|
||||
int nRestart = (n >> 8);
|
||||
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
|
||||
return nRestart;
|
||||
}
|
||||
|
||||
prev[0] += n;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
|
||||
switch (selection) {
|
||||
case 2:
|
||||
prev[0] = getPreviousY(outputRedData);
|
||||
prev[1] = getPreviousY(outputGreenData);
|
||||
prev[2] = getPreviousY(outputBlueData);
|
||||
break;
|
||||
case 3:
|
||||
prev[0] = getPreviousXY(outputRedData);
|
||||
prev[1] = getPreviousXY(outputGreenData);
|
||||
prev[2] = getPreviousXY(outputBlueData);
|
||||
break;
|
||||
case 4:
|
||||
prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData);
|
||||
prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData);
|
||||
prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData);
|
||||
break;
|
||||
case 5:
|
||||
prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1);
|
||||
prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
|
||||
prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
|
||||
break;
|
||||
case 6:
|
||||
prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1);
|
||||
prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
|
||||
prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
|
||||
break;
|
||||
case 7:
|
||||
prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2);
|
||||
prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2);
|
||||
prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2);
|
||||
break;
|
||||
default:
|
||||
prev[0] = getPreviousX(outputRedData);
|
||||
prev[1] = getPreviousX(outputGreenData);
|
||||
prev[2] = getPreviousX(outputBlueData);
|
||||
break;
|
||||
}
|
||||
|
||||
int value, actab[], dctab[];
|
||||
int qtab[];
|
||||
|
||||
for (int ctrC = 0; ctrC < numComp; ctrC++) {
|
||||
qtab = qTab[ctrC];
|
||||
actab = acTab[ctrC];
|
||||
dctab = dcTab[ctrC];
|
||||
for (int i = 0; i < nBlock[ctrC]; i++) {
|
||||
for (int k = 0; k < IDCT_Source.length; k++) {
|
||||
IDCT_Source[k] = 0;
|
||||
}
|
||||
|
||||
value = getHuffmanValue(dctab, temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index);
|
||||
IDCT_Source[0] *= qtab[0];
|
||||
|
||||
for (int j = 1; j < 64; j++) {
|
||||
value = getHuffmanValue(actab, temp, index);
|
||||
|
||||
if (value >= 0xFF00) {
|
||||
return value;
|
||||
}
|
||||
|
||||
j += (value >> 4);
|
||||
|
||||
if ((value & 0x0F) == 0) {
|
||||
if ((value >> 4) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
|
||||
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
|
||||
// HuffTab[*][*][0-256] is always the only 1st-layer table.
|
||||
//
|
||||
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
|
||||
//
|
||||
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||
// ):
|
||||
// return: Huffman Value of table
|
||||
// 0xFF?? if it receives a MARKER
|
||||
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
|
||||
// temp temp storage for remainded bits
|
||||
// index index to bit of temp
|
||||
// in FILE pointer
|
||||
// Effect:
|
||||
// temp store new remainded bits
|
||||
// index change to new index
|
||||
// in change to new position
|
||||
// NOTE:
|
||||
// Initial by temp=0; index=0;
|
||||
// NOTE: (explain temp and index)
|
||||
// temp: is always in the form at calling time or returning time
|
||||
// | byte 4 | byte 3 | byte 2 | byte 1 |
|
||||
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
|
||||
// ^index=3 (from 0 to 15)
|
||||
// 321
|
||||
// NOTE (marker and marker_index):
|
||||
// If get a MARKER from 'in', marker=the low-byte of the MARKER
|
||||
// and marker_index=9
|
||||
// If marker_index=9 then index is always > 8, or HuffmanValue()
|
||||
// will not be called
|
||||
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException {
|
||||
int code, input;
|
||||
final int mask = 0xFFFF;
|
||||
|
||||
if (index[0] < 8) {
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
temp[0] |= input;
|
||||
}
|
||||
else {
|
||||
index[0] -= 8;
|
||||
}
|
||||
|
||||
code = table[temp[0] >> index[0]];
|
||||
|
||||
if ((code & MSB) != 0) {
|
||||
if (markerIndex != 0) {
|
||||
markerIndex = 0;
|
||||
return 0xFF00 | marker;
|
||||
}
|
||||
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])];
|
||||
index[0] += 8;
|
||||
}
|
||||
|
||||
index[0] += 8 - (code >> 8);
|
||||
|
||||
if (index[0] < 0) {
|
||||
throw new IIOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()");
|
||||
}
|
||||
|
||||
if (index[0] < markerIndex) {
|
||||
markerIndex = 0;
|
||||
return 0xFF00 | marker;
|
||||
}
|
||||
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
return code & 0xFF;
|
||||
}
|
||||
|
||||
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException {
|
||||
int result;
|
||||
final int one = 1;
|
||||
final int n_one = -1;
|
||||
final int mask = 0xFFFF;
|
||||
int input;
|
||||
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n == 16) {
|
||||
if (pred[0] >= 0) {
|
||||
return -32768;
|
||||
}
|
||||
else {
|
||||
return 32768;
|
||||
}
|
||||
}
|
||||
|
||||
index[0] -= n;
|
||||
|
||||
if (index[0] >= 0) {
|
||||
if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
result = temp[0] >> index[0];
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
}
|
||||
else {
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
index[0] += 8;
|
||||
|
||||
if (index[0] < 0) {
|
||||
if (markerIndex != 0) {
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
temp[0] <<= 8;
|
||||
input = this.input.readUnsignedByte();
|
||||
|
||||
if (input == 0xFF) {
|
||||
marker = this.input.readUnsignedByte();
|
||||
if (marker != 0) {
|
||||
markerIndex = 9;
|
||||
}
|
||||
}
|
||||
|
||||
temp[0] |= input;
|
||||
index[0] += 8;
|
||||
}
|
||||
|
||||
if (index[0] < 0) {
|
||||
throw new IOException("index=" + index[0] + " in getn()");
|
||||
}
|
||||
|
||||
if (index[0] < markerIndex) {
|
||||
markerIndex = 0;
|
||||
return (0xFF00 | marker) << 8;
|
||||
}
|
||||
|
||||
result = temp[0] >> index[0];
|
||||
temp[0] &= (mask >> (16 - index[0]));
|
||||
}
|
||||
|
||||
if (result < (one << (n - 1))) {
|
||||
result += (n_one << n) + 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getPreviousX(final int data[]) {
|
||||
if (xLoc > 0) {
|
||||
return data[((yLoc * xDim) + xLoc) - 1];
|
||||
}
|
||||
else if (yLoc > 0) {
|
||||
return getPreviousY(data);
|
||||
}
|
||||
else {
|
||||
return (1 << (frame.samplePrecision - 1));
|
||||
}
|
||||
}
|
||||
|
||||
private int getPreviousXY(final int data[]) {
|
||||
if ((xLoc > 0) && (yLoc > 0)) {
|
||||
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
|
||||
}
|
||||
else {
|
||||
return getPreviousY(data);
|
||||
}
|
||||
}
|
||||
|
||||
private int getPreviousY(final int data[]) {
|
||||
if (yLoc > 0) {
|
||||
return data[((yLoc - 1) * xDim) + xLoc];
|
||||
}
|
||||
else {
|
||||
return getPreviousX(data);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLastPixel() {
|
||||
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
|
||||
}
|
||||
|
||||
private void output(final int pred[]) {
|
||||
if (numComp == 1) {
|
||||
outputSingle(pred);
|
||||
}
|
||||
else {
|
||||
outputRGB(pred);
|
||||
}
|
||||
}
|
||||
|
||||
private void outputSingle(final int pred[]) {
|
||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||
outputData[(yLoc * xDim) + xLoc] = mask & pred[0];
|
||||
xLoc++;
|
||||
|
||||
if (xLoc >= xDim) {
|
||||
yLoc++;
|
||||
xLoc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void outputRGB(final int pred[]) {
|
||||
if ((xLoc < xDim) && (yLoc < yDim)) {
|
||||
outputRedData[(yLoc * xDim) + xLoc] = pred[0];
|
||||
outputGreenData[(yLoc * xDim) + xLoc] = pred[1];
|
||||
outputBlueData[(yLoc * xDim) + xLoc] = pred[2];
|
||||
xLoc++;
|
||||
|
||||
if (xLoc >= xDim) {
|
||||
yLoc++;
|
||||
xLoc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int readNumber() throws IOException {
|
||||
final int Ld = input.readUnsignedShort();
|
||||
|
||||
if (Ld != 4) {
|
||||
throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]");
|
||||
}
|
||||
|
||||
return input.readUnsignedShort();
|
||||
}
|
||||
|
||||
int getNumComponents() {
|
||||
return numComp;
|
||||
}
|
||||
|
||||
int getPrecision() {
|
||||
return frame.samplePrecision;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user