mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c9d2c700e | |||
| 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 |
@@ -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.1.2](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.1.2%22) is released (Aug. 14th, 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
|
||||
@@ -96,9 +100,9 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
|
||||
* 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,33 @@ 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 (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)
|
||||
#### IFF - Commodore Amiga/Electronic Arts Interchange File Format
|
||||
|
||||
* Legacy format, allows reading popular image from the Commodore Amiga computer.
|
||||
* 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 +153,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 +165,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 +176,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 +186,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,32 +199,33 @@ 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
|
||||
|
||||
@@ -419,9 +427,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 +467,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.1.2</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.1.2</version> <!-- Alternatively, build your own version -->
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -472,25 +480,65 @@ 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.1.2.jar
|
||||
twelvemonkeys-common-io-3.1.2.jar
|
||||
twelvemonkeys-common-image-3.1.2.jar
|
||||
twelvemonkeys-imageio-core-3.1.2.jar
|
||||
twelvemonkeys-imageio-metadata-3.1.2.jar
|
||||
twelvemonkeys-imageio-jpeg-3.1.2.jar
|
||||
twelvemonkeys-imageio-tiff-3.1.2.jar
|
||||
|
||||
### Links to prebuilt binaries
|
||||
|
||||
##### Latest version (3.1.x)
|
||||
|
||||
Requires Java 7 or later.
|
||||
|
||||
Common dependencies
|
||||
* [common-lang-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.1.2/common-lang-3.1.2.jar)
|
||||
* [common-io-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.1.2/common-io-3.1.2.jar)
|
||||
* [common-image-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.1.2/common-image-3.1.2.jar)
|
||||
|
||||
ImageIO dependencies
|
||||
* [imageio-core-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.1.2/imageio-core-3.1.2.jar)
|
||||
* [imageio-metadata-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.1.2/imageio-metadata-3.1.2.jar)
|
||||
|
||||
ImageIO plugins
|
||||
* [imageio-bmp-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.1.2/imageio-bmp-3.1.2.jar)
|
||||
* [imageio-jpeg-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.1.2/imageio-jpeg-3.1.2.jar)
|
||||
* [imageio-tiff-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.1.2/imageio-tiff-3.1.2.jar)
|
||||
* [imageio-pnm-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.1.2/imageio-pnm-3.1.2.jar)
|
||||
* [imageio-psd-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.1.2/imageio-psd-3.1.2.jar)
|
||||
* [imageio-iff-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.1.2/imageio-iff-3.1.2.jar)
|
||||
* [imageio-pcx-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.1.2/imageio-pcx-3.1.2.jar)
|
||||
* [imageio-pict-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.1.2/imageio-pict-3.1.2.jar)
|
||||
* [imageio-sgi-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.1.2/imageio-sgi-3.1.2.jar)
|
||||
* [imageio-tga-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.1.2/imageio-tga-3.1.2.jar)
|
||||
* [imageio-icns-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.1.2/imageio-icns-3.1.2.jar)
|
||||
* [imageio-thumbsdb-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.1.2/imageio-thumbsdb-3.1.2.jar)
|
||||
|
||||
ImageIO plugins requiring 3rd party libs
|
||||
* [imageio-batik-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.1.2/imageio-batik-3.1.2.jar)
|
||||
|
||||
Photoshop Path support for ImageIO
|
||||
* [imageio-clippath-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.1.2/imageio-clippath-3.1.2.jar)
|
||||
|
||||
Servlet support
|
||||
* [servlet-3.1.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.1.2/servlet-3.1.2.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 +548,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.3-SNAPSHOT</version>
|
||||
<version>3.2</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.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -23,7 +23,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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>
|
||||
|
||||
+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");
|
||||
|
||||
-2
@@ -321,8 +321,6 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
descriptors.put(pEntry, descriptor);
|
||||
}
|
||||
|
||||
System.err.println("descriptor: " + descriptor);
|
||||
|
||||
return descriptor.getImage();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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.3-SNAPSHOT</version>
|
||||
<version>3.2</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);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
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, final int offset) {
|
||||
double y = (yCbCr[offset] & 0xff);
|
||||
double cb = (yCbCr[offset + 1] & 0xff) - 128;
|
||||
double cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||
int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
|
||||
|
||||
rgb[offset] = clamp(red);
|
||||
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));
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
+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.2</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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</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>
|
||||
|
||||
+6
-9
@@ -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;
|
||||
@@ -102,7 +103,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
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");
|
||||
|
||||
+35
-139
@@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
@@ -267,10 +268,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
@Override
|
||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
// If delegate can determine the spec, we'll just go with that
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||
try {
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||
|
||||
if (rawType != null) {
|
||||
return rawType;
|
||||
if (rawType != null) {
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ignore) {
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// Otherwise, consult the image metadata
|
||||
@@ -312,22 +318,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
assertInput();
|
||||
checkBounds(imageIndex);
|
||||
|
||||
// CompoundDirectory exif = getExif();
|
||||
// if (exif != null) {
|
||||
// System.err.println("exif: " + exif);
|
||||
// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
|
||||
// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
|
||||
//
|
||||
// if (exifIFDEntry != null) {
|
||||
// Directory exifIFD = (Directory) exifIFDEntry.getValue();
|
||||
// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
|
||||
// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
|
||||
// }
|
||||
// }
|
||||
|
||||
SOFSegment sof = getSOF();
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||
boolean bogusAdobeDCT = false;
|
||||
|
||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
|
||||
@@ -338,6 +332,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
sof.marker & 0xf, sof.componentsInFrame()
|
||||
));
|
||||
|
||||
bogusAdobeDCT = true;
|
||||
adobeDCT = null;
|
||||
}
|
||||
|
||||
@@ -346,11 +341,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
if (delegate.canReadRaster() && (
|
||||
bogusAdobeDCT ||
|
||||
sourceCSType == JPEGColorSpace.CMYK ||
|
||||
sourceCSType == JPEGColorSpace.YCCK ||
|
||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile)) ||
|
||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null) { // TODO: Issue warning?
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
||||
if (DEBUG) {
|
||||
System.out.println("Reading using raster and extra conversion");
|
||||
System.out.println("ICC color profile: " + profile);
|
||||
@@ -468,14 +463,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
// Apply source color conversion from implicit color space
|
||||
if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.YCbCrA) {
|
||||
YCbCrConverter.convertYCbCr2RGB(raster);
|
||||
convertYCbCr2RGB(raster);
|
||||
}
|
||||
else if (csType == JPEGColorSpace.YCCK) {
|
||||
// TODO: Need to rethink this (non-) inversion, see #147
|
||||
// TODO: Allow param to specify inversion, or possibly the PDF decode array
|
||||
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
||||
boolean invert = true;// || (adobeDCT.flags0 & 0x8000) == 0;
|
||||
YCbCrConverter.convertYCCK2CMYK(raster, invert);
|
||||
convertYCCK2CMYK(raster);
|
||||
}
|
||||
else if (csType == JPEGColorSpace.CMYK) {
|
||||
invertCMYK(raster);
|
||||
@@ -1113,130 +1107,32 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inner class for lazy-loading of conversion tables.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author Original code by Werner Randelshofer
|
||||
*/
|
||||
static final class YCbCrConverter {
|
||||
/** Define tables for YCC->RGB color space conversion. */
|
||||
private final static int SCALEBITS = 16;
|
||||
private final static int MAXJSAMPLE = 255;
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
public static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
YCbCrConverter.convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
public static void convertYCCK2CMYK(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int offset = (x + y * width) * 4;
|
||||
// YCC -> CMY
|
||||
YCbCrConverter.convertYCbCr2RGB(data, data, offset);
|
||||
// Inverse K
|
||||
data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
static void convertYCCK2CMYK(final Raster raster, final boolean invert) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
if (invert) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCCK2CMYKInverted(data, data, (x + y * width) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertYCCK2CMYKInverted(byte[] ycck, byte[] cmyk, int offset) {
|
||||
// Inverted
|
||||
int y = 255 - ycck[offset ] & 0xff;
|
||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||
int cr = 255 - ycck[offset + 2] & 0xff;
|
||||
int k = 255 - ycck[offset + 3] & 0xff;
|
||||
|
||||
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||
|
||||
cmyk[offset ] = clamp(cmykC);
|
||||
cmyk[offset + 1] = clamp(cmykM);
|
||||
cmyk[offset + 2] = clamp(cmykY);
|
||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||
}
|
||||
|
||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||
int y = ycck[offset ] & 0xff;
|
||||
int cb = ycck[offset + 1] & 0xff;
|
||||
int cr = ycck[offset + 2] & 0xff;
|
||||
int k = ycck[offset + 3] & 0xff;
|
||||
|
||||
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||
|
||||
cmyk[offset ] = clamp(cmykC);
|
||||
cmyk[offset + 1] = clamp(cmykM);
|
||||
cmyk[offset + 2] = clamp(cmykY);
|
||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
||||
|
||||
+36
@@ -1491,4 +1491,40 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRawImageTypeAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||
|
||||
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||
assertNull(rawType); // But no exception, please...
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||
|
||||
assertEquals(310, reader.getWidth(0));
|
||||
assertEquals(384, reader.getHeight(0));
|
||||
|
||||
BufferedImage image = reader.read(0, null);
|
||||
assertNotNull(image);
|
||||
assertEquals(310, image.getWidth());
|
||||
assertEquals(384, image.getHeight());
|
||||
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -64,4 +64,8 @@ public interface Entry {
|
||||
|
||||
// For arrays only
|
||||
int valueCount();
|
||||
|
||||
// TODO: getValueAsInt, UnsignedInt, Short, UnsignedShort, Byte, UnsignedByte etc
|
||||
// TODO: getValueAsIntArray, ShortArray, ByteArray, StringArray etc (also for non-arrays, to return a single element array)
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MetadataWriter.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: MetadataWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
|
||||
*/
|
||||
public abstract class MetadataWriter {
|
||||
abstract public boolean write(Directory directory, ImageOutputStream stream) throws IOException;
|
||||
}
|
||||
+35
-12
@@ -38,13 +38,14 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||
*/
|
||||
final class EXIFEntry extends AbstractEntry {
|
||||
// TODO: Expose as TIFFEntry
|
||||
final private short type;
|
||||
|
||||
EXIFEntry(final int identifier, final Object value, final short type) {
|
||||
super(identifier, value);
|
||||
|
||||
if (type < 1 || type > TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type));
|
||||
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal TIFF type: %s", type));
|
||||
}
|
||||
|
||||
// TODO: Validate that type is applicable to value?
|
||||
@@ -71,6 +72,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "IPTC";
|
||||
case TIFF.TAG_PHOTOSHOP:
|
||||
return "Adobe";
|
||||
case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
|
||||
return "ImageSourceData";
|
||||
case TIFF.TAG_ICC_PROFILE:
|
||||
return "ICCProfile";
|
||||
|
||||
@@ -84,8 +87,16 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Compression";
|
||||
case TIFF.TAG_PHOTOMETRIC_INTERPRETATION:
|
||||
return "PhotometricInterpretation";
|
||||
case TIFF.TAG_FILL_ORDER:
|
||||
return "FillOrder";
|
||||
case TIFF.TAG_DOCUMENT_NAME:
|
||||
return "DocumentName";
|
||||
case TIFF.TAG_IMAGE_DESCRIPTION:
|
||||
return "ImageDescription";
|
||||
case TIFF.TAG_MAKE:
|
||||
return "Make";
|
||||
case TIFF.TAG_MODEL:
|
||||
return "Model";
|
||||
case TIFF.TAG_STRIP_OFFSETS:
|
||||
return "StripOffsets";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
@@ -104,14 +115,10 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "PlanarConfiguration";
|
||||
case TIFF.TAG_RESOLUTION_UNIT:
|
||||
return "ResolutionUnit";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||
return "JPEGInterchangeFormat";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||
return "JPEGInterchangeFormatLength";
|
||||
case TIFF.TAG_MAKE:
|
||||
return "Make";
|
||||
case TIFF.TAG_MODEL:
|
||||
return "Model";
|
||||
case TIFF.TAG_PAGE_NAME:
|
||||
return "PageName";
|
||||
case TIFF.TAG_PAGE_NUMBER:
|
||||
return "PageNumber";
|
||||
case TIFF.TAG_SOFTWARE:
|
||||
return "Software";
|
||||
case TIFF.TAG_DATE_TIME:
|
||||
@@ -138,10 +145,20 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "YCbCrPositioning";
|
||||
case TIFF.TAG_COLOR_MAP:
|
||||
return "ColorMap";
|
||||
case TIFF.TAG_INK_SET:
|
||||
return "InkSet";
|
||||
case TIFF.TAG_INK_NAMES:
|
||||
return "InkNames";
|
||||
case TIFF.TAG_EXTRA_SAMPLES:
|
||||
return "ExtraSamples";
|
||||
case TIFF.TAG_SAMPLE_FORMAT:
|
||||
return "SampleFormat";
|
||||
case TIFF.TAG_JPEG_TABLES:
|
||||
return "JPEGTables";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||
return "JPEGInterchangeFormat";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||
return "JPEGInterchangeFormatLength";
|
||||
|
||||
case TIFF.TAG_SUB_IFD:
|
||||
return "SubIFD";
|
||||
@@ -176,6 +193,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Flash";
|
||||
case EXIF.TAG_FOCAL_LENGTH:
|
||||
return "FocalLength";
|
||||
case EXIF.TAG_SENSING_METHOD:
|
||||
return "SensingMethod";
|
||||
case EXIF.TAG_FILE_SOURCE:
|
||||
return "FileSource";
|
||||
case EXIF.TAG_SCENE_TYPE:
|
||||
@@ -189,7 +208,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
case EXIF.TAG_WHITE_BALANCE:
|
||||
return "WhiteBalance";
|
||||
case EXIF.TAG_DIGITAL_ZOOM_RATIO:
|
||||
return "DigitalZoomRation";
|
||||
return "DigitalZoomRatio";
|
||||
case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM:
|
||||
return "FocalLengthIn35mmFilm";
|
||||
case EXIF.TAG_SCENE_CAPTURE_TYPE:
|
||||
@@ -202,6 +221,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Saturation";
|
||||
case EXIF.TAG_SHARPNESS:
|
||||
return "Sharpness";
|
||||
case EXIF.TAG_IMAGE_UNIQUE_ID:
|
||||
return "ImageUniqueID";
|
||||
|
||||
case EXIF.TAG_FLASHPIX_VERSION:
|
||||
return "FlashpixVersion";
|
||||
@@ -214,6 +235,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "DateTimeDigitized";
|
||||
case EXIF.TAG_IMAGE_NUMBER:
|
||||
return "ImageNumber";
|
||||
case EXIF.TAG_MAKER_NOTE:
|
||||
return "MakerNote";
|
||||
case EXIF.TAG_USER_COMMENT:
|
||||
return "UserComment";
|
||||
|
||||
@@ -259,6 +282,6 @@ final class EXIFEntry extends AbstractEntry {
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type - 1];
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -89,8 +89,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
|
||||
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
|
||||
List<IFD> ifds = new ArrayList<IFD>();
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
List<IFD> ifds = new ArrayList<>();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
long nextOffset = -1;
|
||||
@@ -156,7 +156,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
try {
|
||||
if (KNOWN_IFDS.contains(tagId)) {
|
||||
long[] pointerOffsets = getPointerOffsets(entry);
|
||||
List<IFD> subIFDs = new ArrayList<IFD>(pointerOffsets.length);
|
||||
List<IFD> subIFDs = new ArrayList<>(pointerOffsets.length);
|
||||
|
||||
for (long pointerOffset : pointerOffsets) {
|
||||
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
||||
@@ -177,8 +177,11 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
e.printStackTrace();
|
||||
if (DEBUG) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
System.err.println("Error parsing sub-IFD: " + tagId);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,8 +449,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
static int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
|
||||
if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType] * pCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
|
||||
+78
-16
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@@ -39,6 +40,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -48,7 +50,7 @@ import java.util.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
|
||||
*/
|
||||
public class EXIFWriter {
|
||||
public final class EXIFWriter extends MetadataWriter {
|
||||
|
||||
static final int WORD_LENGTH = 2;
|
||||
static final int LONGWORD_LENGTH = 4;
|
||||
@@ -58,6 +60,7 @@ public class EXIFWriter {
|
||||
return write(new IFD(entries), stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(directory);
|
||||
Validate.notNull(stream);
|
||||
@@ -92,11 +95,11 @@ public class EXIFWriter {
|
||||
stream.writeShort(42);
|
||||
}
|
||||
|
||||
public long writeIFD(final Collection<Entry> entries, ImageOutputStream stream) throws IOException {
|
||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return writeIFD(new IFD(entries), stream, false);
|
||||
}
|
||||
|
||||
private long writeIFD(final Directory original, ImageOutputStream stream, boolean isSubIFD) throws IOException {
|
||||
private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException {
|
||||
// TIFF spec says tags should be in increasing order, enforce that when writing
|
||||
Directory ordered = ensureOrderedDirectory(original);
|
||||
|
||||
@@ -181,7 +184,7 @@ public class EXIFWriter {
|
||||
|
||||
private Directory ensureOrderedDirectory(final Directory directory) {
|
||||
if (!isSorted(directory)) {
|
||||
List<Entry> entries = new ArrayList<Entry>(directory.size());
|
||||
List<Entry> entries = new ArrayList<>(directory.size());
|
||||
|
||||
for (Entry entry : directory) {
|
||||
entries.add(entry);
|
||||
@@ -215,7 +218,7 @@ public class EXIFWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
private long writeValue(Entry entry, long dataOffset, ImageOutputStream stream) throws IOException {
|
||||
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
|
||||
short type = getType(entry);
|
||||
int valueLength = EXIFReader.getValueLength(type, getCount(entry));
|
||||
|
||||
@@ -236,18 +239,22 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private int getCount(Entry entry) {
|
||||
private int getCount(final Entry entry) {
|
||||
Object value = entry.getValue();
|
||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
||||
}
|
||||
|
||||
private void writeValueInline(Object value, short type, ImageOutputStream stream) throws IOException {
|
||||
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
if (value.getClass().isArray()) {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
stream.write((byte[]) value);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
short[] shorts;
|
||||
|
||||
if (value instanceof short[]) {
|
||||
@@ -276,7 +283,9 @@ public class EXIFWriter {
|
||||
|
||||
stream.writeShorts(shorts, 0, shorts.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
int[] ints;
|
||||
|
||||
if (value instanceof int[]) {
|
||||
@@ -291,21 +300,49 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeInts(ints, 0, ints.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational[] rationals = (Rational[]) value;
|
||||
for (Rational rational : rationals) {
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_FLOAT:
|
||||
float[] floats;
|
||||
|
||||
if (value instanceof float[]) {
|
||||
floats = (float[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeFloats(floats, 0, floats.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
double[] doubles;
|
||||
|
||||
if (value instanceof double[]) {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeDoubles(doubles, 0, doubles.length);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -317,25 +354,35 @@ public class EXIFWriter {
|
||||
else {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_BYTE:
|
||||
stream.writeByte((Integer) value);
|
||||
case TIFF.TYPE_SBYTE:
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
stream.writeByte(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_ASCII:
|
||||
byte[] bytes = ((String) value).getBytes(Charset.forName("UTF-8"));
|
||||
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.write(bytes);
|
||||
stream.write(0);
|
||||
break;
|
||||
case TIFF.TYPE_SHORT:
|
||||
stream.writeShort((Integer) value);
|
||||
case TIFF.TYPE_SSHORT:
|
||||
stream.writeShort(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational rational = (Rational) value;
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
break;
|
||||
// TODO: More types
|
||||
case TIFF.TYPE_FLOAT:
|
||||
stream.writeFloat(((Number) value).floatValue());
|
||||
break;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -343,7 +390,7 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValueAt(long dataOffset, Object value, short type, ImageOutputStream stream) throws IOException {
|
||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
@@ -351,12 +398,27 @@ public class EXIFWriter {
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private short getType(Entry entry) {
|
||||
private short getType(final Entry entry) {
|
||||
// TODO: What a MESS! Rewrite and expose EXIFEntry as TIFFEntry or so...
|
||||
|
||||
// For internal entries use type directly
|
||||
if (entry instanceof EXIFEntry) {
|
||||
EXIFEntry exifEntry = (EXIFEntry) entry;
|
||||
return exifEntry.getType();
|
||||
}
|
||||
|
||||
// For other entries, use name if it matches
|
||||
String typeName = entry.getTypeName();
|
||||
|
||||
if (typeName != null) {
|
||||
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||
return (short) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the native Java type
|
||||
Object value = Validate.notNull(entry.getValue());
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
|
||||
+23
@@ -88,13 +88,16 @@ public interface TIFF {
|
||||
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
null,
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
"IFD",
|
||||
null, null,
|
||||
"LONG8", "SLONG8", "IFD8"
|
||||
};
|
||||
/** Length of the corresponding type, in bytes. */
|
||||
int[] TYPE_LENGTHS = {
|
||||
-1,
|
||||
1, 1, 2, 4, 8,
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
4,
|
||||
@@ -124,6 +127,8 @@ public interface TIFF {
|
||||
int TAG_YCBCR_POSITIONING = 531;
|
||||
int TAG_X_RESOLUTION = 282;
|
||||
int TAG_Y_RESOLUTION = 283;
|
||||
int TAG_X_POSITION = 286;
|
||||
int TAG_Y_POSITION = 287;
|
||||
int TAG_RESOLUTION_UNIT = 296;
|
||||
|
||||
/// B. Tags relating to recording offset
|
||||
@@ -131,9 +136,13 @@ public interface TIFF {
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
|
||||
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
int TAG_GROUP3OPTIONS = 292;
|
||||
int TAG_GROUP4OPTIONS = 293;
|
||||
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
@@ -153,9 +162,11 @@ public interface TIFF {
|
||||
/// D. Other tags
|
||||
|
||||
int TAG_DATE_TIME = 306;
|
||||
int TAG_DOCUMENT_NAME = 269;
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_PAGE_NAME = 285;
|
||||
int TAG_PAGE_NUMBER = 297;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
@@ -184,6 +195,18 @@ public interface TIFF {
|
||||
*/
|
||||
int TAG_PHOTOSHOP = 34377;
|
||||
|
||||
/**
|
||||
* Photoshop layer and mask information (byte order follows TIFF container).
|
||||
* Layer and mask information found in a typical layered Photoshop file.
|
||||
* Starts with a character string of "Adobe Photoshop Document Data Block"
|
||||
* (or "Adobe Photoshop Document Data V0002" for PSB)
|
||||
* including the null termination character.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
|
||||
|
||||
int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
|
||||
|
||||
/**
|
||||
* ICC Color Profile.
|
||||
* @see java.awt.color.ICC_Profile
|
||||
|
||||
+77
-53
@@ -36,110 +36,115 @@ package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
* @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$
|
||||
*/
|
||||
public interface IPTC {
|
||||
static final int ENVELOPE_RECORD = 1 << 8;
|
||||
static final int APPLICATION_RECORD = 2 << 8;
|
||||
int ENVELOPE_RECORD = 1 << 8;
|
||||
int APPLICATION_RECORD = 2 << 8;
|
||||
|
||||
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
||||
/** 1:05: Destination */
|
||||
int TAG_DESTINATION = ENVELOPE_RECORD | 5;
|
||||
/** 1:50: Product ID */
|
||||
int TAG_PRODUCT_ID = ENVELOPE_RECORD | 50;
|
||||
/** 1:90: Coded Character Set */
|
||||
int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
||||
|
||||
/** 2:00 Record Version (mandatory) */
|
||||
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
|
||||
/** 2:03 Object Type Reference */
|
||||
public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
|
||||
int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
|
||||
/** 2:04 Object Attribute Reference (repeatable) */
|
||||
public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
/** 2:05 Object Name */
|
||||
public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
||||
int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
||||
/** 2:07 Edit Status */
|
||||
public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
/** 2:08 Editorial Update */
|
||||
public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
/** 2:10 Urgency */
|
||||
public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
/** 2:12 Subect Reference (repeatable) */
|
||||
public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
||||
int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
||||
/** 2:15 Category */
|
||||
public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
/** 2:20 Supplemental Category (repeatable) */
|
||||
public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
||||
int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
||||
/** 2:22 Fixture Identifier */
|
||||
public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
/** 2:25 Keywords (repeatable) */
|
||||
public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
||||
int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
||||
/** 2:26 Content Locataion Code (repeatable) */
|
||||
public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
|
||||
int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
|
||||
/** 2:27 Content Locataion Name (repeatable) */
|
||||
public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
/** 2:30 Release Date */
|
||||
public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
||||
int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
||||
/** 2:35 Release Time */
|
||||
public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
/** 2:37 Expiration Date */
|
||||
public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
||||
int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
||||
/** 2:38 Expiration Time */
|
||||
public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
/** 2:40 Special Instructions */
|
||||
public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
|
||||
int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
|
||||
/** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
|
||||
public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
|
||||
int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
|
||||
/** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
|
||||
public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
|
||||
int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
|
||||
/** 2:47 Reference Date (mandatory if 2:45 present) */
|
||||
public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
|
||||
int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
|
||||
/** 2:50 Reference Number (mandatory if 2:45 present) */
|
||||
public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
/** 2:55 Date Created */
|
||||
public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
||||
int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
||||
/** 2:60 Time Created */
|
||||
public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
/** 2:62 Digital Creation Date */
|
||||
public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
||||
int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
||||
/** 2:63 Digital Creation Date */
|
||||
public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
/** 2:65 Originating Program */
|
||||
public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
|
||||
int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
|
||||
/** 2:70 Program Version (only valid if 2:65 present) */
|
||||
public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
|
||||
int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
|
||||
/** 2:75 Object Cycle (a: morning, p: evening, b: both) */
|
||||
public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
/** 2:80 By-line (repeatable) */
|
||||
public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
|
||||
int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
|
||||
/** 2:85 By-line Title (repeatable) */
|
||||
public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
/** 2:90 City */
|
||||
public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
/** 2:92 Sub-location */
|
||||
public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
||||
int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
||||
/** 2:95 Province/State */
|
||||
public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
|
||||
int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
|
||||
/** 2:100 Country/Primary Location Code */
|
||||
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
|
||||
int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
|
||||
/** 2:101 Country/Primary Location Name */
|
||||
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
|
||||
int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
|
||||
/** 2:103 Original Transmission Reference */
|
||||
public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
/** 2:105 Headline */
|
||||
public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
/** 2:110 Credit */
|
||||
public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
/** 2:115 Source */
|
||||
public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
/** 2:116 Copyright Notice */
|
||||
public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
||||
int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
||||
/** 2:118 Contact */
|
||||
public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
/** 2:120 Catption/Abstract */
|
||||
public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
||||
int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
||||
/** 2:122 Writer/Editor (repeatable) */
|
||||
public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
/** 2:125 Rasterized Caption (binary data) */
|
||||
public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
||||
int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
||||
/** 2:130 Image Type */
|
||||
public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
/** 2:131 Image Orientation */
|
||||
public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
||||
int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
||||
/** 2:135 Language Identifier */
|
||||
public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
|
||||
// TODO: 2:150-2:154 Audio
|
||||
|
||||
@@ -150,9 +155,28 @@ public interface IPTC {
|
||||
*
|
||||
* @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
|
||||
*/
|
||||
static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
|
||||
int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
|
||||
|
||||
// TODO: Other custom fields in 155-200 range?
|
||||
|
||||
// TODO: 2:200-2:202 Object Preview Data
|
||||
|
||||
final class Tags {
|
||||
static boolean isArray(final short tagId) {
|
||||
switch (tagId) {
|
||||
case IPTC.TAG_DESTINATION:
|
||||
case IPTC.TAG_PRODUCT_ID:
|
||||
case IPTC.TAG_SUBJECT_REFERENCE:
|
||||
case IPTC.TAG_SUPPLEMENTAL_CATEGORIES:
|
||||
case IPTC.TAG_KEYWORDS:
|
||||
case IPTC.TAG_CONTENT_LOCATION_CODE:
|
||||
case IPTC.TAG_CONTENT_LOCATION_NAME:
|
||||
case IPTC.TAG_BY_LINE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ import java.util.Collection;
|
||||
*/
|
||||
final class IPTCDirectory extends AbstractDirectory {
|
||||
IPTCDirectory(final Collection<? extends Entry> entries) {
|
||||
// TODO: Normalize multiple entries with same key to single entry w/array
|
||||
super(entries);
|
||||
}
|
||||
}
|
||||
+24
@@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
return "RecordVersion";
|
||||
case IPTC.TAG_KEYWORDS:
|
||||
return "Keywords";
|
||||
case IPTC.TAG_SPECIAL_INSTRUCTIONS:
|
||||
return "Instructions";
|
||||
case IPTC.TAG_DIGITAL_CREATION_DATE:
|
||||
return "DigitalCreationDate";
|
||||
case IPTC.TAG_DIGITAL_CREATION_TIME:
|
||||
return "DigitalCreationTime";
|
||||
case IPTC.TAG_DATE_CREATED:
|
||||
return "DateCreated";
|
||||
case IPTC.TAG_TIME_CREATED:
|
||||
return "TimeCreated";
|
||||
case IPTC.TAG_BY_LINE_TITLE:
|
||||
return "ByLineTitle";
|
||||
case IPTC.TAG_CITY:
|
||||
return "City";
|
||||
case IPTC.TAG_SUB_LOCATION:
|
||||
return "SubLocation";
|
||||
case IPTC.TAG_PROVINCE_OR_STATE:
|
||||
return "StateProvince";
|
||||
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE:
|
||||
return "CountryCode";
|
||||
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION:
|
||||
return "Country";
|
||||
case IPTC.TAG_SOURCE:
|
||||
return "Source";
|
||||
case IPTC.TAG_CAPTION:
|
||||
|
||||
+56
-28
@@ -43,8 +43,9 @@ import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IPTCReader
|
||||
@@ -60,61 +61,88 @@ public final class IPTCReader extends MetadataReader {
|
||||
|
||||
private int encoding = ENCODING_UNSPECIFIED;
|
||||
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
Map<Short, Entry> entries = new LinkedHashMap<>();
|
||||
|
||||
// 0x1c identifies start of a tag
|
||||
while (input.read() == 0x1c) {
|
||||
short tagId = input.readShort();
|
||||
int tagByteCount = input.readUnsignedShort();
|
||||
Entry entry = readEntry(input, tagId, tagByteCount);
|
||||
|
||||
boolean array = IPTC.Tags.isArray(tagId);
|
||||
Entry entry = readEntry(input, tagId, tagByteCount, array, array ? entries.get(tagId) : null);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
entries.put(tagId, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCDirectory(entries);
|
||||
return new IPTCDirectory(entries.values());
|
||||
}
|
||||
|
||||
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException {
|
||||
Object value = null;
|
||||
private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) {
|
||||
Object[] oldValue = oldEntry != null ? (Object[]) oldEntry.getValue() : null;
|
||||
Object[] value;
|
||||
|
||||
if (newValue instanceof String) {
|
||||
if (oldValue == null) {
|
||||
value = new String[] {(String) newValue};
|
||||
}
|
||||
else {
|
||||
String[] array = (String[]) oldValue;
|
||||
value = Arrays.copyOf(array, array.length + 1);
|
||||
value[value.length - 1] = newValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (oldValue == null) {
|
||||
value = new Object[] {newValue};
|
||||
}
|
||||
else {
|
||||
value = Arrays.copyOf(oldValue, oldValue.length + 1);
|
||||
value [value .length - 1] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCEntry(tagId, value);
|
||||
}
|
||||
|
||||
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength, final boolean array, final Entry oldEntry) throws IOException {
|
||||
Object value;
|
||||
|
||||
switch (pTagId) {
|
||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||
// TODO: Move somewhere else?
|
||||
encoding = parseEncoding(pInput, pLength);
|
||||
return null;
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
// TODO: Assert length == 2?
|
||||
// A single unsigned short value
|
||||
value = pInput.readUnsignedShort();
|
||||
break;
|
||||
default:
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
|
||||
pInput.skipBytes(pLength);
|
||||
return null;
|
||||
// TODO: Create Tags.getType(tag), to allow for more flexible types
|
||||
if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) {
|
||||
// Treat Application records as Strings
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non-Application fields, typically not human readable
|
||||
byte[] data = new byte[pLength];
|
||||
pInput.readFully(data);
|
||||
value = data;
|
||||
}
|
||||
|
||||
// fall through
|
||||
}
|
||||
|
||||
// If we don't have a value, treat it as a string
|
||||
if (value == null) {
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCEntry(pTagId, value);
|
||||
return array ? mergeEntries(pTagId, value, oldEntry) : new IPTCEntry(pTagId, value);
|
||||
}
|
||||
|
||||
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
|
||||
@@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader {
|
||||
}
|
||||
|
||||
// Fall back to use ISO-8859-1
|
||||
// This will not fail, but may may create wrong fallback-characters
|
||||
// This will not fail, but may create wrong fallback-characters
|
||||
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||
}
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* IPTCWriter.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: IPTCWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class IPTCWriter extends MetadataWriter {
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
notNull(directory, "directory");
|
||||
notNull(stream, "stream");
|
||||
|
||||
// TODO: Make sure we always write application record version (2.00)
|
||||
// TODO: Write encoding UTF8?
|
||||
|
||||
for (Entry entry : directory) {
|
||||
int tag = (Integer) entry.getIdentifier();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (IPTC.Tags.isArray((short) tag)) {
|
||||
Object[] values = (Object[]) value;
|
||||
|
||||
for (Object v : values) {
|
||||
stream.write(0x1c);
|
||||
stream.writeShort(tag);
|
||||
writeValue(stream, v);
|
||||
}
|
||||
}
|
||||
else {
|
||||
stream.write(0x1c);
|
||||
stream.writeShort(tag);
|
||||
writeValue(stream, value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeValue(final ImageOutputStream stream, final Object value) throws IOException {
|
||||
if (value instanceof String) {
|
||||
byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.writeShort(data.length);
|
||||
stream.write(data);
|
||||
}
|
||||
else if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
stream.writeShort(data.length);
|
||||
stream.write(data);
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
// TODO: Need to know types from tag
|
||||
stream.writeShort(2);
|
||||
stream.writeShort((Integer) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null).equals(getIdentifier())) {
|
||||
return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
|
||||
String fieldName = StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
|
||||
return name != null ? fieldName + ": " + name : fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader {
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<PSDEntry> entries = new ArrayList<PSDEntry>();
|
||||
List<PSDEntry> entries = new ArrayList<>();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ public final class XMPReader extends MetadataReader {
|
||||
// TODO: Consider parsing using SAX?
|
||||
// TODO: Determine encoding and parse using a Reader...
|
||||
// TODO: Refactor scanner to return inputstream?
|
||||
// TODO: Be smarter about ASCII-NULL termination/padding (the SAXParser aka Xerces DOMParser doesn't like it)...
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
|
||||
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ public abstract class MetadataReaderAbstractTest {
|
||||
assertNotNull(directory);
|
||||
}
|
||||
|
||||
protected final Matcher<Entry> hasValue(final Object value) {
|
||||
protected static Matcher<Entry> hasValue(final Object value) {
|
||||
return new EntryHasValue(value);
|
||||
}
|
||||
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 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.metadata;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* ReaderAbstractTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ReaderAbstractTest.java,v 1.0 04.01.12 09:40 haraldk Exp$
|
||||
*/
|
||||
public abstract class MetadataWriterAbstractTest {
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
protected final URL getResource(final String name) throws IOException {
|
||||
return getClass().getResource(name);
|
||||
}
|
||||
|
||||
protected final ImageInputStream getDataAsIIS() throws IOException {
|
||||
return ImageIO.createImageInputStream(getData());
|
||||
}
|
||||
|
||||
protected abstract InputStream getData() throws IOException;
|
||||
|
||||
protected abstract MetadataWriter createWriter();
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteNullDirectory() throws IOException {
|
||||
createWriter().write(null, new MemoryCacheImageOutputStream(new ByteArrayOutputStream()));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testWriteNullStream() throws IOException {
|
||||
createWriter().write(new AbstractDirectory(new ArrayList<Entry>()) {
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
+8
-27
@@ -28,24 +28,17 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -61,37 +54,25 @@ import static org.junit.Assert.assertNotNull;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
*/
|
||||
public class EXIFWriterTest {
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
protected final URL getResource(final String name) throws IOException {
|
||||
return getClass().getResource(name);
|
||||
}
|
||||
|
||||
protected final ImageInputStream getDataAsIIS() throws IOException {
|
||||
return ImageIO.createImageInputStream(getData());
|
||||
}
|
||||
|
||||
// @Override
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected EXIFReader createReader() {
|
||||
return new EXIFReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EXIFWriter createWriter() {
|
||||
return new EXIFWriter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteReadSimple() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<Entry>();
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
@@ -139,7 +120,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
public void testWriteMotorola() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<Entry>();
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
@@ -174,7 +155,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
public void testWriteIntel() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<Entry>();
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
@@ -254,7 +235,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSize() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<Entry>();
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
|
||||
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriterAbstractTest;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
|
||||
/**
|
||||
* IPTCWriterTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: IPTCWriterTest.java,v 1.0 05/06/15 harald.kuhr Exp$
|
||||
*/
|
||||
public class IPTCWriterTest extends MetadataWriterAbstractTest {
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
return getResource("/iptc/iptc-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MetadataWriter createWriter() {
|
||||
return new IPTCWriter();
|
||||
}
|
||||
|
||||
private IPTCReader createReader() {
|
||||
return new IPTCReader();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteExisting() throws IOException {
|
||||
IPTCReader reader = createReader();
|
||||
Directory iptc = reader.read(getDataAsIIS());
|
||||
assumeNotNull(iptc);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
|
||||
createWriter().write(iptc, stream);
|
||||
stream.close();
|
||||
|
||||
Directory written = reader.read(new ByteArrayImageInputStream(bytes.toByteArray()));
|
||||
assertEquals(iptc, written);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrite() throws IOException {
|
||||
List<IPTCEntry> entries = new ArrayList<>();
|
||||
entries.add(new IPTCEntry(IPTC.TAG_KEYWORDS, new String[] {"Uno", "Due", "Tre"}));
|
||||
|
||||
Directory iptc = new IPTCDirectory(entries);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
|
||||
createWriter().write(iptc, stream);
|
||||
stream.close();
|
||||
|
||||
Directory written = createReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
|
||||
assertEquals(iptc, written);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pcx</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+12
-38
@@ -28,52 +28,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pcx;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
final class PCXMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
final class PCXMetadata extends AbstractMetadata {
|
||||
private final PCXHeader header;
|
||||
private final IndexColorModel vgaPalette;
|
||||
|
||||
PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
|
||||
this.header = header;
|
||||
this.vgaPalette = vgaPalette;
|
||||
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IndexColorModel palette = null;
|
||||
@@ -141,7 +111,8 @@ final class PCXMetadata extends IIOMetadata {
|
||||
|
||||
// No compression
|
||||
|
||||
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.getCompression() != PCX.COMPRESSION_NONE) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
@@ -159,7 +130,8 @@ final class PCXMetadata extends IIOMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
// Planar configuration only makes sense for multi-channel images
|
||||
@@ -202,7 +174,8 @@ final class PCXMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
@@ -218,7 +191,8 @@ final class PCXMetadata extends IIOMetadata {
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pdf</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PDF 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>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pict</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PICT 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>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PNM 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>
|
||||
|
||||
+21
-40
@@ -28,51 +28,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
final class PNMMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
final class PNMMetadata extends AbstractMetadata {
|
||||
private final PNMHeader header;
|
||||
|
||||
PNMMetadata(final PNMHeader header) {
|
||||
this.header = header;
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
@@ -105,7 +76,9 @@ final class PNMMetadata extends IIOMetadata {
|
||||
// TODO: Might make sense to set gamma?
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? "FALSE" : "TRUE");
|
||||
blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO
|
||||
? "FALSE"
|
||||
: "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
@@ -113,11 +86,14 @@ final class PNMMetadata extends IIOMetadata {
|
||||
|
||||
// No compression
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT ? "Real" : "UnsignedIntegral");
|
||||
sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT
|
||||
? "Real"
|
||||
: "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
@@ -128,7 +104,9 @@ final class PNMMetadata extends IIOMetadata {
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN ? "0" : Integer.toString(header.getBitsPerSample() - 1);
|
||||
String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||
? "0"
|
||||
: Integer.toString(header.getBitsPerSample() - 1);
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
|
||||
|
||||
@@ -166,7 +144,8 @@ final class PNMMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
@@ -178,7 +157,8 @@ final class PNMMetadata extends IIOMetadata {
|
||||
|
||||
// No document node
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTextNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (!header.getComments().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
@@ -197,7 +177,8 @@ final class PNMMetadata extends IIOMetadata {
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-psd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PSD 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>
|
||||
|
||||
@@ -578,6 +578,94 @@ interface PSD {
|
||||
*/
|
||||
int RES_ALTERNATE_SPOT_COLORS = 0x042B;
|
||||
|
||||
/**
|
||||
* (Photoshop CS2) Layer Selection ID(s).
|
||||
* 2 bytes count, following is repeated for each count: 4 bytes layer ID.
|
||||
*/
|
||||
int RES_LAYER_SELECTION_IDS = 0x042D;
|
||||
|
||||
/**
|
||||
* (Photoshop CS2) HDR Toning information
|
||||
*/
|
||||
int RES_HDR_TONING_INFO = 0x042E;
|
||||
|
||||
/**
|
||||
* (Photoshop CS2) Print info
|
||||
*/
|
||||
int RES_PRINT_INFO = 0x042F;
|
||||
|
||||
/**
|
||||
* (Photoshop CS2) Layer Group(s) Enabled ID.
|
||||
* 1 byte for each layer in the document, repeated by length of the resource.
|
||||
* NOTE: Layer groups have start and end markers.
|
||||
*/
|
||||
int RES_LAYER_GROUPS_ENABLED = 0x0430;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) Color samplers resource.
|
||||
* Also see ID 1038 for old format.
|
||||
* See Color samplers resource format.
|
||||
*/
|
||||
int RES_COLOR_SAMPLERS_RESOURCE = 0x0431;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) Measurement Scale.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see Descriptor structure)
|
||||
*/
|
||||
int RES_MEASUREMENT_SCALE = 0x0432;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) Timeline Information.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see Descriptor structure)
|
||||
*/
|
||||
int RES_TIMELINE_INFO = 0x0433;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) Sheet Disclosure.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure)
|
||||
*/
|
||||
int RES_SHEET_DISCLOSURE = 0x0434;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) DisplayInfo structure to support floating point colors.
|
||||
* Also see ID 1007. See Appendix A in Photoshop API Guide.pdf .
|
||||
*/
|
||||
int RES_DISPLAY_INFO_FP = 0x0435;
|
||||
|
||||
/**
|
||||
* (Photoshop CS3) Onion Skins.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure)
|
||||
*/
|
||||
int RES_ONION_SKINS = 0x0436;
|
||||
|
||||
/**
|
||||
* (Photoshop CS4) Count Information.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
|
||||
* Information about the count in the document. See the Count Tool.
|
||||
*/
|
||||
int RES_COUNT_INFO = 0x0438;
|
||||
|
||||
/**
|
||||
* (Photoshop CS5) Print Information.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
|
||||
* Information about the current print settings in the document. The color management options.
|
||||
*/
|
||||
int RES_PRINT_INFO_CMM = 0x043A;
|
||||
|
||||
/**
|
||||
* (Photoshop CS5) Print Style.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
|
||||
* Information about the current print style in the document. The printing marks, labels, ornaments, etc.
|
||||
*/
|
||||
int RES_PRINT_STYLE = 0x043B;
|
||||
|
||||
/**
|
||||
* (Photoshop CC) Path Selection State.
|
||||
* 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
|
||||
* Information about the current path selection state.
|
||||
*/
|
||||
int RES_PATH_SELECTION_STATE = 0x0440;
|
||||
|
||||
// 07d0-0bb6
|
||||
/* Saved path information */
|
||||
|
||||
|
||||
+1
-3
@@ -28,8 +28,6 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.DataBuffer;
|
||||
@@ -66,7 +64,7 @@ final class PSDColorData {
|
||||
IndexColorModel getIndexColorModel() {
|
||||
if (colorModel == null) {
|
||||
int[] rgb = toInterleavedRGB(colors);
|
||||
colorModel = new InverseColorMapIndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
colorModel = new IndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
|
||||
return colorModel;
|
||||
|
||||
+91
-98
@@ -54,11 +54,11 @@ import java.util.List;
|
||||
/**
|
||||
* ImageReader for Adobe Photoshop Document (PSD) format.
|
||||
*
|
||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification<a>
|
||||
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification<a>
|
||||
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
|
||||
*/
|
||||
// TODO: Implement ImageIO meta data interface
|
||||
// TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile?
|
||||
@@ -224,22 +224,22 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
}
|
||||
|
||||
if (header.channels == 4 && header.bits == 8) {
|
||||
if (header.channels == 4 && header.bits == 8) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||
}
|
||||
else if (header.channels == 5 && header.bits == 8) {
|
||||
else if (header.channels == 5 && header.bits == 8) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false);
|
||||
}
|
||||
else if (header.channels == 4 && header.bits == 16) {
|
||||
else if (header.channels == 4 && header.bits == 16) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
|
||||
}
|
||||
else if (header.channels == 5 && header.bits == 16) {
|
||||
else if (header.channels == 5 && header.bits == 16) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false);
|
||||
}
|
||||
else if (header.channels == 4 && header.bits == 32) {
|
||||
else if (header.channels == 4 && header.bits == 32) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, false, false);
|
||||
}
|
||||
else if (header.channels == 5 && header.bits == 32) {
|
||||
else if (header.channels == 5 && header.bits == 32) {
|
||||
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false);
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
case PSD.COLOR_MODE_LAB:
|
||||
// TODO: Implement
|
||||
// TODO: If there's a color profile embedded, it should be easy, otherwise we're out of luck...
|
||||
// TODO: See the LAB color handling in TIFF
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", header.mode, header.channels, header.bits));
|
||||
}
|
||||
@@ -263,7 +264,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
ImageTypeSpecifier rawType = getRawImageTypeInternal(imageIndex);
|
||||
|
||||
ColorSpace cs = rawType.getColorModel().getColorSpace();
|
||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
||||
List<ImageTypeSpecifier> types = new ArrayList<>();
|
||||
|
||||
switch (header.mode) {
|
||||
case PSD.COLOR_MODE_RGB:
|
||||
@@ -308,7 +309,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
||||
}
|
||||
else if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 16) {
|
||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
|
||||
}
|
||||
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 16) {
|
||||
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
|
||||
@@ -362,25 +363,6 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final Rectangle dest = new Rectangle();
|
||||
computeRegions(param, header.width, header.height, image, source, dest);
|
||||
|
||||
/*
|
||||
NOTE: It seems safe to just leave this out for now. The only thing we need is to support sub sampling.
|
||||
Sun's readers does not support arbitrary destination formats.
|
||||
|
||||
// TODO: Create temp raster in native format w * 1
|
||||
// Read (sub-sampled) row into temp raster (skip other rows)
|
||||
// Copy pixels from temp raster
|
||||
// If possible, leave the destination image "untouched" (accelerated)
|
||||
// See Jim Grahams comments:
|
||||
// http://forums.java.net/jive/message.jspa?messageID=295758#295758
|
||||
|
||||
// TODO: Banding...
|
||||
|
||||
ImageTypeSpecifier spec = getRawImageType(imageIndex);
|
||||
BufferedImage temp = spec.createBufferedImage(getWidth(imageIndex), 1);
|
||||
temp.getRaster();
|
||||
|
||||
*/
|
||||
|
||||
final int xSub;
|
||||
final int ySub;
|
||||
|
||||
@@ -449,20 +431,19 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
return layersStart;
|
||||
}
|
||||
|
||||
private void readImageData(final BufferedImage pImage,
|
||||
private void readImageData(final BufferedImage destination,
|
||||
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
|
||||
final int pXSub, final int pYSub,
|
||||
final int[] pByteCounts, final int pCompression) throws IOException {
|
||||
|
||||
final WritableRaster raster = pImage.getRaster();
|
||||
final ColorModel destCM = pImage.getColorModel();
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
ColorModel destCM = destination.getColorModel();
|
||||
|
||||
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
||||
final WritableRaster rowRaster = pSourceCM.createCompatibleWritableRaster(header.width, 1);
|
||||
|
||||
final int channels = rowRaster.getNumBands();
|
||||
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
||||
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
||||
int channels = pSourceCM.createCompatibleSampleModel(1, 1).getNumBands();
|
||||
ImageTypeSpecifier singleBandRowSpec = ImageTypeSpecifiers.createGrayscale(header.bits, pSourceCM.getTransferType());
|
||||
WritableRaster rowRaster = singleBandRowSpec.createBufferedImage(header.width, 1).getRaster();
|
||||
boolean banded = destRaster.getDataBuffer().getNumBanks() > 1;
|
||||
int interleavedBands = banded ? 1 : destRaster.getNumBands();
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
|
||||
@@ -470,19 +451,19 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
switch (header.bits) {
|
||||
case 1:
|
||||
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
read1bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
|
||||
read1bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 8:
|
||||
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
read8bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
read8bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 16:
|
||||
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
read16bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
read16bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 32:
|
||||
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||
read32bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
read32bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
|
||||
@@ -495,12 +476,12 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
if (header.bits == 8) {
|
||||
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
|
||||
decomposeAlpha(destCM, raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands());
|
||||
decomposeAlpha(destCM, destRaster.getDataBuffer(), pDest.width, pDest.height, destRaster.getNumBands());
|
||||
}
|
||||
|
||||
// NOTE: ColorSpace uses Object.equals(), so we rely on using same instances!
|
||||
if (!pSourceCM.getColorSpace().equals(pImage.getColorModel().getColorSpace())) {
|
||||
convertToDestinationCS(pSourceCM, pImage.getColorModel(), raster);
|
||||
if (!pSourceCM.getColorSpace().equals(destination.getColorModel().getColorSpace())) {
|
||||
convertToDestinationCS(pSourceCM, destination.getColorModel(), destRaster);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,28 +527,24 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final int[] pRowByteCounts, final int pRowOffset,
|
||||
final boolean pRLECompressed) throws IOException {
|
||||
|
||||
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
final boolean invert = isCMYK && pChannel < colorComponents;
|
||||
final boolean banded = pData.getNumBanks() > 1;
|
||||
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
// NOTE: Length is in *16 bit values* (shorts)
|
||||
int length = 2 * (pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth);
|
||||
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 4 * pChannelWidth);
|
||||
|
||||
// TODO: Sometimes need to read the line y == source.y + source.height...
|
||||
// Read entire line, if within source region and sampling
|
||||
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
||||
|
||||
try {
|
||||
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
|
||||
for (int x = 0; x < pChannelWidth; x++) {
|
||||
pRow[x] = input.readInt();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.readFully(pRow, 0, pChannelWidth);
|
||||
@@ -580,7 +557,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
int value = pRow[pSource.x + x * pXSub];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (isCMYK && pChannel < colorComponents) {
|
||||
if (invert) {
|
||||
value = 0xffffffff - value;
|
||||
}
|
||||
|
||||
@@ -609,27 +586,23 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final int[] pRowByteCounts, final int pRowOffset,
|
||||
final boolean pRLECompressed) throws IOException {
|
||||
|
||||
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
final boolean invert = isCMYK && pChannel < colorComponents;
|
||||
final boolean banded = pData.getNumBanks() > 1;
|
||||
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
// NOTE: Length is in *16 bit values* (shorts)
|
||||
int length = 2 * (pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth);
|
||||
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 2 * pChannelWidth);
|
||||
|
||||
// TODO: Sometimes need to read the line y == source.y + source.height...
|
||||
// Read entire line, if within source region and sampling
|
||||
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
||||
try {
|
||||
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
|
||||
for (int x = 0; x < pChannelWidth; x++) {
|
||||
pRow[x] = input.readShort();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.readFully(pRow, 0, pChannelWidth);
|
||||
@@ -642,8 +615,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
short value = pRow[pSource.x + x * pXSub];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (isCMYK && pChannel < colorComponents) {
|
||||
value = (short) (65535 - value & 0xffff);
|
||||
if (invert) {
|
||||
value = (short) (0xffff - value & 0xffff);
|
||||
}
|
||||
|
||||
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
|
||||
@@ -671,8 +644,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final int[] pRowByteCounts, final int pRowOffset,
|
||||
final boolean pRLECompressed) throws IOException {
|
||||
|
||||
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
final boolean invert = isCMYK && pChannel < colorComponents;
|
||||
final boolean banded = pData.getNumBanks() > 1;
|
||||
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
@@ -682,13 +656,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
// Read entire line, if within source region and sampling
|
||||
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
||||
try {
|
||||
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
|
||||
input.readFully(pRow, 0, pChannelWidth);
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.readFully(pRow, 0, pChannelWidth);
|
||||
@@ -701,8 +671,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
byte value = pRow[pSource.x + x * pXSub];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (isCMYK && pChannel < colorComponents) {
|
||||
value = (byte) (255 - value & 0xff);
|
||||
if (invert) {
|
||||
value = (byte) (0xff - value & 0xff);
|
||||
}
|
||||
|
||||
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
|
||||
@@ -741,13 +711,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
// Read entire line, if within source region and sampling
|
||||
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
|
||||
try {
|
||||
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
|
||||
input.readFully(pRow, 0, pRow.length);
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.readFully(pRow, 0, pRow.length);
|
||||
@@ -868,8 +834,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
if (!header.hasValidDimensions()) {
|
||||
processWarningOccurred(String.format("Dimensions exceed maximum allowed for %s: %dx%d (max %dx%d)",
|
||||
header.largeFormat ? "PSB" : "PSD",
|
||||
header.width, header.height, header.getMaxSize(), header.getMaxSize()));
|
||||
header.largeFormat ? "PSB" : "PSD",
|
||||
header.width, header.height, header.getMaxSize(), header.getMaxSize()));
|
||||
}
|
||||
|
||||
metadata = new PSDMetadata();
|
||||
@@ -898,6 +864,10 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
// Don't need the header again
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("header: " + header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,7 +883,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
if (pParseData && imageResourcesLength > 0) {
|
||||
if (metadata.imageResources == null) {
|
||||
metadata.imageResources = new ArrayList<PSDImageResource>();
|
||||
metadata.imageResources = new ArrayList<>();
|
||||
long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength;
|
||||
|
||||
while (imageInput.getStreamPosition() < expectedEnd) {
|
||||
@@ -921,6 +891,10 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
metadata.imageResources.add(resource);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("imageResources: " + metadata.imageResources);
|
||||
}
|
||||
|
||||
if (imageInput.getStreamPosition() != expectedEnd) {
|
||||
throw new IIOException("Corrupt PSD document"); // ..or maybe just a bug in the reader.. ;-)
|
||||
}
|
||||
@@ -970,10 +944,13 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
long read = imageInput.getStreamPosition() - pos;
|
||||
|
||||
long diff = layerInfoLength - (read - (header.largeFormat ? 8 : 4)); // - 4 for the layerInfoLength field itself
|
||||
long diff = layerInfoLength - (read - (header.largeFormat
|
||||
? 8
|
||||
: 4)); // - 4 for the layerInfoLength field itself
|
||||
|
||||
imageInput.skipBytes(diff);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
metadata.layerInfo = Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -990,6 +967,10 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
// TODO: We should now be able to flush input
|
||||
// imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("layerInfo: " + metadata.layerInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,20 +982,18 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final int width = getLayerWidth(layerIndex);
|
||||
final int height = getLayerHeight(layerIndex);
|
||||
|
||||
// TODO: This behaviour must be documented!
|
||||
// If layer has no pixel data, return null
|
||||
if (width <= 0 || height <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
|
||||
// final int width = layerInfo.right - layerInfo.left;
|
||||
// final int height = layerInfo.bottom - layerInfo.top;
|
||||
|
||||
// Even if raw/imageType has no alpha, the layers may still have alpha...
|
||||
ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex);
|
||||
|
||||
// TODO: Find a better way of handling layers of size 0
|
||||
// - Return null? Return a BufferedImage subclass that has no data (0 x 0)?
|
||||
// Create image (or dummy, if h/w are <= 0)
|
||||
// BufferedImage layer = imageType.createBufferedImage(Math.max(1, width), Math.max(1, height));
|
||||
if (width <= 0 || height <= 0) {
|
||||
return null;
|
||||
}
|
||||
BufferedImage layer = getDestination(param, getImageTypes(layerIndex + 1), Math.max(1, width), Math.max(1, height));
|
||||
|
||||
imageInput.seek(findLayerStartPos(layerIndex));
|
||||
@@ -1028,9 +1007,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final WritableRaster raster = layer.getRaster();
|
||||
final ColorModel destCM = layer.getColorModel();
|
||||
|
||||
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
||||
ColorModel sourceCM = imageType.getColorModel();
|
||||
final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
|
||||
final WritableRaster rowRaster = sourceCM.createCompatibleWritableRaster((int) Math.ceil(width / (double) sourceCM.getNumComponents()), 1);
|
||||
|
||||
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
||||
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
||||
@@ -1134,7 +1112,6 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If there really is more channels, then create new imageTypeSpec
|
||||
if (newBandNum > compositeType.getNumBands()) {
|
||||
int[] indices = new int[newBandNum];
|
||||
@@ -1228,7 +1205,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
for (PSDImageResource resource : metadata.imageResources) {
|
||||
if (resource instanceof PSDThumbnail) {
|
||||
if (thumbnails == null) {
|
||||
thumbnails = new ArrayList<PSDThumbnail>();
|
||||
thumbnails = new ArrayList<>();
|
||||
}
|
||||
|
||||
thumbnails.add((PSDThumbnail) resource);
|
||||
@@ -1386,7 +1363,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
System.out.println("read time: " + (System.currentTimeMillis() - start));
|
||||
System.out.println("image: " + image);
|
||||
|
||||
if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
|
||||
if (image.getType() == BufferedImage.TYPE_CUSTOM) {
|
||||
try {
|
||||
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
|
||||
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
||||
@@ -1407,8 +1384,24 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
for (int i = 1; i < images; i++) {
|
||||
start = System.currentTimeMillis();
|
||||
BufferedImage layer = imageReader.read(i);
|
||||
|
||||
System.out.println("layer read time: " + (System.currentTimeMillis() - start));
|
||||
System.err.println("layer: " + layer);
|
||||
|
||||
if (layer != null && layer.getType() == BufferedImage.TYPE_CUSTOM) {
|
||||
try {
|
||||
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
|
||||
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
||||
layer = op.filter(layer, gc.createCompatibleImage(layer.getWidth(), layer.getHeight(), layer.getTransparency()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
layer = ImageUtil.accelerate(layer);
|
||||
}
|
||||
System.out.println("layer conversion time: " + (System.currentTimeMillis() - start));
|
||||
System.out.println("layer: " + layer);
|
||||
}
|
||||
|
||||
showIt(layer, "layer " + i);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
@@ -571,9 +572,8 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
compressionNode.appendChild(compressionTypeName);
|
||||
|
||||
if (compression != PSD.COMPRESSION_NONE) {
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "true");
|
||||
compressionNode.appendChild(lossless);
|
||||
compressionNode.appendChild(new IIOMetadataNode("Lossless"));
|
||||
// "value" defaults to TRUE, all PSD compressions are lossless
|
||||
}
|
||||
|
||||
return compressionNode;
|
||||
@@ -755,7 +755,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
}
|
||||
|
||||
private void appendTextEntriesFlat(final IIOMetadataNode node, final Directory directory, final FilterIterator.Filter<Entry> filter) {
|
||||
FilterIterator<Entry> entries = new FilterIterator<Entry>(directory.iterator(), filter);
|
||||
FilterIterator<Entry> entries = new FilterIterator<>(directory.iterator(), filter);
|
||||
|
||||
while (entries.hasNext()) {
|
||||
Entry entry = entries.next();
|
||||
@@ -807,7 +807,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
Iterator<T> iterator = (Iterator<T>) imageResources.iterator();
|
||||
|
||||
return new FilterIterator<T>(iterator, new FilterIterator.Filter<T>() {
|
||||
return new FilterIterator<>(iterator, new FilterIterator.Filter<T>() {
|
||||
public boolean accept(final T pElement) {
|
||||
return resourceType.isInstance(pElement);
|
||||
}
|
||||
@@ -817,7 +817,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
Iterator<PSDImageResource> getResources(final int... resourceTypes) {
|
||||
Iterator<PSDImageResource> iterator = imageResources.iterator();
|
||||
|
||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
return new FilterIterator<>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
public boolean accept(final PSDImageResource pResource) {
|
||||
for (int type : resourceTypes) {
|
||||
if (type == pResource.id) {
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ final class PSDUtil {
|
||||
static String intToStr(int value) {
|
||||
return new String(
|
||||
new byte[]{
|
||||
(byte) ((value & 0xff000000) >> 24),
|
||||
(byte) ((value & 0xff000000) >>> 24),
|
||||
(byte) ((value & 0x00ff0000) >> 16),
|
||||
(byte) ((value & 0x0000ff00) >> 8),
|
||||
(byte) ((value & 0x000000ff))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-reference</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: reference test cases</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+20
-40
@@ -28,48 +28,19 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.sgi;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
final class SGIMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
final class SGIMetadata extends AbstractMetadata {
|
||||
private final SGIHeader header;
|
||||
|
||||
SGIMetadata(final SGIHeader header) {
|
||||
this.header = header;
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention
|
||||
@@ -117,12 +88,15 @@ final class SGIMetadata extends IIOMetadata {
|
||||
|
||||
// No compression
|
||||
|
||||
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.getCompression() != SGI.COMPRESSION_NONE) {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE ? "RLE" : "Uknown");
|
||||
compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE
|
||||
? "RLE"
|
||||
: "Uknown");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
@@ -135,7 +109,8 @@ final class SGIMetadata extends IIOMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
@@ -183,7 +158,8 @@ final class SGIMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
@@ -195,7 +171,8 @@ final class SGIMetadata extends IIOMetadata {
|
||||
|
||||
// No document node
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTextNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (!header.getName().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
@@ -212,14 +189,17 @@ final class SGIMetadata extends IIOMetadata {
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
|
||||
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
|
||||
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3
|
||||
? "none"
|
||||
: "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tga</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
interface TGA {
|
||||
byte[] MAGIC = {'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.', 0};
|
||||
|
||||
/** Fixed header size: 18.*/
|
||||
int HEADER_SIZE = 18;
|
||||
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* TGAExtensions.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class TGAExtensions {
|
||||
public static final int EXT_AREA_SIZE = 495;
|
||||
|
||||
private String authorName;
|
||||
private String authorComments;
|
||||
|
||||
private Calendar creationDate;
|
||||
private String jobId;
|
||||
|
||||
private String softwareId;
|
||||
private String softwareVersion;
|
||||
|
||||
private int backgroundColor;
|
||||
private double pixelAspectRatio;
|
||||
private double gamma;
|
||||
|
||||
private long colorCorrectionOffset;
|
||||
private long postageStampOffset;
|
||||
private long scanLineOffset;
|
||||
|
||||
private int attributeType;
|
||||
|
||||
private TGAExtensions() {
|
||||
}
|
||||
|
||||
static TGAExtensions read(final ImageInputStream stream) throws IOException {
|
||||
int extSize = stream.readUnsignedShort();
|
||||
|
||||
// Should always be 495 for version 2.0, no newer version exists...
|
||||
if (extSize < EXT_AREA_SIZE) {
|
||||
throw new IIOException(String.format("TGA Extension Area size less than %d: %d", EXT_AREA_SIZE, extSize));
|
||||
}
|
||||
|
||||
TGAExtensions extensions = new TGAExtensions();
|
||||
extensions.authorName = readString(stream, 41);;
|
||||
extensions.authorComments = readString(stream, 324);
|
||||
extensions.creationDate = readDate(stream);
|
||||
extensions.jobId = readString(stream, 41);
|
||||
|
||||
stream.skipBytes(6); // Job time, 3 shorts, hours/minutes/seconds elapsed
|
||||
|
||||
extensions.softwareId = readString(stream, 41);
|
||||
|
||||
// Software version (* 100) short + single byte ASCII (ie. 101 'b' for 1.01b)
|
||||
int softwareVersion = stream.readUnsignedShort();
|
||||
int softwareLetter = stream.readByte();
|
||||
|
||||
extensions.softwareVersion = softwareVersion != 0 && softwareLetter != ' '
|
||||
? String.format("%d.%d%d", softwareVersion / 100, softwareVersion % 100, softwareLetter).trim()
|
||||
: null;
|
||||
|
||||
extensions.backgroundColor = stream.readInt(); // ARGB
|
||||
|
||||
extensions.pixelAspectRatio = readRational(stream);
|
||||
extensions.gamma = readRational(stream);
|
||||
|
||||
extensions.colorCorrectionOffset = stream.readUnsignedInt();
|
||||
extensions.postageStampOffset = stream.readUnsignedInt();
|
||||
extensions.scanLineOffset = stream.readUnsignedInt();
|
||||
|
||||
// Offset 494 specifies Attribute type:
|
||||
// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
|
||||
// 1: undefined data in the Alpha field, can be ignored
|
||||
// 2: undefined data in the Alpha field, but should be retained
|
||||
// 3: useful Alpha channel data is present
|
||||
// 4: pre-multiplied Alpha (see description below)
|
||||
// 5 -127: RESERVED
|
||||
// 128-255: Un-assigned
|
||||
extensions.attributeType = stream.readUnsignedByte();
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private static double readRational(final ImageInputStream stream) throws IOException {
|
||||
int numerator = stream.readUnsignedShort();
|
||||
int denominator = stream.readUnsignedShort();
|
||||
|
||||
return denominator != 0 ? numerator / (double) denominator : 1;
|
||||
}
|
||||
|
||||
private static Calendar readDate(final ImageInputStream stream) throws IOException {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.clear();
|
||||
|
||||
int month = stream.readUnsignedShort();
|
||||
int date = stream.readUnsignedShort();
|
||||
int year = stream.readUnsignedShort();
|
||||
|
||||
int hourOfDay = stream.readUnsignedShort();
|
||||
int minute = stream.readUnsignedShort();
|
||||
int second = stream.readUnsignedShort();
|
||||
|
||||
// Unused
|
||||
if (month == 0 && year == 0 && date == 0 && hourOfDay == 0 && minute == 0 && second == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
calendar.set(year, month - 1, date, hourOfDay, minute, second);
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
private static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
|
||||
byte[] data = new byte[maxLength];
|
||||
stream.readFully(data);
|
||||
|
||||
return asZeroTerminatedASCIIString(data);
|
||||
}
|
||||
|
||||
private static String asZeroTerminatedASCIIString(final byte[] data) {
|
||||
int len = data.length;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == 0) {
|
||||
len = i;
|
||||
}
|
||||
}
|
||||
|
||||
return new String(data, 0, len, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public boolean hasAlpha() {
|
||||
switch (attributeType) {
|
||||
case 3:
|
||||
case 4:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAlphaPremultiplied() {
|
||||
switch (attributeType) {
|
||||
case 4:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public long getThumbnailOffset() {
|
||||
return postageStampOffset;
|
||||
}
|
||||
|
||||
public String getAuthorName() {
|
||||
return authorName;
|
||||
}
|
||||
|
||||
public String getAuthorComments() {
|
||||
return authorComments;
|
||||
}
|
||||
|
||||
public Calendar getCreationDate() {
|
||||
return creationDate;
|
||||
}
|
||||
|
||||
public String getSoftware() {
|
||||
return softwareId;
|
||||
}
|
||||
|
||||
public String getSoftwareVersion() {
|
||||
return softwareVersion;
|
||||
}
|
||||
|
||||
public double getPixelAspectRatio() {
|
||||
return pixelAspectRatio;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return backgroundColor;
|
||||
}
|
||||
}
|
||||
+179
-31
@@ -33,6 +33,7 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@@ -51,6 +52,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@@ -59,6 +61,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
||||
|
||||
private TGAHeader header;
|
||||
private TGAExtensions extensions;
|
||||
|
||||
protected TGAImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
@@ -67,6 +70,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
header = null;
|
||||
extensions = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,7 +93,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||
|
||||
// TODO: Implement
|
||||
specifiers.add(rawType);
|
||||
@@ -110,19 +114,29 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
||||
case TGA.IMAGETYPE_MONOCHROME:
|
||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||
return ImageTypeSpecifiers.createGrayscale(1, DataBuffer.TYPE_BYTE);
|
||||
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
|
||||
case TGA.IMAGETYPE_TRUECOLOR:
|
||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
|
||||
boolean hasAlpha = header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha();
|
||||
boolean isAlphaPremultiplied = extensions != null && extensions.isAlphaPremultiplied();
|
||||
|
||||
switch (header.getPixelDepth()) {
|
||||
case 16:
|
||||
if (hasAlpha) {
|
||||
// USHORT_1555_ARGB...
|
||||
return ImageTypeSpecifiers.createPacked(sRGB, 0x7C00, 0x03E0, 0x001F, 0x8000, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
|
||||
}
|
||||
// Default mask out alpha
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||
case 24:
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
case 32:
|
||||
// 4BYTE_BGRA...
|
||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false);
|
||||
// 4BYTE_BGRX...
|
||||
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
|
||||
// if hasAlpha is false
|
||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
|
||||
default:
|
||||
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
||||
}
|
||||
@@ -166,31 +180,32 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
DataInput input;
|
||||
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
input = imageInput;
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
case 24:
|
||||
case 32:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||
break;
|
||||
case 16:
|
||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
|
||||
if (height - 1 - y < srcRegion.y) {
|
||||
case 8:
|
||||
case 24:
|
||||
case 32:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
|
||||
if (height - 1 - y < srcRegion.y) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -212,11 +227,11 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
input.readFully(rowDataByte, 0, rowDataByte.length);
|
||||
|
||||
if (srcChannel.getNumBands() == 4) {
|
||||
invertAlpha(rowDataByte);
|
||||
if (srcChannel.getNumBands() == 4 && (header.getAttributeBits() == 0 || extensions != null && !extensions.hasAlpha())) {
|
||||
// Remove the alpha channel (make pixels opaque) if there are no "attribute bits" (alpha bits)
|
||||
removeAlpha32(rowDataByte);
|
||||
}
|
||||
|
||||
// Subsample horizontal
|
||||
@@ -240,9 +255,9 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void invertAlpha(final byte[] rowDataByte) {
|
||||
for (int i = 3; i < rowDataByte.length; i += 4) {
|
||||
rowDataByte[i] = (byte) (0xFF - rowDataByte[i]);
|
||||
private void removeAlpha32(final byte[] rowData) {
|
||||
for (int i = 3; i < rowData.length; i += 4) {
|
||||
rowData[i] = (byte) 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,21 +328,154 @@ public final class TGAImageReader extends ImageReaderBase {
|
||||
private void readHeader() throws IOException {
|
||||
if (header == null) {
|
||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// Read header
|
||||
header = TGAHeader.read(imageInput);
|
||||
|
||||
// System.err.println("header: " + header);
|
||||
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
|
||||
// Read footer, if 2.0 format (ends with TRUEVISION-XFILE\0)
|
||||
skipToEnd(imageInput);
|
||||
imageInput.seek(imageInput.getStreamPosition() - 26);
|
||||
|
||||
long extOffset = imageInput.readInt();
|
||||
/*long devOffset = */imageInput.readInt(); // Ignored for now
|
||||
|
||||
byte[] magic = new byte[18];
|
||||
imageInput.readFully(magic);
|
||||
|
||||
if (Arrays.equals(magic, TGA.MAGIC)) {
|
||||
if (extOffset > 0) {
|
||||
imageInput.seek(extOffset);
|
||||
extensions = TGAExtensions.read(imageInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageInput.seek(imageInput.getFlushedPosition());
|
||||
}
|
||||
|
||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
// TODO: Candidate util method
|
||||
private static void skipToEnd(final ImageInputStream stream) throws IOException {
|
||||
if (stream.length() > 0) {
|
||||
// Seek to end of file
|
||||
stream.seek(stream.length());
|
||||
}
|
||||
else {
|
||||
// Skip to end
|
||||
long lastGood = stream.getStreamPosition();
|
||||
|
||||
while (stream.read() != -1) {
|
||||
lastGood = stream.getStreamPosition();
|
||||
stream.skipBytes(1024);
|
||||
}
|
||||
|
||||
stream.seek(lastGood);
|
||||
|
||||
while (true) {
|
||||
if (stream.read() == -1) {
|
||||
break;
|
||||
}
|
||||
// Just continue reading to EOF...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thumbnail support
|
||||
|
||||
@Override
|
||||
public boolean readerSupportsThumbnails() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasThumbnails(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new TGAMetadata(header);
|
||||
return extensions != null && extensions.getThumbnailOffset() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumThumbnails(final int imageIndex) throws IOException {
|
||||
return hasThumbnails(imageIndex) ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThumbnailWidth(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
Validate.isTrue(thumbnailIndex >= 0 && thumbnailIndex < getNumThumbnails(imageIndex), "thumbnailIndex >= numThumbnails");
|
||||
|
||||
imageInput.seek(extensions.getThumbnailOffset());
|
||||
|
||||
return imageInput.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThumbnailHeight(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||
getThumbnailWidth(imageIndex, thumbnailIndex); // Laziness...
|
||||
|
||||
return imageInput.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage readThumbnail(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
int width = getThumbnailWidth(imageIndex, thumbnailIndex);
|
||||
int height = getThumbnailHeight(imageIndex, thumbnailIndex);
|
||||
|
||||
// For thumbnail, always read entire image
|
||||
Rectangle srcRegion = new Rectangle(width, height);
|
||||
|
||||
BufferedImage destination = getDestination(null, imageTypes, width, height);
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||
|
||||
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||
|
||||
// Thumbnail is always stored non-compressed, no need for RLE support
|
||||
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
case 24:
|
||||
case 32:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y);
|
||||
break;
|
||||
case 16:
|
||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||
}
|
||||
|
||||
processThumbnailProgress(100f * y / height);
|
||||
|
||||
if (height - 1 - y < srcRegion.y) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processThumbnailComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
// Metadata support
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new TGAMetadata(header, extensions);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
+7
-5
@@ -45,7 +45,8 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
||||
super(new TGAProviderInfo());
|
||||
}
|
||||
|
||||
@Override public boolean canDecodeInput(final Object source) throws IOException {
|
||||
@Override
|
||||
public boolean canDecodeInput(final Object source) throws IOException {
|
||||
if (!(source instanceof ImageInputStream)) {
|
||||
return false;
|
||||
}
|
||||
@@ -58,7 +59,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
||||
try {
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// NOTE: The TGA format does not have a magic identifier, so this is guesswork...
|
||||
// NOTE: The original TGA format does not have a magic identifier, so this is guesswork...
|
||||
// We'll try to match sane values, and hope no other files contains the same sequence.
|
||||
|
||||
stream.readUnsignedByte();
|
||||
@@ -88,11 +89,11 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
int colorMapStart = stream.readUnsignedShort();
|
||||
int colorMapSize = stream.readUnsignedShort();
|
||||
int colorMapDetph = stream.readUnsignedByte();
|
||||
int colorMapDepth = stream.readUnsignedByte();
|
||||
|
||||
if (colorMapSize == 0) {
|
||||
// No color map, all 3 fields should be 0
|
||||
if (colorMapStart!= 0 || colorMapDetph != 0) {
|
||||
if (colorMapStart != 0 || colorMapDepth != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
||||
if (colorMapStart >= colorMapSize) {
|
||||
return false;
|
||||
}
|
||||
if (colorMapDetph != 15 && colorMapDetph != 16 && colorMapDetph != 24 && colorMapDetph != 32) {
|
||||
if (colorMapDepth != 15 && colorMapDepth != 16 && colorMapDepth != 24 && colorMapDepth != 32) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -134,6 +135,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
// We're pretty sure by now, but there can still be false positives...
|
||||
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
|
||||
// unless we are working with a local file (and the file may still be a valid original TGA without it).
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
|
||||
+124
-73
@@ -28,53 +28,29 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tga;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.Calendar;
|
||||
|
||||
final class TGAMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
final class TGAMetadata extends AbstractMetadata {
|
||||
private final TGAHeader header;
|
||||
private final TGAExtensions extensions;
|
||||
|
||||
TGAMetadata(final TGAHeader header) {
|
||||
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
||||
this.header = header;
|
||||
standardFormatSupported = true;
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_MONOCHROME:
|
||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||
@@ -92,15 +68,22 @@ final class TGAMetadata extends IIOMetadata {
|
||||
default:
|
||||
csType.setAttribute("name", "Unknown");
|
||||
}
|
||||
chroma.appendChild(csType);
|
||||
|
||||
// TODO: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
case 16:
|
||||
numChannels.setAttribute("value", Integer.toString(1));
|
||||
break;
|
||||
case 16:
|
||||
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
}
|
||||
else {
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
}
|
||||
break;
|
||||
case 24:
|
||||
numChannels.setAttribute("value", Integer.toString(3));
|
||||
break;
|
||||
@@ -108,11 +91,10 @@ final class TGAMetadata extends IIOMetadata {
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
break;
|
||||
}
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
|
||||
// NOTE: TGA files may contain a color map, even if true color...
|
||||
// Not sure if this is a good idea to expose to the meta data,
|
||||
@@ -124,20 +106,31 @@ final class TGAMetadata extends IIOMetadata {
|
||||
|
||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||
palette.appendChild(paletteEntry);
|
||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||
|
||||
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
|
||||
palette.appendChild(paletteEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||
Color background = new Color(extensions.getBackgroundColor(), true);
|
||||
|
||||
IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
|
||||
chroma.appendChild(backgroundColor);
|
||||
|
||||
backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
|
||||
backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
|
||||
backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
|
||||
}
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||
@@ -145,15 +138,16 @@ final class TGAMetadata extends IIOMetadata {
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||
? "Uknown" : "RLE";
|
||||
compressionTypeName.setAttribute("value", value);
|
||||
node.appendChild(compressionTypeName);
|
||||
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||
? "Uknown" : "RLE";
|
||||
compressionTypeName.setAttribute("value", value);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
|
||||
return node;
|
||||
default:
|
||||
@@ -162,14 +156,17 @@ final class TGAMetadata extends IIOMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
node.appendChild(planarConfiguration);
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
switch (header.getImageType()) {
|
||||
case TGA.IMAGETYPE_COLORMAPPED:
|
||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||
@@ -182,13 +179,19 @@ final class TGAMetadata extends IIOMetadata {
|
||||
break;
|
||||
}
|
||||
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
switch (header.getPixelDepth()) {
|
||||
case 8:
|
||||
case 16:
|
||||
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
||||
case 16:
|
||||
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
||||
}
|
||||
else {
|
||||
bitsPerSample.setAttribute("value", createListValue(3, "5"));
|
||||
}
|
||||
break;
|
||||
case 24:
|
||||
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
||||
@@ -198,12 +201,6 @@ final class TGAMetadata extends IIOMetadata {
|
||||
break;
|
||||
}
|
||||
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
// TODO: Do we need MSB?
|
||||
// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -221,10 +218,12 @@ final class TGAMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
switch (header.getOrigin()) {
|
||||
case TGA.ORIGIN_LOWER_LEFT:
|
||||
@@ -241,38 +240,90 @@ final class TGAMetadata extends IIOMetadata {
|
||||
break;
|
||||
}
|
||||
|
||||
dimension.appendChild(imageOrientation);
|
||||
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
dimension.appendChild(pixelAspectRatio);
|
||||
pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0");
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: Extra "developer area" and other stuff might go here...
|
||||
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||
document.appendChild(formatVersion);
|
||||
formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0");
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "identification");
|
||||
textEntry.setAttribute("value", header.getIdentification());
|
||||
text.appendChild(textEntry);
|
||||
// ImageCreationTime from extensions date
|
||||
if (extensions != null && extensions.getCreationDate() != null) {
|
||||
IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
|
||||
document.appendChild(imageCreationTime);
|
||||
|
||||
return text;
|
||||
Calendar date = extensions.getCreationDate();
|
||||
|
||||
imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
|
||||
imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
|
||||
imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
|
||||
imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
|
||||
imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
|
||||
imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
|
||||
}
|
||||
|
||||
return null;
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
||||
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||
}
|
||||
|
||||
if (extensions != null) {
|
||||
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : extensions.getSoftware() + " " + extensions.getSoftwareVersion());
|
||||
appendTextEntry(text, "Artist", extensions.getAuthorName());
|
||||
appendTextEntry(text, "UserComment", extensions.getAuthorComments());
|
||||
}
|
||||
|
||||
return text.hasChildNodes() ? text : null;
|
||||
}
|
||||
|
||||
private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||
if (value != null) {
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
parent.appendChild(textEntry);
|
||||
textEntry.setAttribute("keyword", keyword);
|
||||
textEntry.setAttribute("value", value);
|
||||
}
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getPixelDepth() == 32 ? "nonpremultiplied" : "none");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
if (extensions != null) {
|
||||
if (extensions.hasAlpha()) {
|
||||
alpha.setAttribute("value", extensions.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||
}
|
||||
else {
|
||||
alpha.setAttribute("value", "none");
|
||||
}
|
||||
}
|
||||
else if (header.getAttributeBits() == 8) {
|
||||
alpha.setAttribute("value", "nonpremultiplied");
|
||||
}
|
||||
else {
|
||||
alpha.setAttribute("value", "none");
|
||||
}
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-thumbsdb</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1.3-SNAPSHOT</version>
|
||||
<version>3.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
||||
@@ -24,7 +24,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+500
-166
@@ -36,9 +36,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* CCITT Modified Huffman RLE<!--, and hopefully soon: Group 3 (T4) and Group 4 (T6) fax compression-->.
|
||||
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author <a href="https://github.com/Schmidor">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
||||
*/
|
||||
@@ -51,33 +52,57 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
private int decodedLength;
|
||||
private int decodedPos;
|
||||
|
||||
private int bitBuffer;
|
||||
private int bitBufferLength;
|
||||
|
||||
// Need to take fill order into account (?) (use flip table?)
|
||||
private final int fillOrder;
|
||||
private final int type;
|
||||
|
||||
private final int[] changes;
|
||||
private int changesCount;
|
||||
private int[] changesReferenceRow;
|
||||
private int[] changesCurrentRow;
|
||||
private int changesReferenceRowCount;
|
||||
private int changesCurrentRowCount;
|
||||
|
||||
private static final int EOL_CODE = 0x01; // 12 bit
|
||||
private boolean optionG32D = false;
|
||||
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) {
|
||||
@SuppressWarnings("unused") // Leading zeros for aligning EOL
|
||||
private boolean optionG3Fill = false;
|
||||
|
||||
private boolean optionUncompressed = false;
|
||||
|
||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
|
||||
final long options) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||
// We know this is only used for b/w (1 bit)
|
||||
this.decodedRow = new byte[(columns + 7) / 8];
|
||||
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4
|
||||
this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
|
||||
this.type = Validate.isTrue(
|
||||
type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
|
||||
type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"
|
||||
);
|
||||
this.fillOrder = Validate.isTrue(
|
||||
fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
|
||||
fillOrder, "Expected fill order 1 or 2: %s"
|
||||
);
|
||||
|
||||
this.changes = new int[columns];
|
||||
this.changesReferenceRow = new int[columns];
|
||||
this.changesCurrentRow = new int[columns];
|
||||
|
||||
switch (type) {
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
||||
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
||||
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||
}
|
||||
|
||||
// IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table?
|
||||
// -- If white run, start at 4 bits to determine length, if black, start at 2 bits
|
||||
|
||||
private void fetch() throws IOException {
|
||||
if (decodedPos >= decodedLength) {
|
||||
decodedLength = 0;
|
||||
@@ -91,7 +116,8 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// ..otherwise, just client code trying to read past the end of stream
|
||||
// ..otherwise, just client code trying to read past the end of
|
||||
// stream
|
||||
decodedLength = -1;
|
||||
}
|
||||
|
||||
@@ -99,141 +125,279 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeRow() throws IOException {
|
||||
resetBuffer();
|
||||
private void decode1D() throws IOException {
|
||||
int index = 0;
|
||||
boolean white = true;
|
||||
changesCurrentRowCount = 0;
|
||||
|
||||
boolean literalRun = true;
|
||||
do {
|
||||
int completeRun;
|
||||
|
||||
/*
|
||||
if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
|
||||
int eol = readBits(12);
|
||||
System.err.println("eol: " + eol);
|
||||
while (eol != EOL_CODE) {
|
||||
eol = readBits(1);
|
||||
System.err.println("eol: " + eol);
|
||||
// throw new IOException("Missing EOL");
|
||||
if (white) {
|
||||
completeRun = decodeRun(whiteRunTree);
|
||||
}
|
||||
else {
|
||||
completeRun = decodeRun(blackRunTree);
|
||||
}
|
||||
|
||||
literalRun = readBits(1) == 1;
|
||||
index += completeRun;
|
||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||
|
||||
// Flip color for next run
|
||||
white = !white;
|
||||
} while (index < columns);
|
||||
}
|
||||
|
||||
private void decode2D() throws IOException {
|
||||
changesReferenceRowCount = changesCurrentRowCount;
|
||||
int[] tmp = changesCurrentRow;
|
||||
changesCurrentRow = changesReferenceRow;
|
||||
changesReferenceRow = tmp;
|
||||
|
||||
boolean white = true;
|
||||
int index = 0;
|
||||
changesCurrentRowCount = 0;
|
||||
|
||||
mode: while (index < columns) {
|
||||
// read mode
|
||||
Node n = codeTree.root;
|
||||
|
||||
while (true) {
|
||||
n = n.walk(readBit());
|
||||
|
||||
if (n == null) {
|
||||
continue mode;
|
||||
}
|
||||
else if (n.isLeaf) {
|
||||
switch (n.value) {
|
||||
case VALUE_HMODE:
|
||||
int runLength;
|
||||
runLength = decodeRun(white ? whiteRunTree : blackRunTree);
|
||||
index += runLength;
|
||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||
|
||||
runLength = decodeRun(white ? blackRunTree : whiteRunTree);
|
||||
index += runLength;
|
||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||
break;
|
||||
|
||||
case VALUE_PASSMODE:
|
||||
int pChangingElement = getNextChangingElement(index, white) + 1;
|
||||
|
||||
if (pChangingElement >= changesReferenceRowCount || pChangingElement == -1) {
|
||||
index = columns;
|
||||
}
|
||||
else {
|
||||
index = changesReferenceRow[pChangingElement];
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
// Vertical mode (-3 to 3)
|
||||
int vChangingElement = getNextChangingElement(index, white);
|
||||
|
||||
if (vChangingElement >= changesReferenceRowCount || vChangingElement == -1) {
|
||||
index = columns + n.value;
|
||||
}
|
||||
else {
|
||||
index = changesReferenceRow[vChangingElement] + n.value;
|
||||
}
|
||||
|
||||
changesCurrentRow[changesCurrentRowCount] = index;
|
||||
changesCurrentRowCount++;
|
||||
white = !white;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
continue mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getNextChangingElement(final int a0, final boolean white) {
|
||||
int start = white ? 0 : 1;
|
||||
|
||||
for (int i = start; i < changesReferenceRowCount; i += 2) {
|
||||
if (a0 < changesReferenceRow[i] || (a0 == 0 && changesReferenceRow[i] == 0)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void decodeRowType2() throws IOException {
|
||||
resetBuffer();
|
||||
decode1D();
|
||||
}
|
||||
|
||||
private void decodeRowType4() throws IOException {
|
||||
eof: while (true) {
|
||||
// read till next EOL code
|
||||
Node n = eolOnlyTree.root;
|
||||
|
||||
while (true) {
|
||||
n = n.walk(readBit());
|
||||
|
||||
if (n == null) {
|
||||
continue eof;
|
||||
}
|
||||
|
||||
if (n.isLeaf) {
|
||||
break eof;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println("literalRun: " + literalRun);
|
||||
*/
|
||||
int index = 0;
|
||||
|
||||
if (literalRun) {
|
||||
changesCount = 0;
|
||||
boolean white = true;
|
||||
|
||||
do {
|
||||
int completeRun = 0;
|
||||
|
||||
int run;
|
||||
do {
|
||||
if (white) {
|
||||
run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
|
||||
}
|
||||
else {
|
||||
run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2);
|
||||
}
|
||||
|
||||
completeRun += run;
|
||||
}
|
||||
while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes
|
||||
|
||||
changes[changesCount++] = index + completeRun;
|
||||
|
||||
// System.err.printf("%s run: %d\n", white ? "white" : "black", run);
|
||||
|
||||
// TODO: Optimize with lookup for 0-7 bits?
|
||||
// Fill bits to byte boundary...
|
||||
while (index % 8 != 0 && completeRun-- > 0) {
|
||||
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
|
||||
}
|
||||
|
||||
// ...then fill complete bytes to either 0xff or 0x00...
|
||||
if (index % 8 == 0) {
|
||||
final byte value = (byte) (white ? 0xff : 0x00);
|
||||
|
||||
while (completeRun > 7) {
|
||||
decodedRow[index / 8] = value;
|
||||
completeRun -= 8;
|
||||
index += 8;
|
||||
}
|
||||
}
|
||||
|
||||
// ...finally fill any remaining bits
|
||||
while (completeRun-- > 0) {
|
||||
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
|
||||
}
|
||||
|
||||
// Flip color for next run
|
||||
white = !white;
|
||||
}
|
||||
while (index < columns);
|
||||
if (!optionG32D || readBit()) {
|
||||
decode1D();
|
||||
}
|
||||
else {
|
||||
// non-literal run
|
||||
decode2D();
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeRowType6() throws IOException {
|
||||
decode2D();
|
||||
}
|
||||
|
||||
private void decodeRow() throws IOException {
|
||||
switch (type) {
|
||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||
decodeRowType2();
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
decodeRowType4();
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
decodeRowType6();
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) {
|
||||
int index = 0;
|
||||
boolean white = true;
|
||||
|
||||
|
||||
for (int i = 0; i <= changesCurrentRowCount; i++) {
|
||||
int nextChange = columns;
|
||||
|
||||
if (i != changesCurrentRowCount) {
|
||||
nextChange = changesCurrentRow[i];
|
||||
}
|
||||
|
||||
if (nextChange > columns) {
|
||||
nextChange = columns;
|
||||
}
|
||||
|
||||
int byteIndex = index / 8;
|
||||
|
||||
while (index % 8 != 0 && (nextChange - index) > 0) {
|
||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index % 8 == 0) {
|
||||
byteIndex = index / 8;
|
||||
final byte value = (byte) (white ? 0x00 : 0xff);
|
||||
|
||||
while ((nextChange - index) > 7) {
|
||||
decodedRow[byteIndex] = value;
|
||||
index += 8;
|
||||
++byteIndex;
|
||||
}
|
||||
}
|
||||
|
||||
while ((nextChange - index) > 0) {
|
||||
if (index % 8 == 0) {
|
||||
decodedRow[byteIndex] = 0;
|
||||
}
|
||||
|
||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||
index++;
|
||||
}
|
||||
|
||||
white = !white;
|
||||
}
|
||||
|
||||
if (index != columns) {
|
||||
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
||||
}
|
||||
|
||||
decodedLength = (index + 7) / 8;
|
||||
}
|
||||
|
||||
private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException {
|
||||
// TODO: Optimize...
|
||||
// Looping and comparing is the most straight-forward, but probably not the most effective way...
|
||||
int code = readBits(minCodeSize);
|
||||
private int decodeRun(final Tree tree) throws IOException {
|
||||
int total = 0;
|
||||
|
||||
for (int bits = 0; bits < codes.length; bits++) {
|
||||
short[] bitCodes = codes[bits];
|
||||
Node n = tree.root;
|
||||
|
||||
for (int i = 0; i < bitCodes.length; i++) {
|
||||
if (bitCodes[i] == code) {
|
||||
// System.err.println("code: " + code);
|
||||
while (true) {
|
||||
boolean bit = readBit();
|
||||
n = n.walk(bit);
|
||||
|
||||
// Code found, return matching run length
|
||||
return runLengths[bits][i];
|
||||
}
|
||||
if (n == null) {
|
||||
throw new IOException("Unknown code in Huffman RLE stream");
|
||||
}
|
||||
|
||||
// No code found, read one more bit and try again
|
||||
code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
|
||||
if (n.isLeaf) {
|
||||
total += n.value;
|
||||
if (n.value < 64) {
|
||||
return total;
|
||||
}
|
||||
else {
|
||||
n = tree.root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException("Unknown code in Huffman RLE stream");
|
||||
}
|
||||
|
||||
private void resetBuffer() {
|
||||
private void resetBuffer() throws IOException {
|
||||
for (int i = 0; i < decodedRow.length; i++) {
|
||||
decodedRow[i] = 0;
|
||||
}
|
||||
|
||||
bitBuffer = 0;
|
||||
bitBufferLength = 0;
|
||||
while (true) {
|
||||
if (bufferPos == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
readBit();
|
||||
}
|
||||
}
|
||||
|
||||
private int readBits(int bitCount) throws IOException {
|
||||
while (bitBufferLength < bitCount) {
|
||||
int read = in.read();
|
||||
if (read == -1) {
|
||||
int buffer = -1;
|
||||
int bufferPos = -1;
|
||||
|
||||
private boolean readBit() throws IOException {
|
||||
if (bufferPos < 0 || bufferPos > 7) {
|
||||
buffer = in.read();
|
||||
|
||||
if (buffer == -1) {
|
||||
throw new EOFException("Unexpected end of Huffman RLE stream");
|
||||
}
|
||||
|
||||
int bits = read & 0xff;
|
||||
bitBuffer = (bitBuffer << 8) | bits;
|
||||
bitBufferLength += 8;
|
||||
bufferPos = 0;
|
||||
}
|
||||
|
||||
// TODO: Take fill order into account
|
||||
bitBufferLength -= bitCount;
|
||||
int result = bitBuffer >> bitBufferLength;
|
||||
bitBuffer &= (1 << bitBufferLength) - 1;
|
||||
boolean isSet;
|
||||
|
||||
return result;
|
||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
||||
}
|
||||
else {
|
||||
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
||||
}
|
||||
|
||||
bufferPos++;
|
||||
|
||||
if (bufferPos > 7) {
|
||||
bufferPos = -1;
|
||||
}
|
||||
|
||||
return isSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -304,150 +468,320 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
|
||||
private static final class Node {
|
||||
Node left;
|
||||
Node right;
|
||||
|
||||
int value; // > 63 non term.
|
||||
|
||||
boolean canBeFill = false;
|
||||
boolean isLeaf = false;
|
||||
|
||||
void set(final boolean next, final Node node) {
|
||||
if (!next) {
|
||||
left = node;
|
||||
}
|
||||
else {
|
||||
right = node;
|
||||
}
|
||||
}
|
||||
|
||||
Node walk(final boolean next) {
|
||||
return next ? right : left;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[leaf=" + isLeaf + ", value=" + value + ", canBeFill=" + canBeFill + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Tree {
|
||||
final Node root = new Node();
|
||||
|
||||
void fill(final int depth, final int path, final int value) throws IOException {
|
||||
Node current = root;
|
||||
|
||||
for (int i = 0; i < depth; i++) {
|
||||
int bitPos = depth - 1 - i;
|
||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||
Node next = current.walk(isSet);
|
||||
|
||||
if (next == null) {
|
||||
next = new Node();
|
||||
|
||||
if (i == depth - 1) {
|
||||
next.value = value;
|
||||
next.isLeaf = true;
|
||||
}
|
||||
|
||||
if (path == 0) {
|
||||
next.canBeFill = true;
|
||||
}
|
||||
|
||||
current.set(isSet, next);
|
||||
}
|
||||
else {
|
||||
if (next.isLeaf) {
|
||||
throw new IOException("node is leaf, no other following");
|
||||
}
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
void fill(final int depth, final int path, final Node node) throws IOException {
|
||||
Node current = root;
|
||||
|
||||
for (int i = 0; i < depth; i++) {
|
||||
int bitPos = depth - 1 - i;
|
||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||
Node next = current.walk(isSet);
|
||||
|
||||
if (next == null) {
|
||||
if (i == depth - 1) {
|
||||
next = node;
|
||||
}
|
||||
else {
|
||||
next = new Node();
|
||||
}
|
||||
|
||||
if (path == 0) {
|
||||
next.canBeFill = true;
|
||||
}
|
||||
|
||||
current.set(isSet, next);
|
||||
}
|
||||
else {
|
||||
if (next.isLeaf) {
|
||||
throw new IOException("node is leaf, no other following");
|
||||
}
|
||||
}
|
||||
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final short[][] BLACK_CODES = {
|
||||
{ // 2 bits
|
||||
0x2, 0x3,
|
||||
0x2, 0x3,
|
||||
},
|
||||
{ // 3 bits
|
||||
0x2, 0x3,
|
||||
0x2, 0x3,
|
||||
},
|
||||
{ // 4 bits
|
||||
0x2, 0x3,
|
||||
0x2, 0x3,
|
||||
},
|
||||
{ // 5 bits
|
||||
0x3,
|
||||
0x3,
|
||||
},
|
||||
{ // 6 bits
|
||||
0x4, 0x5,
|
||||
0x4, 0x5,
|
||||
},
|
||||
{ // 7 bits
|
||||
0x4, 0x5, 0x7,
|
||||
0x4, 0x5, 0x7,
|
||||
},
|
||||
{ // 8 bits
|
||||
0x4, 0x7,
|
||||
0x4, 0x7,
|
||||
},
|
||||
{ // 9 bits
|
||||
0x18,
|
||||
0x18,
|
||||
},
|
||||
{ // 10 bits
|
||||
0x17, 0x18, 0x37, 0x8, 0xf,
|
||||
0x17, 0x18, 0x37, 0x8, 0xf,
|
||||
},
|
||||
{ // 11 bits
|
||||
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
||||
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
||||
},
|
||||
{ // 12 bits
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
||||
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
||||
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
||||
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
||||
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
||||
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
||||
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
|
||||
},
|
||||
{ // 13 bits
|
||||
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
||||
0x74, 0x75, 0x76, 0x77,
|
||||
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
||||
0x74, 0x75, 0x76, 0x77,
|
||||
}
|
||||
};
|
||||
static final short[][] BLACK_RUN_LENGTHS = {
|
||||
{ // 2 bits
|
||||
3, 2,
|
||||
3, 2,
|
||||
},
|
||||
{ // 3 bits
|
||||
1, 4,
|
||||
1, 4,
|
||||
},
|
||||
{ // 4 bits
|
||||
6, 5,
|
||||
6, 5,
|
||||
},
|
||||
{ // 5 bits
|
||||
7,
|
||||
7,
|
||||
},
|
||||
{ // 6 bits
|
||||
9, 8,
|
||||
9, 8,
|
||||
},
|
||||
{ // 7 bits
|
||||
10, 11, 12,
|
||||
10, 11, 12,
|
||||
},
|
||||
{ // 8 bits
|
||||
13, 14,
|
||||
13, 14,
|
||||
},
|
||||
{ // 9 bits
|
||||
15,
|
||||
15,
|
||||
},
|
||||
{ // 10 bits
|
||||
16, 17, 0, 18, 64,
|
||||
16, 17, 0, 18, 64,
|
||||
},
|
||||
{ // 11 bits
|
||||
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
||||
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
||||
},
|
||||
{ // 12 bits
|
||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320,
|
||||
384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49,
|
||||
62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35,
|
||||
36, 37, 38, 39, 42, 43,
|
||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
||||
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
||||
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43,
|
||||
},
|
||||
{ // 13 bits
|
||||
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960,
|
||||
1024, 1088, 1152, 1216,
|
||||
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
||||
1152, 1216,
|
||||
}
|
||||
};
|
||||
|
||||
public static final short[][] WHITE_CODES = {
|
||||
{ // 4 bits
|
||||
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
||||
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
||||
},
|
||||
{ // 5 bits
|
||||
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
||||
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
||||
},
|
||||
{ // 6 bits
|
||||
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
||||
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
||||
},
|
||||
{ // 7 bits
|
||||
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
|
||||
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
|
||||
},
|
||||
{ // 8 bits
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
||||
0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55,
|
||||
0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
||||
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
||||
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
||||
},
|
||||
{ // 9 bits
|
||||
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||
},
|
||||
{ // 10 bits
|
||||
},
|
||||
{ // 11 bits
|
||||
0x8, 0xc, 0xd,
|
||||
0x8, 0xc, 0xd,
|
||||
},
|
||||
{ // 12 bits
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
}
|
||||
};
|
||||
|
||||
public static final short[][] WHITE_RUN_LENGTHS = {
|
||||
{ // 4 bits
|
||||
2, 3, 4, 5, 6, 7,
|
||||
2, 3, 4, 5, 6, 7,
|
||||
},
|
||||
{ // 5 bits
|
||||
128, 8, 9, 64, 10, 11,
|
||||
128, 8, 9, 64, 10, 11,
|
||||
},
|
||||
{ // 6 bits
|
||||
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
||||
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
||||
},
|
||||
{ // 7 bits
|
||||
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
|
||||
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
|
||||
},
|
||||
{ // 8 bits
|
||||
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43,
|
||||
44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51,
|
||||
52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
||||
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
|
||||
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
||||
},
|
||||
{ // 9 bits
|
||||
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
||||
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
||||
},
|
||||
{ // 10 bits
|
||||
},
|
||||
{ // 11 bits
|
||||
1792, 1856, 1920,
|
||||
1792, 1856, 1920,
|
||||
},
|
||||
{ // 12 bits
|
||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
|
||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
|
||||
}
|
||||
};
|
||||
|
||||
final static Node EOL;
|
||||
final static Node FILL;
|
||||
final static Tree blackRunTree;
|
||||
final static Tree whiteRunTree;
|
||||
final static Tree eolOnlyTree;
|
||||
final static Tree codeTree;
|
||||
|
||||
final static int VALUE_EOL = -2000;
|
||||
final static int VALUE_FILL = -1000;
|
||||
final static int VALUE_PASSMODE = -3000;
|
||||
final static int VALUE_HMODE = -4000;
|
||||
|
||||
static {
|
||||
EOL = new Node();
|
||||
EOL.isLeaf = true;
|
||||
EOL.value = VALUE_EOL;
|
||||
FILL = new Node();
|
||||
FILL.value = VALUE_FILL;
|
||||
FILL.left = FILL;
|
||||
FILL.right = EOL;
|
||||
|
||||
eolOnlyTree = new Tree();
|
||||
try {
|
||||
eolOnlyTree.fill(12, 0, FILL);
|
||||
eolOnlyTree.fill(12, 1, EOL);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
blackRunTree = new Tree();
|
||||
try {
|
||||
for (int i = 0; i < BLACK_CODES.length; i++) {
|
||||
for (int j = 0; j < BLACK_CODES[i].length; j++) {
|
||||
blackRunTree.fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]);
|
||||
}
|
||||
}
|
||||
blackRunTree.fill(12, 0, FILL);
|
||||
blackRunTree.fill(12, 1, EOL);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
whiteRunTree = new Tree();
|
||||
try {
|
||||
for (int i = 0; i < WHITE_CODES.length; i++) {
|
||||
for (int j = 0; j < WHITE_CODES[i].length; j++) {
|
||||
whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
whiteRunTree.fill(12, 0, FILL);
|
||||
whiteRunTree.fill(12, 1, EOL);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
codeTree = new Tree();
|
||||
try {
|
||||
codeTree.fill(4, 1, VALUE_PASSMODE); // pass mode
|
||||
codeTree.fill(3, 1, VALUE_HMODE); // H mode
|
||||
codeTree.fill(1, 1, 0); // V(0)
|
||||
codeTree.fill(3, 3, 1); // V_R(1)
|
||||
codeTree.fill(6, 3, 2); // V_R(2)
|
||||
codeTree.fill(7, 3, 3); // V_R(3)
|
||||
codeTree.fill(3, 2, -1); // V_L(1)
|
||||
codeTree.fill(6, 2, -2); // V_L(2)
|
||||
codeTree.fill(7, 2, -3); // V_L(3)
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
/*
|
||||
* 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.tiff;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
||||
*
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CCITTFaxEncoderStream extends OutputStream {
|
||||
|
||||
private int currentBufferLength = 0;
|
||||
private final byte[] inputBuffer;
|
||||
private final int inputBufferLength;
|
||||
private int columns;
|
||||
private int rows;
|
||||
|
||||
private int[] changesCurrentRow;
|
||||
private int[] changesReferenceRow;
|
||||
private int currentRow = 0;
|
||||
private int changesCurrentRowLength = 0;
|
||||
private int changesReferenceRowLength = 0;
|
||||
private byte outputBuffer = 0;
|
||||
private byte outputBufferBitLength = 0;
|
||||
private int type;
|
||||
private int fillOrder;
|
||||
private boolean optionG32D;
|
||||
private boolean optionG3Fill;
|
||||
private boolean optionUncompressed;
|
||||
private OutputStream stream;
|
||||
|
||||
public CCITTFaxEncoderStream(final OutputStream stream, final int columns, final int rows, final int type, final int fillOrder,
|
||||
final long options) {
|
||||
|
||||
this.stream = stream;
|
||||
this.type = type;
|
||||
this.columns = columns;
|
||||
this.rows = rows;
|
||||
this.fillOrder = fillOrder;
|
||||
|
||||
this.changesReferenceRow = new int[columns];
|
||||
this.changesCurrentRow = new int[columns];
|
||||
|
||||
switch (type) {
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
||||
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
||||
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
||||
break;
|
||||
}
|
||||
|
||||
inputBufferLength = (columns + 7) / 8;
|
||||
inputBuffer = new byte[inputBufferLength];
|
||||
|
||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||
"CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
inputBuffer[currentBufferLength] = (byte) b;
|
||||
currentBufferLength++;
|
||||
|
||||
if (currentBufferLength == inputBufferLength) {
|
||||
encodeRow();
|
||||
currentBufferLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
stream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
private void encodeRow() throws IOException {
|
||||
currentRow++;
|
||||
int[] tmp = changesReferenceRow;
|
||||
changesReferenceRow = changesCurrentRow;
|
||||
changesCurrentRow = tmp;
|
||||
changesReferenceRowLength = changesCurrentRowLength;
|
||||
changesCurrentRowLength = 0;
|
||||
|
||||
int index = 0;
|
||||
boolean white = true;
|
||||
while (index < columns) {
|
||||
int byteIndex = index / 8;
|
||||
int bit = index % 8;
|
||||
if ((((inputBuffer[byteIndex] >> (7 - bit)) & 1) == 1) == (white)) {
|
||||
changesCurrentRow[changesCurrentRowLength] = index;
|
||||
changesCurrentRowLength++;
|
||||
white = !white;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||
encodeRowType2();
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
encodeRowType4();
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
encodeRowType6();
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentRow == rows) {
|
||||
if (type == TIFFExtension.COMPRESSION_CCITT_T6) {
|
||||
writeEOL();
|
||||
writeEOL();
|
||||
}
|
||||
fill();
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeRowType2() throws IOException {
|
||||
encode1D();
|
||||
fill();
|
||||
}
|
||||
|
||||
private void encodeRowType4() throws IOException {
|
||||
writeEOL();
|
||||
if (optionG32D) {
|
||||
// do k=1 only on first line. Detect first line by missing reference
|
||||
// line.
|
||||
if (changesReferenceRowLength == 0) {
|
||||
write(1, 1);
|
||||
encode1D();
|
||||
}
|
||||
else {
|
||||
write(0, 1);
|
||||
encode2D();
|
||||
}
|
||||
}
|
||||
else {
|
||||
encode1D();
|
||||
}
|
||||
if (optionG3Fill) {
|
||||
fill();
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeRowType6() throws IOException {
|
||||
encode2D();
|
||||
}
|
||||
|
||||
private void encode1D() throws IOException {
|
||||
int index = 0;
|
||||
boolean white = true;
|
||||
while (index < columns) {
|
||||
int[] nextChanges = getNextChanges(index, white);
|
||||
int runLength = nextChanges[0] - index;
|
||||
writeRun(runLength, white);
|
||||
index += runLength;
|
||||
white = !white;
|
||||
}
|
||||
}
|
||||
|
||||
private int[] getNextChanges(int pos, boolean white) {
|
||||
int[] result = new int[] {columns, columns};
|
||||
for (int i = 0; i < changesCurrentRowLength; i++) {
|
||||
if (pos < changesCurrentRow[i] || (pos == 0 && white)) {
|
||||
result[0] = changesCurrentRow[i];
|
||||
if ((i + 1) < changesCurrentRowLength) {
|
||||
result[1] = changesCurrentRow[i + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void writeRun(int runLength, boolean white) throws IOException {
|
||||
int nonterm = runLength / 64;
|
||||
Code[] codes = white ? WHITE_NONTERMINATING_CODES : BLACK_NONTERMINATING_CODES;
|
||||
while (nonterm > 0) {
|
||||
if (nonterm >= codes.length) {
|
||||
write(codes[codes.length - 1].code, codes[codes.length - 1].length);
|
||||
nonterm -= codes.length - 1;
|
||||
}
|
||||
else {
|
||||
write(codes[nonterm - 1].code, codes[nonterm - 1].length);
|
||||
nonterm = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Code c = white ? WHITE_TERMINATING_CODES[runLength % 64] : BLACK_TERMINATING_CODES[runLength % 64];
|
||||
write(c.code, c.length);
|
||||
}
|
||||
|
||||
private void encode2D() throws IOException {
|
||||
boolean white = true;
|
||||
int index = 0; // a0
|
||||
while (index < columns) {
|
||||
int[] nextChanges = getNextChanges(index, white); // a1, a2
|
||||
|
||||
int[] nextRefs = getNextRefChanges(index, white); // b1, b2
|
||||
|
||||
int difference = nextChanges[0] - nextRefs[0];
|
||||
if (nextChanges[0] > nextRefs[1]) {
|
||||
// PMODE
|
||||
write(1, 4);
|
||||
index = nextRefs[1];
|
||||
}
|
||||
else if (difference > 3 || difference < -3) {
|
||||
// HMODE
|
||||
write(1, 3);
|
||||
writeRun(nextChanges[0] - index, white);
|
||||
writeRun(nextChanges[1] - nextChanges[0], !white);
|
||||
index = nextChanges[1];
|
||||
|
||||
}
|
||||
else {
|
||||
// VMODE
|
||||
switch (difference) {
|
||||
case 0:
|
||||
write(1, 1);
|
||||
break;
|
||||
case 1:
|
||||
write(3, 3);
|
||||
break;
|
||||
case 2:
|
||||
write(3, 6);
|
||||
break;
|
||||
case 3:
|
||||
write(3, 7);
|
||||
break;
|
||||
case -1:
|
||||
write(2, 3);
|
||||
break;
|
||||
case -2:
|
||||
write(2, 6);
|
||||
break;
|
||||
case -3:
|
||||
write(2, 7);
|
||||
break;
|
||||
}
|
||||
white = !white;
|
||||
index = nextRefs[0] + difference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int[] getNextRefChanges(int a0, boolean white) {
|
||||
int[] result = new int[] {columns, columns};
|
||||
for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) {
|
||||
if (changesReferenceRow[i] > a0 || (a0 == 0 && i == 0)) {
|
||||
result[0] = changesReferenceRow[i];
|
||||
if ((i + 1) < changesReferenceRowLength) {
|
||||
result[1] = changesReferenceRow[i + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void write(int code, int codeLength) throws IOException {
|
||||
|
||||
for (int i = 0; i < codeLength; i++) {
|
||||
boolean codeBit = ((code >> (codeLength - i - 1)) & 1) == 1;
|
||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||
outputBuffer |= (codeBit ? 1 << (7 - ((outputBufferBitLength) % 8)) : 0);
|
||||
}
|
||||
else {
|
||||
outputBuffer |= (codeBit ? 1 << (((outputBufferBitLength) % 8)) : 0);
|
||||
}
|
||||
outputBufferBitLength++;
|
||||
|
||||
if (outputBufferBitLength == 8) {
|
||||
stream.write(outputBuffer);
|
||||
clearOutputBuffer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeEOL() throws IOException {
|
||||
if (optionG3Fill) {
|
||||
// Fill up so EOL ends on a byte-boundary
|
||||
while (outputBufferBitLength != 4) {
|
||||
write(0, 1);
|
||||
}
|
||||
}
|
||||
write(1, 12);
|
||||
}
|
||||
|
||||
private void fill() throws IOException {
|
||||
if (outputBufferBitLength != 0) {
|
||||
stream.write(outputBuffer);
|
||||
}
|
||||
clearOutputBuffer();
|
||||
}
|
||||
|
||||
private void clearOutputBuffer() {
|
||||
outputBuffer = 0;
|
||||
outputBufferBitLength = 0;
|
||||
}
|
||||
|
||||
public static class Code {
|
||||
private Code(int code, int length) {
|
||||
this.code = code;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
final int code;
|
||||
final int length;
|
||||
}
|
||||
|
||||
public static final Code[] WHITE_TERMINATING_CODES;
|
||||
|
||||
public static final Code[] WHITE_NONTERMINATING_CODES;
|
||||
|
||||
public static final Code[] BLACK_TERMINATING_CODES;
|
||||
|
||||
public static final Code[] BLACK_NONTERMINATING_CODES;
|
||||
|
||||
static {
|
||||
// Setup HUFFMAN Codes
|
||||
WHITE_TERMINATING_CODES = new Code[64];
|
||||
WHITE_NONTERMINATING_CODES = new Code[40];
|
||||
for (int i = 0; i < CCITTFaxDecoderStream.WHITE_CODES.length; i++) {
|
||||
int bitLength = i + 4;
|
||||
for (int j = 0; j < CCITTFaxDecoderStream.WHITE_CODES[i].length; j++) {
|
||||
int value = CCITTFaxDecoderStream.WHITE_RUN_LENGTHS[i][j];
|
||||
int code = CCITTFaxDecoderStream.WHITE_CODES[i][j];
|
||||
|
||||
if (value < 64) {
|
||||
WHITE_TERMINATING_CODES[value] = new Code(code, bitLength);
|
||||
}
|
||||
else {
|
||||
WHITE_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BLACK_TERMINATING_CODES = new Code[64];
|
||||
BLACK_NONTERMINATING_CODES = new Code[40];
|
||||
for (int i = 0; i < CCITTFaxDecoderStream.BLACK_CODES.length; i++) {
|
||||
int bitLength = i + 2;
|
||||
for (int j = 0; j < CCITTFaxDecoderStream.BLACK_CODES[i].length; j++) {
|
||||
int value = CCITTFaxDecoderStream.BLACK_RUN_LENGTHS[i][j];
|
||||
int code = CCITTFaxDecoderStream.BLACK_CODES[i][j];
|
||||
|
||||
if (value < 64) {
|
||||
BLACK_TERMINATING_CODES[value] = new Code(code, bitLength);
|
||||
}
|
||||
else {
|
||||
BLACK_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -190,7 +190,6 @@ abstract class LZWDecoder implements Decoder {
|
||||
|
||||
public static Decoder create(boolean oldBitReversedStream) {
|
||||
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
||||
// return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWTreeDecoder();
|
||||
}
|
||||
|
||||
static final class LZWSpecDecoder extends LZWDecoder {
|
||||
|
||||
+109
-110
@@ -33,21 +33,22 @@ import com.twelvemonkeys.io.enc.Encoder;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.tiff.LZWDecoder.LZWString;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* LZWEncoder
|
||||
* <p/>
|
||||
* Inspired by LZWTreeEncoder by <a href="mailto:yuwen_66@yahoo.com">Wen Yu</a> and the
|
||||
* <a href="http://gingko.homeip.net/docs/file_formats/lzwgif.html#bob">algorithm described by Bob Montgomery</a>
|
||||
* which
|
||||
* "[...] uses a tree method to search if a new string is already in the table,
|
||||
* which is much simpler, faster, and easier to understand than hashing."
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: LZWEncoder.java,v 1.0 02.12.13 14:13 haraldk Exp$
|
||||
*/
|
||||
final class LZWEncoder implements Encoder {
|
||||
// TODO: Consider extracting LZWStringTable from LZWDecoder
|
||||
|
||||
/** Clear: Re-initialize tables. */
|
||||
static final int CLEAR_CODE = 256;
|
||||
/** End of Information. */
|
||||
@@ -58,16 +59,18 @@ final class LZWEncoder implements Encoder {
|
||||
|
||||
private static final int TABLE_SIZE = 1 << MAX_BITS;
|
||||
|
||||
private final LZWString[] table = new LZWString[TABLE_SIZE];
|
||||
private final Map<LZWString, Integer> reverseTable = new TreeMap<>(); // This is foobar
|
||||
// private final Map<LZWString, Integer> reverseTable = new HashMap<>(TABLE_SIZE); // This is foobar
|
||||
private int tableLength;
|
||||
LZWString omega = LZWString.EMPTY;
|
||||
// A child is made up of a parent (or prefix) code plus a suffix byte
|
||||
// and siblings are strings with a common parent(or prefix) and different
|
||||
// suffix bytes
|
||||
private final short[] CHILDREN = new short[TABLE_SIZE];
|
||||
private final short[] SIBLINGS = new short[TABLE_SIZE];
|
||||
private final short[] SUFFIXES = new short[TABLE_SIZE];
|
||||
|
||||
int bitsPerCode;
|
||||
private int oldCode = CLEAR_CODE;
|
||||
private int maxCode;
|
||||
int bitMask;
|
||||
// Initial setup
|
||||
private int parent = -1;
|
||||
private int bitsPerCode = MIN_BITS;
|
||||
private int nextValidCode = EOI_CODE + 1;
|
||||
private int maxCode = maxValue(bitsPerCode);
|
||||
|
||||
// Buffer for partial codes
|
||||
private int bits = 0;
|
||||
@@ -76,120 +79,116 @@ final class LZWEncoder implements Encoder {
|
||||
// Keep track of how many bytes we will write, to make sure we write EOI at correct position
|
||||
private long remaining;
|
||||
|
||||
protected LZWEncoder(final int length) {
|
||||
this.remaining = length;
|
||||
|
||||
// First 258 entries of table is always fixed
|
||||
for (int i = 0; i < 256; i++) {
|
||||
table[i] = new LZWString((byte) i);
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
tableLength = 258;
|
||||
bitsPerCode = MIN_BITS;
|
||||
bitMask = bitmaskFor(bitsPerCode);
|
||||
maxCode = maxCode();
|
||||
reverseTable.clear();
|
||||
LZWEncoder(final long length) {
|
||||
remaining = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
// InitializeStringTable();
|
||||
// WriteCode(ClearCode);
|
||||
// Ω = the empty string;
|
||||
// for each character in the strip {
|
||||
// K = GetNextCharacter();
|
||||
// if Ω+K is in the string table {
|
||||
// Ω = Ω+K;/* string concatenation */
|
||||
// }
|
||||
// else{
|
||||
// WriteCode (CodeFromString( Ω));
|
||||
// AddTableEntry(Ω+K);
|
||||
// Ω=K;
|
||||
// } }/*end of for loop*/
|
||||
// WriteCode (CodeFromString(Ω));
|
||||
// WriteCode (EndOfInformation);
|
||||
encodeBytes(stream, buffer);
|
||||
|
||||
if (remaining < 0) {
|
||||
throw new IOException("Write past end of stream");
|
||||
}
|
||||
|
||||
// TODO: Write 9 bit clear code ONLY first time!
|
||||
if (oldCode == CLEAR_CODE) {
|
||||
writeCode(stream, CLEAR_CODE);
|
||||
}
|
||||
|
||||
int len = buffer.remaining();
|
||||
|
||||
while (buffer.hasRemaining()) {
|
||||
byte k = buffer.get();
|
||||
|
||||
LZWString string = omega.concatenate(k);
|
||||
|
||||
int tableIndex = isInTable(string);
|
||||
if (tableIndex >= 0) {
|
||||
omega = string;
|
||||
oldCode = tableIndex;
|
||||
}
|
||||
else {
|
||||
writeCode(stream, oldCode);
|
||||
addStringToTable(string);
|
||||
oldCode = k & 0xff;
|
||||
omega = table[k & 0xff];
|
||||
|
||||
// Handle table (almost) full
|
||||
if (tableLength >= TABLE_SIZE - 2) {
|
||||
writeCode(stream, CLEAR_CODE);
|
||||
init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remaining -= len;
|
||||
|
||||
// Write EOI when er are done (the API isn't very supportive of this)
|
||||
if (remaining <= 0) {
|
||||
writeCode(stream, oldCode);
|
||||
// Write EOI when er are done (the API isn't very supportive of this at the moment)
|
||||
writeCode(stream, parent);
|
||||
writeCode(stream, EOI_CODE);
|
||||
|
||||
// Flush partial codes by writing 0 pad
|
||||
if (bitPos > 0) {
|
||||
writeCode(stream, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int isInTable(final LZWString string) {
|
||||
if (string.length == 1) {
|
||||
return string.value & 0xff;
|
||||
void encodeBytes(final OutputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
int length = buffer.remaining();
|
||||
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer index = reverseTable.get(string);
|
||||
return index != null ? index : -1;
|
||||
if (parent == -1) {
|
||||
// Init stream
|
||||
writeCode(stream, CLEAR_CODE);
|
||||
parent = buffer.get() & 0xff;
|
||||
}
|
||||
|
||||
while (buffer.hasRemaining()) {
|
||||
int value = buffer.get() & 0xff;
|
||||
int child = CHILDREN[parent];
|
||||
|
||||
if (child > 0) {
|
||||
if (SUFFIXES[child] == value) {
|
||||
parent = child;
|
||||
}
|
||||
else {
|
||||
int sibling = child;
|
||||
|
||||
while (true) {
|
||||
if (SIBLINGS[sibling] > 0) {
|
||||
sibling = SIBLINGS[sibling];
|
||||
|
||||
if (SUFFIXES[sibling] == value) {
|
||||
parent = sibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
SIBLINGS[sibling] = (short) nextValidCode;
|
||||
SUFFIXES[nextValidCode] = (short) value;
|
||||
writeCode(stream, parent);
|
||||
parent = value;
|
||||
nextValidCode++;
|
||||
|
||||
increaseCodeSizeOrResetIfNeeded(stream);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
CHILDREN[parent] = (short) nextValidCode;
|
||||
SUFFIXES[nextValidCode] = (short) value;
|
||||
writeCode(stream, parent);
|
||||
parent = value;
|
||||
nextValidCode++;
|
||||
|
||||
increaseCodeSizeOrResetIfNeeded(stream);
|
||||
}
|
||||
}
|
||||
|
||||
remaining -= length;
|
||||
}
|
||||
|
||||
private int addStringToTable(final LZWString string) {
|
||||
final int index = tableLength++;
|
||||
table[index] = string;
|
||||
reverseTable.put(string, index);
|
||||
private void increaseCodeSizeOrResetIfNeeded(final OutputStream stream) throws IOException {
|
||||
if (nextValidCode > maxCode) {
|
||||
if (bitsPerCode == MAX_BITS) {
|
||||
// Reset stream by writing Clear code
|
||||
writeCode(stream, CLEAR_CODE);
|
||||
|
||||
if (tableLength > maxCode) {
|
||||
bitsPerCode++;
|
||||
|
||||
if (bitsPerCode > MAX_BITS) {
|
||||
throw new IllegalStateException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
|
||||
// Reset tables
|
||||
resetTables();
|
||||
}
|
||||
else {
|
||||
// Increase code size
|
||||
bitsPerCode++;
|
||||
maxCode = maxValue(bitsPerCode);
|
||||
}
|
||||
|
||||
bitMask = bitmaskFor(bitsPerCode);
|
||||
maxCode = maxCode();
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
private void resetTables() {
|
||||
Arrays.fill(CHILDREN, (short) 0);
|
||||
Arrays.fill(SIBLINGS, (short) 0);
|
||||
|
||||
bitsPerCode = MIN_BITS;
|
||||
maxCode = maxValue(bitsPerCode);
|
||||
nextValidCode = EOI_CODE + 1;
|
||||
}
|
||||
|
||||
private void writeCode(final OutputStream stream, final int code) throws IOException {
|
||||
// System.err.printf("LZWEncoder.writeCode: 0x%04x\n", code);
|
||||
bits = (bits << bitsPerCode) | (code & bitMask);
|
||||
bits = (bits << bitsPerCode) | (code & maxCode);
|
||||
bitPos += bitsPerCode;
|
||||
|
||||
while (bitPos >= 8) {
|
||||
@@ -202,11 +201,11 @@ final class LZWEncoder implements Encoder {
|
||||
bits &= bitmaskFor(bitPos);
|
||||
}
|
||||
|
||||
private static int bitmaskFor(final int bits) {
|
||||
return (1 << bits) - 1;
|
||||
private static int maxValue(final int codeLen) {
|
||||
return (1 << codeLen) - 1;
|
||||
}
|
||||
|
||||
protected int maxCode() {
|
||||
return bitMask;
|
||||
private static int bitmaskFor(final int bits) {
|
||||
return maxValue(bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -59,4 +59,13 @@ interface TIFFBaseline {
|
||||
int RESOLUTION_UNIT_NONE = 1;
|
||||
int RESOLUTION_UNIT_DPI = 2; // Default
|
||||
int RESOLUTION_UNIT_CENTIMETER = 3;
|
||||
|
||||
int FILL_LEFT_TO_RIGHT = 1; // Default
|
||||
|
||||
// NOTE: These are bit flags that can be ORed together!
|
||||
int FILETYPE_REDUCEDIMAGE = 1;
|
||||
int FILETYPE_PAGE = 2;
|
||||
int FILETYPE_MASK = 4;
|
||||
|
||||
int ORIENTATION_TOPLEFT = 1;
|
||||
}
|
||||
|
||||
+15
@@ -62,6 +62,8 @@ interface TIFFExtension {
|
||||
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
|
||||
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
|
||||
|
||||
int FILL_RIGHT_TO_LEFT = 2;
|
||||
|
||||
int SAMPLEFORMAT_INT = 2;
|
||||
int SAMPLEFORMAT_FP = 3;
|
||||
int SAMPLEFORMAT_UNDEFINED = 4;
|
||||
@@ -83,4 +85,17 @@ interface TIFFExtension {
|
||||
* description of the inks to be used.
|
||||
*/
|
||||
int INKSET_NOT_CMYK = 2;
|
||||
|
||||
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;
|
||||
|
||||
int GROUP3OPT_2DENCODING = 1;
|
||||
int GROUP3OPT_UNCOMPRESSED = 2;
|
||||
int GROUP3OPT_FILLBITS = 4;
|
||||
int GROUP4OPT_UNCOMPRESSED = 2;
|
||||
}
|
||||
|
||||
+1308
File diff suppressed because it is too large
Load Diff
+722
-223
File diff suppressed because it is too large
Load Diff
+10
-1
@@ -65,7 +65,7 @@ public final class TIFFImageWriteParam extends ImageWriteParam {
|
||||
// See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html
|
||||
compressionTypes = new String[] {
|
||||
"None",
|
||||
null, null, null,/* "CCITT RLE", "CCITT T.4", "CCITT T.6", */
|
||||
"CCITT RLE", "CCITT T.4", "CCITT T.6",
|
||||
"LZW", "JPEG", "ZLib", "PackBits", "Deflate",
|
||||
null/* "EXIF JPEG" */ // A well-defined form of "Old-style JPEG", no tables/process, only 513 (offset) and 514 (length)
|
||||
};
|
||||
@@ -111,6 +111,15 @@ public final class TIFFImageWriteParam extends ImageWriteParam {
|
||||
else if (param.getCompressionType().equals("JPEG")) {
|
||||
return TIFFExtension.COMPRESSION_JPEG;
|
||||
}
|
||||
else if (param.getCompressionType().equals("CCITT RLE")) {
|
||||
return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
|
||||
}
|
||||
else if (param.getCompressionType().equals("CCITT T.4")) {
|
||||
return TIFFExtension.COMPRESSION_CCITT_T4;
|
||||
}
|
||||
else if (param.getCompressionType().equals("CCITT T.6")) {
|
||||
return TIFFExtension.COMPRESSION_CCITT_T6;
|
||||
}
|
||||
// else if (param.getCompressionType().equals("EXIF JPEG")) {
|
||||
// return TIFFExtension.COMPRESSION_OLD_JPEG;
|
||||
// }
|
||||
|
||||
+276
-63
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
@@ -39,9 +40,12 @@ import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
@@ -50,10 +54,9 @@ import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
@@ -65,13 +68,17 @@ import java.util.zip.DeflaterOutputStream;
|
||||
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
||||
*/
|
||||
public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// Short term
|
||||
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
||||
|
||||
// Long term
|
||||
// TODO: Support writing multipage TIFFs using canWriteSequence/prepareWriteSequence/writeToSequence/endWriteSequence
|
||||
// TODO: Support tiling
|
||||
// TODO: Support thumbnails
|
||||
// TODO: Support ImageIO metadata
|
||||
// TODO: Support CCITT Modified Huffman compression (2)
|
||||
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
|
||||
// TODO: CCITT compressions T.4 and T.6
|
||||
// TODO: Support JPEG compression of CMYK data (pending JPEGImageWriter CMYK write support)
|
||||
// ----
|
||||
// TODO: Support storing multiple images in one stream (multi-page TIFF)
|
||||
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
|
||||
@@ -91,6 +98,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// Support LZW compression (5)
|
||||
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
|
||||
// Use sensible defaults for compression based on input? None is sensible... :-)
|
||||
// Support resolution, resolution unit and software tags from ImageIO metadata
|
||||
|
||||
public static final Rational STANDARD_DPI = new Rational(72);
|
||||
|
||||
@@ -98,14 +106,81 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(final Object output) {
|
||||
super.setOutput(output);
|
||||
|
||||
// TODO: Allow appending/partly overwrite of existing file...
|
||||
}
|
||||
|
||||
static final class TIFFEntry extends AbstractEntry {
|
||||
TIFFEntry(Object identifier, Object value) {
|
||||
// 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()));
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
||||
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
// TODO: Validate input
|
||||
|
||||
assertOutput();
|
||||
@@ -120,13 +195,22 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
ColorModel colorModel = renderedImage.getColorModel();
|
||||
int numComponents = colorModel.getNumComponents();
|
||||
|
||||
//TODO: streamMetadata?
|
||||
TIFFImageMetadata metadata;
|
||||
if (image.getMetadata() != null) {
|
||||
metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||
}
|
||||
else {
|
||||
metadata = initMeta(null, ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
|
||||
}
|
||||
|
||||
SampleModel sampleModel = renderedImage.getSampleModel();
|
||||
|
||||
int[] bandOffsets;
|
||||
int[] bitOffsets;
|
||||
if (sampleModel instanceof ComponentSampleModel) {
|
||||
bandOffsets = ((ComponentSampleModel) sampleModel).getBandOffsets();
|
||||
bitOffsets = null;
|
||||
bitOffsets = null;
|
||||
}
|
||||
else if (sampleModel instanceof SinglePixelPackedSampleModel) {
|
||||
bitOffsets = ((SinglePixelPackedSampleModel) sampleModel).getBitOffsets();
|
||||
@@ -140,88 +224,131 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
||||
}
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
||||
Map<Integer, Entry> entries = new LinkedHashMap<>();
|
||||
entries.put(TIFF.TAG_IMAGE_WIDTH, new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
|
||||
entries.put(TIFF.TAG_IMAGE_HEIGHT, new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
|
||||
// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
|
||||
entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, asShortArray(sampleModel.getSampleSize())));
|
||||
entries.put(TIFF.TAG_BITS_PER_SAMPLE, new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, asShortArray(sampleModel.getSampleSize())));
|
||||
// If numComponents > numColorComponents, write ExtraSamples
|
||||
if (numComponents > colorModel.getNumColorComponents()) {
|
||||
// TODO: Write per component > numColorComponents
|
||||
if (colorModel.hasAlpha()) {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, colorModel.isAlphaPremultiplied() ? TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA : TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA));
|
||||
entries.put(TIFF.TAG_EXTRA_SAMPLES, new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, colorModel.isAlphaPremultiplied()
|
||||
? TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA
|
||||
: TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA));
|
||||
}
|
||||
else {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED));
|
||||
entries.put(TIFF.TAG_EXTRA_SAMPLES, new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED));
|
||||
}
|
||||
}
|
||||
|
||||
// Write compression field from param or metadata
|
||||
int compression = TIFFImageWriteParam.getCompressionType(param);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
||||
int compression;
|
||||
if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA)
|
||||
&& image.getMetadata() != null && metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION) != null) {
|
||||
compression = (int) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue();
|
||||
}
|
||||
else {
|
||||
compression = TIFFImageWriteParam.getCompressionType(param);
|
||||
}
|
||||
entries.put(TIFF.TAG_COMPRESSION, new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
|
||||
|
||||
// TODO: Let param/metadata control predictor
|
||||
// TODO: Depending on param.getCompressionMode(): DISABLED/EXPLICIT/COPY_FROM_METADATA/DEFAULT
|
||||
switch (compression) {
|
||||
case TIFFExtension.COMPRESSION_ZLIB:
|
||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||
case TIFFExtension.COMPRESSION_LZW:
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PREDICTOR, TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING));
|
||||
entries.put(TIFF.TAG_PREDICTOR, new TIFFEntry(TIFF.TAG_PREDICTOR, TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING));
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
Entry group3options = metadata.getIFD().getEntryById(TIFF.TAG_GROUP3OPTIONS);
|
||||
if (group3options == null) {
|
||||
group3options = new TIFFEntry(TIFF.TAG_GROUP3OPTIONS, (long) TIFFExtension.GROUP3OPT_2DENCODING);
|
||||
}
|
||||
entries.put(TIFF.TAG_GROUP3OPTIONS, group3options);
|
||||
break;
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
Entry group4options = metadata.getIFD().getEntryById(TIFF.TAG_GROUP4OPTIONS);
|
||||
if (group4options == null) {
|
||||
group4options = new TIFFEntry(TIFF.TAG_GROUP4OPTIONS, 0L);
|
||||
}
|
||||
entries.put(TIFF.TAG_GROUP4OPTIONS, group4options);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
// TODO: We might want to support CMYK in JPEG as well...
|
||||
// TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support.
|
||||
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
|
||||
TIFFExtension.PHOTOMETRIC_YCBCR :
|
||||
getPhotometricInterpretation(colorModel);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
|
||||
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
|
||||
|
||||
if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
|
||||
entries.put(TIFF.TAG_COLOR_MAP, new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
|
||||
entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
|
||||
}
|
||||
else {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents));
|
||||
if (colorModel.getPixelSize() == 1) {
|
||||
numComponents = 1;
|
||||
}
|
||||
|
||||
entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents));
|
||||
|
||||
// Note: Assuming sRGB to be the default RGB interpretation
|
||||
ColorSpace colorSpace = colorModel.getColorSpace();
|
||||
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB()) {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
|
||||
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
||||
entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
||||
// Default sample format SAMPLEFORMAT_UINT need not be written
|
||||
if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT/* TODO: if isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
|
||||
entries.put(TIFF.TAG_SAMPLE_FORMAT, new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
|
||||
}
|
||||
// TODO: Float values!
|
||||
|
||||
entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer")); // TODO: Get from metadata (optional) + fill in version number
|
||||
// Get Software from metadata, or use default
|
||||
Entry software = metadata.getIFD().getEntryById(TIFF.TAG_SOFTWARE);
|
||||
entries.put(TIFF.TAG_SOFTWARE, software != null
|
||||
? software
|
||||
: new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer " + originatingProvider.getVersion()));
|
||||
|
||||
entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||
// Get X/YResolution and ResolutionUnit from metadata if set, otherwise use defaults
|
||||
// TODO: Add logic here OR in metadata merging, to make sure these 3 values are consistent.
|
||||
Entry xRes = metadata.getIFD().getEntryById(TIFF.TAG_X_RESOLUTION);
|
||||
entries.put(TIFF.TAG_X_RESOLUTION, xRes != null ? xRes : new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
|
||||
Entry yRes = metadata.getIFD().getEntryById(TIFF.TAG_Y_RESOLUTION);
|
||||
entries.put(TIFF.TAG_Y_RESOLUTION, yRes != null ? yRes : new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
|
||||
Entry resUnit = metadata.getIFD().getEntryById(TIFF.TAG_RESOLUTION_UNIT);
|
||||
entries.put(TIFF.TAG_RESOLUTION_UNIT,
|
||||
resUnit != null ? resUnit : new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||
|
||||
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
||||
entries.put(TIFF.TAG_ROWS_PER_STRIP, new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
|
||||
// - StripByteCounts - for no compression, entire image data... (TODO: How to know the byte counts prior to writing data?)
|
||||
TIFFEntry dummyStripByteCounts = new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, -1);
|
||||
entries.add(dummyStripByteCounts); // Updated later
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, dummyStripByteCounts); // Updated later
|
||||
// - StripOffsets - can be offset to single strip only (TODO: but how large is the IFD data...???)
|
||||
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
|
||||
entries.add(dummyStripOffsets); // Updated later
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, dummyStripOffsets); // Updated later
|
||||
|
||||
// TODO: If tiled, write tile indexes etc, or always do that?
|
||||
// TODO: If tiled, write tile indexes etc
|
||||
// Depending on param.getTilingMode
|
||||
|
||||
EXIFWriter exifWriter = new EXIFWriter();
|
||||
|
||||
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
||||
// This implementation, allows semi-streaming-compatible uncompressed TIFFs
|
||||
long streamOffset = exifWriter.computeIFDSize(entries) + 12; // 12 == 4 byte magic, 4 byte IDD 0 pointer, 4 byte EOF
|
||||
long streamOffset = exifWriter.computeIFDSize(entries.values()) + 12; // 12 == 4 byte magic, 4 byte IDD 0 pointer, 4 byte EOF
|
||||
|
||||
entries.remove(dummyStripByteCounts);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, renderedImage.getWidth() * renderedImage.getHeight() * numComponents));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS,
|
||||
renderedImage.getWidth() * renderedImage.getHeight() * numComponents));
|
||||
entries.remove(dummyStripOffsets);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, streamOffset));
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, streamOffset));
|
||||
|
||||
exifWriter.write(entries, imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
exifWriter.write(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
imageOutput.flush();
|
||||
}
|
||||
else {
|
||||
@@ -233,6 +360,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// TODO: Create compressor stream per Tile/Strip
|
||||
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("JPEG");
|
||||
|
||||
if (!writers.hasNext()) {
|
||||
// This can only happen if someone deliberately uninstalled it
|
||||
throw new IIOException("No JPEG ImageWriter found!");
|
||||
@@ -249,7 +377,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
else {
|
||||
// Write image data
|
||||
writeImageData(createCompressorStream(renderedImage, param), renderedImage, numComponents, bandOffsets, bitOffsets);
|
||||
writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets,
|
||||
bitOffsets);
|
||||
}
|
||||
|
||||
// Update IFD0-pointer, and write IFD
|
||||
@@ -257,11 +386,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
long streamPosition = imageOutput.getStreamPosition();
|
||||
|
||||
entries.remove(dummyStripOffsets);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, 8));
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, 8));
|
||||
entries.remove(dummyStripByteCounts);
|
||||
entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, streamPosition - 8));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, streamPosition - 8));
|
||||
|
||||
long ifdOffset = exifWriter.writeIFD(entries, imageOutput);
|
||||
long ifdOffset = exifWriter.writeIFD(entries.values(), imageOutput);
|
||||
imageOutput.writeInt(0); // Next IFD (none)
|
||||
streamPosition = imageOutput.getStreamPosition();
|
||||
|
||||
@@ -273,7 +402,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
}
|
||||
|
||||
private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param) {
|
||||
private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param, Map<Integer, Entry> entries) {
|
||||
/*
|
||||
36 MB test data:
|
||||
|
||||
@@ -327,7 +456,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
// Use predictor by default for LZW and ZLib/Deflate
|
||||
// TODO: Unless explicitly disabled in TIFFImageWriteParam
|
||||
int compression = TIFFImageWriteParam.getCompressionType(param);
|
||||
int compression = (int) entries.get(TIFF.TAG_COMPRESSION).getValue();
|
||||
OutputStream stream;
|
||||
|
||||
switch (compression) {
|
||||
@@ -370,8 +499,27 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
case TIFFExtension.COMPRESSION_LZW:
|
||||
stream = IIOUtil.createStreamAdapter(imageOutput);
|
||||
stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight() * image.getTile(0, 0).getNumBands() * image.getColorModel().getComponentSize(0) + 7) / 8));
|
||||
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
|
||||
stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight()
|
||||
* image.getTile(0, 0).getNumBands() * image.getColorModel().getComponentSize(0) + 7) / 8));
|
||||
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(),
|
||||
image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
|
||||
|
||||
return new DataOutputStream(stream);
|
||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
long option = 0L;
|
||||
if (compression != TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE) {
|
||||
option = (long) entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4
|
||||
? TIFF.TAG_GROUP3OPTIONS
|
||||
: TIFF.TAG_GROUP4OPTIONS).getValue();
|
||||
}
|
||||
Entry fillOrderEntry = entries.get(TIFF.TAG_FILL_ORDER);
|
||||
int fillOrder = (int) (fillOrderEntry != null
|
||||
? fillOrderEntry.getValue()
|
||||
: TIFFBaseline.FILL_LEFT_TO_RIGHT);
|
||||
stream = IIOUtil.createStreamAdapter(imageOutput);
|
||||
stream = new CCITTFaxEncoderStream(stream, image.getTileWidth(), image.getTileHeight(), compression, fillOrder, option);
|
||||
|
||||
return new DataOutputStream(stream);
|
||||
}
|
||||
@@ -380,12 +528,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
private int getPhotometricInterpretation(final ColorModel colorModel) {
|
||||
if (colorModel.getNumComponents() == 1 && colorModel.getComponentSize(0) == 1) {
|
||||
if (colorModel.getPixelSize() == 1) {
|
||||
if (colorModel instanceof IndexColorModel) {
|
||||
if (colorModel.getRGB(0) == 0xFFFFFF && colorModel.getRGB(1) == 0x000000) {
|
||||
if (colorModel.getRGB(0) == 0xFFFFFFFF && colorModel.getRGB(1) == 0xFF000000) {
|
||||
return TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
|
||||
}
|
||||
else if (colorModel.getRGB(0) != 0x000000 || colorModel.getRGB(1) != 0xFFFFFF) {
|
||||
else if (colorModel.getRGB(0) != 0xFF000000 || colorModel.getRGB(1) != 0xFFFFFFFF) {
|
||||
return TIFFBaseline.PHOTOMETRIC_PALETTE;
|
||||
}
|
||||
// Else, fall through to default, BLACK_IS_ZERO
|
||||
@@ -418,9 +566,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
for (int i = 0; i < colorModel.getMapSize(); i++) {
|
||||
int color = colorModel.getRGB(i);
|
||||
colorMap[i ] = (short) upScale((color >> 16) & 0xff);
|
||||
colorMap[i + colorMap.length / 3] = (short) upScale((color >> 8) & 0xff);
|
||||
colorMap[i + 2 * colorMap.length / 3] = (short) upScale((color ) & 0xff);
|
||||
colorMap[i] = (short) upScale((color >> 16) & 0xff);
|
||||
colorMap[i + colorMap.length / 3] = (short) upScale((color >> 8) & 0xff);
|
||||
colorMap[i + 2 * colorMap.length / 3] = (short) upScale((color) & 0xff);
|
||||
}
|
||||
|
||||
return colorMap;
|
||||
@@ -460,16 +608,20 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
// TODO: SampleSize may differ between bands/banks
|
||||
int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
|
||||
|
||||
// System.err.println("tileWidth: " + tileWidth);
|
||||
final ByteBuffer buffer;
|
||||
if (sampleSize == 1) {
|
||||
buffer = ByteBuffer.allocate((tileWidth + 7) / 8);
|
||||
}
|
||||
else {
|
||||
buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
|
||||
}
|
||||
// System.err.println("tileWidth: " + tileWidth);
|
||||
|
||||
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
|
||||
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
|
||||
final Raster tile = renderedImage.getTile(xTile, yTile);
|
||||
final DataBuffer dataBuffer = tile.getDataBuffer();
|
||||
final int numBands = tile.getNumBands();
|
||||
// final SampleModel sampleModel = tile.getSampleModel();
|
||||
|
||||
switch (dataBuffer.getDataType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
@@ -477,9 +629,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
|
||||
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
||||
for (int y = 0; y < tileHeight; y++) {
|
||||
final int yOff = y * tileWidth * numBands;
|
||||
int steps = sampleSize == 1 ? (tileWidth + 7) / 8 : tileWidth;
|
||||
final int yOff = y * steps * numBands;
|
||||
|
||||
for (int x = 0; x < tileWidth; x++) {
|
||||
for (int x = 0; x < steps; x++) {
|
||||
final int xOff = yOff + x * numBands;
|
||||
|
||||
for (int s = 0; s < numBands; s++) {
|
||||
@@ -607,13 +760,75 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// Metadata
|
||||
|
||||
@Override
|
||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||
return null;
|
||||
public TIFFImageMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
return initMeta(null, imageType, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||
return null;
|
||||
public TIFFImageMetadata convertImageMetadata(final IIOMetadata inData,
|
||||
final ImageTypeSpecifier imageType,
|
||||
final ImageWriteParam param) {
|
||||
Validate.notNull(inData, "inData");
|
||||
Validate.notNull(imageType, "imageType");
|
||||
|
||||
Directory ifd;
|
||||
|
||||
if (inData instanceof TIFFImageMetadata) {
|
||||
ifd = ((TIFFImageMetadata) inData).getIFD();
|
||||
}
|
||||
else {
|
||||
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||
|
||||
try {
|
||||
if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
|
||||
outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
|
||||
}
|
||||
else if (inData.isStandardMetadataFormatSupported()) {
|
||||
outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
|
||||
}
|
||||
else {
|
||||
// Unknown format, we can't convert it
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (IIOInvalidTreeException e) {
|
||||
// TODO: How to issue warning when warning requires imageIndex??? Use -1?
|
||||
}
|
||||
|
||||
ifd = outData.getIFD();
|
||||
}
|
||||
|
||||
// Overwrite in values with values from imageType and param as needed
|
||||
return initMeta(ifd, imageType, param);
|
||||
}
|
||||
|
||||
private TIFFImageMetadata initMeta(final Directory ifd, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
Validate.notNull(imageType, "imageType");
|
||||
|
||||
Map<Integer, Entry> entries = new LinkedHashMap<>(ifd != null ? ifd.size() + 10 : 20);
|
||||
|
||||
if (ifd != null) {
|
||||
for (Entry entry : ifd) {
|
||||
entries.put((Integer) entry.getIdentifier(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Set values from imageType
|
||||
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel())));
|
||||
|
||||
// TODO: Set values from param if != null + combined values...
|
||||
|
||||
return new TIFFImageMetadata(entries.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
|
||||
return super.getDefaultStreamMetadata(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata convertStreamMetadata(final IIOMetadata inData, final ImageWriteParam param) {
|
||||
return super.convertStreamMetadata(inData, param);
|
||||
}
|
||||
|
||||
// Param
|
||||
@@ -629,7 +844,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
int argIdx = 0;
|
||||
|
||||
// TODO: Proper argument parsing: -t <type> -c <compression>
|
||||
int type = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : -1;
|
||||
int type = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : -1;
|
||||
int compression = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : 0;
|
||||
|
||||
if (args.length <= argIdx) {
|
||||
@@ -754,7 +969,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// }
|
||||
// writer.dispose();
|
||||
|
||||
|
||||
image = null;
|
||||
|
||||
BufferedImage read = ImageIO.read(output);
|
||||
@@ -762,5 +976,4 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
TIFFImageReader.showIt(read, output.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
|
||||
/**
|
||||
* TIFFMedataFormat.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: TIFFMedataFormat.java,v 1.0 17/04/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class TIFFMedataFormat extends IIOMetadataFormatImpl {
|
||||
private static final TIFFMedataFormat INSTANCE = new TIFFMedataFormat();
|
||||
|
||||
// We'll reuse the metadata formats defined for JAI
|
||||
public static final String SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_image_1.0";
|
||||
public static final String SUN_NATIVE_STREAM_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_stream_1.0";
|
||||
|
||||
public TIFFMedataFormat() {
|
||||
super(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TIFFMedataFormat getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -41,7 +41,7 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
|
||||
protected TIFFProviderInfo() {
|
||||
super(
|
||||
TIFFProviderInfo.class,
|
||||
new String[] {"tiff", "TIFF"},
|
||||
new String[] {"tiff", "TIFF", "tif", "TIF"},
|
||||
new String[] {"tif", "tiff"},
|
||||
new String[] {
|
||||
"image/tiff", "image/x-tiff"
|
||||
@@ -50,8 +50,8 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"},
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
|
||||
new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
false, TIFFMedataFormat.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "TODO", null, null,
|
||||
true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadata", null, null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-61
@@ -37,21 +37,17 @@ import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr 16 bit samples
|
||||
* to (raw) RGB 16 bit samples.
|
||||
* Input stream that provides on-the-fly upsampling of TIFF subsampled YCbCr 16 bit samples.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||
*/
|
||||
final class YCbCr16UpsamplerStream extends FilterInputStream {
|
||||
// TODO: As we deal with short/16 bit samples, we need to take byte order into account
|
||||
private final int horizChromaSub;
|
||||
private final int vertChromaSub;
|
||||
private final int yCbCrPos;
|
||||
private final int columns;
|
||||
private final double[] coefficients;
|
||||
private final ByteOrder byteOrder;
|
||||
|
||||
private final int units;
|
||||
private final int unitSize;
|
||||
@@ -65,7 +61,7 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
|
||||
int bufferLength;
|
||||
int bufferPos;
|
||||
|
||||
public YCbCr16UpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients, final ByteOrder byteOrder) {
|
||||
public YCbCr16UpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final ByteOrder byteOrder) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
Validate.notNull(chromaSub, "chromaSub");
|
||||
@@ -76,8 +72,6 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
|
||||
this.vertChromaSub = chromaSub[1];
|
||||
this.yCbCrPos = yCbCrPos;
|
||||
this.columns = columns;
|
||||
this.coefficients = coefficients == null ? YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS : coefficients;
|
||||
this.byteOrder = byteOrder;
|
||||
|
||||
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
||||
// For a 4:2 subsampled stream like this:
|
||||
@@ -150,7 +144,7 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
|
||||
decodedRows[pixelOff + 5] = cr2;
|
||||
|
||||
// Convert to RGB
|
||||
convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
|
||||
// convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,56 +222,4 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
|
||||
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||
int y;
|
||||
int cb;
|
||||
int cr;
|
||||
|
||||
// Short values, depends on byte order!
|
||||
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||
y = ((yCbCr[offset ] & 0xff) << 8) | (yCbCr[offset + 1] & 0xff);
|
||||
cb = (((yCbCr[offset + 2] & 0xff) << 8) | (yCbCr[offset + 3] & 0xff)) - 32768;
|
||||
cr = (((yCbCr[offset + 4] & 0xff) << 8) | (yCbCr[offset + 5] & 0xff)) - 32768;
|
||||
}
|
||||
else {
|
||||
y = ((yCbCr[offset + 1] & 0xff) << 8) | (yCbCr[offset ] & 0xff);
|
||||
cb = (((yCbCr[offset + 3] & 0xff) << 8) | (yCbCr[offset + 2] & 0xff)) - 32768;
|
||||
cr = (((yCbCr[offset + 5] & 0xff) << 8) | (yCbCr[offset + 4] & 0xff)) - 32768;
|
||||
}
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||
int green = (int) Math.round((y - lumaRed * (red) - lumaBlue * (blue)) / lumaGreen);
|
||||
|
||||
short r = clampShort(red);
|
||||
short g = clampShort(green);
|
||||
short b = clampShort(blue);
|
||||
|
||||
// Short values, depends on byte order!
|
||||
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||
rgb[offset ] = (byte) ((r >>> 8) & 0xff);
|
||||
rgb[offset + 1] = (byte) (r & 0xff);
|
||||
rgb[offset + 2] = (byte) ((g >>> 8) & 0xff);
|
||||
rgb[offset + 3] = (byte) (g & 0xff);
|
||||
rgb[offset + 4] = (byte) ((b >>> 8) & 0xff);
|
||||
rgb[offset + 5] = (byte) (b & 0xff);
|
||||
}
|
||||
else {
|
||||
rgb[offset ] = (byte) (r & 0xff);
|
||||
rgb[offset + 1] = (byte) ((r >>> 8) & 0xff);
|
||||
rgb[offset + 2] = (byte) (g & 0xff);
|
||||
rgb[offset + 3] = (byte) ((g >>> 8) & 0xff);
|
||||
rgb[offset + 4] = (byte) (b & 0xff);
|
||||
rgb[offset + 5] = (byte) ((b >>> 8) & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
private short clampShort(int val) {
|
||||
return (short) Math.max(0, Math.min(0xffff, val));
|
||||
}
|
||||
}
|
||||
|
||||
+2
-130
@@ -30,30 +30,24 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr samples to (raw) RGB samples.
|
||||
* Input stream that provides on-the-fly upsampling of TIFF subsampled YCbCr samples.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||
*/
|
||||
final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
// NOTE: DO NOT MODIFY OR EXPOSE THIS ARRAY OUTSIDE PACKAGE!
|
||||
static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
|
||||
|
||||
private final int horizChromaSub;
|
||||
private final int vertChromaSub;
|
||||
private final int yCbCrPos;
|
||||
private final int columns;
|
||||
private final double[] coefficients;
|
||||
|
||||
private final int units;
|
||||
private final int unitSize;
|
||||
@@ -66,7 +60,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
int bufferLength;
|
||||
int bufferPos;
|
||||
|
||||
public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients) {
|
||||
public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
Validate.notNull(chromaSub, "chromaSub");
|
||||
@@ -76,7 +70,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
this.vertChromaSub = chromaSub[1];
|
||||
this.yCbCrPos = yCbCrPos;
|
||||
this.columns = columns;
|
||||
this.coefficients = Arrays.equals(CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
|
||||
|
||||
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
||||
// For a 4:2 subsampled stream like this:
|
||||
@@ -141,14 +134,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
decodedRows[pixelOff] = buffer[bufferPos++];
|
||||
decodedRows[pixelOff + 1] = cb;
|
||||
decodedRows[pixelOff + 2] = cr;
|
||||
|
||||
// Convert to RGB
|
||||
if (coefficients == null) {
|
||||
YCbCrConverter.convertYCbCr2RGB(decodedRows, decodedRows, pixelOff);
|
||||
}
|
||||
else {
|
||||
convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,120 +212,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
|
||||
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||
double y = (yCbCr[offset ] & 0xff);
|
||||
double cb = (yCbCr[offset + 1] & 0xff) - 128;
|
||||
double cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||
int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
|
||||
|
||||
rgb[offset ] = clamp(red);
|
||||
rgb[offset + 2] = clamp(blue);
|
||||
rgb[offset + 1] = clamp(green);
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
|
||||
// TODO: This code is copied from JPEG package, make it "more" public: com.tm.imageio.color package?
|
||||
/**
|
||||
* Static inner class for lazy-loading of conversion tables.
|
||||
*/
|
||||
static final class YCbCrConverter {
|
||||
/** Define tables for YCC->RGB color space conversion. */
|
||||
private final static int SCALEBITS = 16;
|
||||
private final static int MAXJSAMPLE = 255;
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
|
||||
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 (TIFFImageReader.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();
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
static void convertYCCK2CMYK(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||
// Inverted
|
||||
int y = 255 - ycck[offset ] & 0xff;
|
||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||
int cr = 255 - ycck[offset + 2] & 0xff;
|
||||
int k = 255 - ycck[offset + 3] & 0xff;
|
||||
|
||||
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||
|
||||
cmyk[offset ] = clamp(cmykC);
|
||||
cmyk[offset + 1] = clamp(cmykM);
|
||||
cmyk[offset + 2] = clamp(cmykY);
|
||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+101
-64
@@ -45,29 +45,63 @@ import static org.junit.Assert.*;
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk Exp$
|
||||
* @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk
|
||||
* Exp$
|
||||
*/
|
||||
public class CCITTFaxDecoderStreamTest {
|
||||
|
||||
// group3_1d.tif: EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|2W|2B|2W|5*F
|
||||
static final byte[] DATA_G3_1D = { 0x00, 0x18, 0x4E, 0x00, 0x30, (byte) 0x9C, 0x00, 0x61, 0x38, 0x00, (byte) 0xBE,
|
||||
(byte) 0xE0 };
|
||||
|
||||
// group3_1d_fill.tif
|
||||
static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01,
|
||||
(byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 };
|
||||
|
||||
// group3_2d.tif: EOL|k=1|3W|1B|2W|EOL|k=0|V|V|V|EOL|k=1|3W|1B|2W|EOL|k=0|V-1|V|V|6*F
|
||||
static final byte[] DATA_G3_2D = { 0x00, 0x1C, 0x27, 0x00, 0x17, 0x00, 0x1C, 0x27, 0x00, 0x12, (byte) 0xC0 };
|
||||
|
||||
// group3_2d_fill.tif
|
||||
static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01,
|
||||
0x2C };
|
||||
|
||||
static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4,
|
||||
0x00, 0x48, 0x03 };
|
||||
|
||||
// group4.tif:
|
||||
// Line 1: V-3, V-2, V0
|
||||
// Line 2: V0 V0 V0
|
||||
// Line 3: V0 V0 V0
|
||||
// Line 4: V-1, V0, V0 EOL EOL
|
||||
static final byte[] DATA_G4 = { 0x04, 0x17, (byte) 0xF5, (byte) 0x80, 0x08, 0x00, (byte) 0x80 };
|
||||
|
||||
// TODO: Better tests (full A4 width scan lines?)
|
||||
|
||||
// From http://www.mikekohn.net/file_formats/tiff.php
|
||||
static final byte[] DATA_TYPE_2 = {
|
||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||
static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100
|
||||
// 11100000
|
||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||
(byte) 0x7d, (byte) 0xc0, // 01111101 11000000
|
||||
};
|
||||
|
||||
static final byte[] DATA_TYPE_3 = {
|
||||
0x00, 0x01, (byte) 0xc2, 0x70,
|
||||
0x00, 0x01, 0x70,
|
||||
0x01,
|
||||
static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000
|
||||
// 00000001
|
||||
// 11000010
|
||||
// 01110000
|
||||
0x00, 0x01, 0x78, // 00000000 00000001 01111000
|
||||
0x00, 0x01, 0x78, // 00000000 00000001 01110000
|
||||
0x00, 0x01, 0x56, // 00000000 00000001 01010110
|
||||
// 0x01, // 00000001
|
||||
|
||||
};
|
||||
|
||||
static final byte[] DATA_TYPE_4 = {
|
||||
0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0
|
||||
// 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding)
|
||||
static final byte[] DATA_TYPE_4 = { 0x26, // 001 00110
|
||||
(byte) 0xb0, // 101 10 000
|
||||
0x5f, // 010 1 1 1 1 1
|
||||
(byte) 0xfa, // 1 1 1 1 1 010
|
||||
(byte) 0xc0 // 11 (000000 padding)
|
||||
};
|
||||
|
||||
// Image should be (6 x 4):
|
||||
@@ -82,85 +116,88 @@ public class CCITTFaxDecoderStreamTest {
|
||||
image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 6; x++) {
|
||||
image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff);
|
||||
image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff);
|
||||
}
|
||||
}
|
||||
|
||||
image.setRGB(2, 3, 0xff000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadCountType2() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
|
||||
|
||||
int count = 0;
|
||||
int read;
|
||||
while ((read = stream.read()) >= 0) {
|
||||
count++;
|
||||
}
|
||||
|
||||
// Just make sure we'll have 4 bytes
|
||||
assertEquals(4, count);
|
||||
|
||||
// Verify that we don't return arbitrary values
|
||||
assertEquals(-1, read);
|
||||
image.setRGB(2, 3, 0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeType2() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6,
|
||||
TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
|
||||
// JPanel panel = new JPanel();
|
||||
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||
// JOptionPane.showConfirmDialog(null, panel);
|
||||
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testDecodeType3() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1);
|
||||
@Test
|
||||
public void testDecodeType3_1D() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
DataInputStream dataInput = new DataInputStream(stream);
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
System.err.println("y: " + y);
|
||||
dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
|
||||
}
|
||||
|
||||
// JPanel panel = new JPanel();
|
||||
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||
// JOptionPane.showConfirmDialog(null, panel);
|
||||
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void testDecodeType3_1D_FILL() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeType3_2D() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeType3_2D_FILL() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T4, 1,
|
||||
TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeType3_2D_REVERSED() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T4, 2, TIFFExtension.GROUP3OPT_2DENCODING);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeType4() throws IOException {
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1);
|
||||
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6,
|
||||
TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
|
||||
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] bytes = new byte[imageData.length];
|
||||
DataInputStream dataInput = new DataInputStream(stream);
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
System.err.println("y: " + y);
|
||||
dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
|
||||
}
|
||||
|
||||
// JPanel panel = new JPanel();
|
||||
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||
// JOptionPane.showConfirmDialog(null, panel);
|
||||
|
||||
new DataInputStream(stream).readFully(bytes);
|
||||
assertArrayEquals(imageData, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.tiff.CCITTFaxEncoderStream.Code;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* CCITTFaxEncoderStreamTest
|
||||
*
|
||||
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
|
||||
* @author last modified by $Author$
|
||||
* @version $Id$
|
||||
*/
|
||||
public class CCITTFaxEncoderStreamTest {
|
||||
|
||||
// Image should be (6 x 4):
|
||||
// 1 1 1 0 1 1 x x
|
||||
// 1 1 1 0 1 1 x x
|
||||
// 1 1 1 0 1 1 x x
|
||||
// 1 1 0 0 1 1 x x
|
||||
BufferedImage image;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 6; x++) {
|
||||
image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff);
|
||||
}
|
||||
}
|
||||
|
||||
image.setRGB(2, 3, 0xffffffff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildCodes() throws IOException {
|
||||
assertTrue(CCITTFaxEncoderStream.WHITE_TERMINATING_CODES.length == 64);
|
||||
for (Code code : CCITTFaxEncoderStream.WHITE_TERMINATING_CODES) {
|
||||
assertNotNull(code);
|
||||
}
|
||||
assertTrue(CCITTFaxEncoderStream.WHITE_NONTERMINATING_CODES.length == 40);
|
||||
for (Code code : CCITTFaxEncoderStream.WHITE_NONTERMINATING_CODES) {
|
||||
assertNotNull(code);
|
||||
}
|
||||
assertTrue(CCITTFaxEncoderStream.BLACK_TERMINATING_CODES.length == 64);
|
||||
for (Code code : CCITTFaxEncoderStream.BLACK_TERMINATING_CODES) {
|
||||
assertNotNull(code);
|
||||
}
|
||||
assertTrue(CCITTFaxEncoderStream.BLACK_NONTERMINATING_CODES.length == 40);
|
||||
for (Code code : CCITTFaxEncoderStream.BLACK_NONTERMINATING_CODES) {
|
||||
assertNotNull(code);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType2() throws IOException {
|
||||
testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType4() throws IOException {
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1,
|
||||
TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testType6() throws IOException {
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReversedFillOrder() throws IOException {
|
||||
testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L);
|
||||
testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReencodeImages() throws IOException {
|
||||
testImage(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif"));
|
||||
}
|
||||
|
||||
protected URL getClassLoaderResource(final String pName) {
|
||||
return getClass().getResource(pName);
|
||||
}
|
||||
|
||||
private void testStreamEncodeDecode(int type, int fillOrder, long options) throws IOException {
|
||||
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||
byte[] redecodedData = new byte[imageData.length];
|
||||
ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
|
||||
OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 6, 4, type, fillOrder, options);
|
||||
outputSteam.write(imageData);
|
||||
outputSteam.close();
|
||||
byte[] encodedData = imageOutput.toByteArray();
|
||||
|
||||
CCITTFaxDecoderStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 6, type,
|
||||
fillOrder, options);
|
||||
new DataInputStream(inputStream).readFully(redecodedData);
|
||||
inputStream.close();
|
||||
|
||||
assertArrayEquals(imageData, redecodedData);
|
||||
}
|
||||
|
||||
private void testImage(URL imageUrl) throws IOException {
|
||||
ImageInputStream iis = ImageIO.createImageInputStream(imageUrl.openStream());
|
||||
ImageReader reader = ImageIO.getImageReadersByFormatName("TIFF").next();
|
||||
reader.setInput(iis, true);
|
||||
|
||||
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
|
||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
|
||||
ImageOutputStream output = ImageIO.createImageOutputStream(outputBuffer);
|
||||
writer.setOutput(output);
|
||||
BufferedImage originalImage = reader.read(0);
|
||||
|
||||
IIOImage outputImage = new IIOImage(originalImage, null, reader.getImageMetadata(0));
|
||||
writer.write(outputImage);
|
||||
|
||||
BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray()));
|
||||
byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData();
|
||||
|
||||
Assert.assertArrayEquals(((DataBufferByte) originalImage.getData().getDataBuffer()).getData(), reencodedData);
|
||||
}
|
||||
}
|
||||
+599
@@ -0,0 +1,599 @@
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* TIFFImageMetadataTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: TIFFImageMetadataTest.java,v 1.0 30/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public class TIFFImageMetadataTest {
|
||||
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
// TODO: Candidate super method
|
||||
private URL getClassLoaderResource(final String resource) {
|
||||
return getClass().getResource(resource);
|
||||
}
|
||||
|
||||
// TODO: Candidate abstract super method
|
||||
private IIOMetadata createMetadata(final String resource) throws IOException {
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
|
||||
Directory ifd = new EXIFReader().read(input);
|
||||
// System.err.println("ifd: " + ifd);
|
||||
return new TIFFImageMetadata(ifd);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetadataStandardFormat() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/smallliz.tif");
|
||||
Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
|
||||
// Root: "javax_imageio_1.0"
|
||||
assertNotNull(root);
|
||||
assertEquals(IIOMetadataFormatImpl.standardMetadataFormatName, root.getNodeName());
|
||||
assertEquals(6, root.getChildNodes().getLength());
|
||||
|
||||
// "Chroma"
|
||||
Node chroma = root.getFirstChild();
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
|
||||
assertEquals(3, chroma.getChildNodes().getLength());
|
||||
|
||||
Node colorSpaceType = chroma.getFirstChild();
|
||||
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
|
||||
assertEquals("YCbCr", ((Element) colorSpaceType).getAttribute("value"));
|
||||
|
||||
Node numChannels = colorSpaceType.getNextSibling();
|
||||
assertEquals("NumChannels", numChannels.getNodeName());
|
||||
assertEquals("3", ((Element) numChannels).getAttribute("value"));
|
||||
|
||||
Node blackIsZero = numChannels.getNextSibling();
|
||||
assertEquals("BlackIsZero", blackIsZero.getNodeName());
|
||||
assertEquals(0, blackIsZero.getAttributes().getLength());
|
||||
|
||||
// "Compression"
|
||||
Node compression = chroma.getNextSibling();
|
||||
assertEquals("Compression", compression.getNodeName());
|
||||
assertEquals(2, compression.getChildNodes().getLength());
|
||||
|
||||
Node compressionTypeName = compression.getFirstChild();
|
||||
assertEquals("CompressionTypeName", compressionTypeName.getNodeName());
|
||||
assertEquals("Old JPEG", ((Element) compressionTypeName).getAttribute("value"));
|
||||
|
||||
Node lossless = compressionTypeName.getNextSibling();
|
||||
assertEquals("Lossless", lossless.getNodeName());
|
||||
assertEquals("FALSE", ((Element) lossless).getAttribute("value"));
|
||||
|
||||
// "Data"
|
||||
Node data = compression.getNextSibling();
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(4, data.getChildNodes().getLength());
|
||||
|
||||
Node planarConfiguration = data.getFirstChild();
|
||||
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
|
||||
assertEquals("PixelInterleaved", ((Element) planarConfiguration).getAttribute("value"));
|
||||
|
||||
Node sampleFormat = planarConfiguration.getNextSibling();
|
||||
assertEquals("SampleFormat", sampleFormat.getNodeName());
|
||||
assertEquals("UnsignedIntegral", ((Element) sampleFormat).getAttribute("value"));
|
||||
|
||||
Node bitsPerSample = sampleFormat.getNextSibling();
|
||||
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
|
||||
assertEquals("8 8 8", ((Element) bitsPerSample).getAttribute("value"));
|
||||
|
||||
Node sampleMSB = bitsPerSample.getNextSibling();
|
||||
assertEquals("SampleMSB", sampleMSB.getNodeName());
|
||||
assertEquals("0 0 0", ((Element) sampleMSB).getAttribute("value"));
|
||||
|
||||
// "Dimension"
|
||||
Node dimension = data.getNextSibling();
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(3, dimension.getChildNodes().getLength());
|
||||
|
||||
Node pixelAspectRatio = dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", ((Element) pixelAspectRatio).getAttribute("value"));
|
||||
|
||||
Node horizontalPixelSize = pixelAspectRatio.getNextSibling();
|
||||
assertEquals("HorizontalPixelSize", horizontalPixelSize.getNodeName());
|
||||
assertEquals("0.254", ((Element) horizontalPixelSize).getAttribute("value"));
|
||||
|
||||
Node verticalPixelSize = horizontalPixelSize.getNextSibling();
|
||||
assertEquals("VerticalPixelSize", verticalPixelSize.getNodeName());
|
||||
assertEquals("0.254", ((Element) verticalPixelSize).getAttribute("value"));
|
||||
|
||||
// "Document"
|
||||
Node document = dimension.getNextSibling();
|
||||
assertEquals("Document", document.getNodeName());
|
||||
assertEquals(1, document.getChildNodes().getLength());
|
||||
|
||||
Node formatVersion = document.getFirstChild();
|
||||
assertEquals("FormatVersion", formatVersion.getNodeName());
|
||||
assertEquals("6.0", ((Element) formatVersion).getAttribute("value"));
|
||||
|
||||
// "Text"
|
||||
Node text = document.getNextSibling();
|
||||
assertEquals("Text", text.getNodeName());
|
||||
assertEquals(1, text.getChildNodes().getLength());
|
||||
|
||||
// NOTE: Could be multiple "TextEntry" elements, with different "keyword" attributes
|
||||
Node textEntry = text.getFirstChild();
|
||||
assertEquals("TextEntry", textEntry.getNodeName());
|
||||
assertEquals("Software", ((Element) textEntry).getAttribute("keyword"));
|
||||
assertEquals("HP IL v1.1", ((Element) textEntry).getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetadataNativeFormat() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
|
||||
Node root = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
|
||||
// Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
|
||||
assertNotNull(root);
|
||||
assertEquals(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
|
||||
assertEquals(1, root.getChildNodes().getLength());
|
||||
|
||||
// IFD: "TIFFIFD"
|
||||
Node ifd = root.getFirstChild();
|
||||
assertEquals("TIFFIFD", ifd.getNodeName());
|
||||
|
||||
NodeList entries = ifd.getChildNodes();
|
||||
assertEquals(13, entries.getLength());
|
||||
|
||||
String[] stripOffsets = {
|
||||
"8", "150", "292", "434", "576", "718", "860", "1002", "1144", "1286",
|
||||
"1793", "3823", "7580", "12225", "17737", "23978", "30534", "36863", "42975", "49180",
|
||||
"55361", "61470", "67022", "71646", "74255", "75241", "75411", "75553", "75695", "75837",
|
||||
"75979", "76316", "77899", "80466", "84068", "88471", "93623", "99105", "104483", "109663",
|
||||
"114969", "120472", "126083", "131289", "135545", "138810", "140808", "141840", "141982", "142124",
|
||||
"142266", "142408", "142615", "144074", "146327", "149721", "154066", "158927", "164022", "169217",
|
||||
"174409", "179657", "185166", "190684", "196236", "201560", "206064", "209497", "211612", "212419",
|
||||
"212561", "212703", "212845", "212987", "213129", "213271", "213413"
|
||||
};
|
||||
|
||||
String[] stripByteCounts = {
|
||||
"142", "142", "142", "142", "142", "142", "142", "142", "142", "507",
|
||||
"2030", "3757", "4645", "5512", "6241", "6556", "6329", "6112", "6205", "6181",
|
||||
"6109", "5552", "4624", "2609", "986", "170", "142", "142", "142", "142",
|
||||
"337", "1583", "2567", "3602", "4403", "5152", "5482", "5378", "5180", "5306",
|
||||
"5503", "5611", "5206", "4256", "3265", "1998", "1032", "142", "142", "142",
|
||||
"142", "207", "1459", "2253", "3394", "4345", "4861", "5095", "5195", "5192",
|
||||
"5248", "5509", "5518", "5552", "5324", "4504", "3433", "2115", "807", "142",
|
||||
"142", "142", "142", "142", "142", "142", "128"
|
||||
};
|
||||
|
||||
// The 13 entries
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, "512");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_HEIGHT, TIFF.TYPE_SHORT, "384");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_BITS_PER_SAMPLE, TIFF.TYPE_SHORT, "8", "8", "8");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, "5");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, "2");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffsets);
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_SAMPLES_PER_PIXEL, TIFF.TYPE_SHORT, "3");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, "5");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCounts);
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_PLANAR_CONFIGURATION, TIFF.TYPE_SHORT, "1");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0");
|
||||
assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0");
|
||||
assertSingleNodeWithValue(entries, 32995, TIFF.TYPE_SHORT, "0"); // Matteing tag, obsoleted by ExtraSamples tag in TIFF 6.0
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreeDetached() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
Node nativeTree = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
assertNotNull(nativeTree);
|
||||
|
||||
Node nativeTree2 = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
assertNotNull(nativeTree2);
|
||||
|
||||
assertNotSame(nativeTree, nativeTree2);
|
||||
assertNodeEquals("Unmodified trees differs", nativeTree, nativeTree2); // Both not modified
|
||||
|
||||
// Modify one of the trees
|
||||
Node ifdNode = nativeTree2.getFirstChild();
|
||||
ifdNode.removeChild(ifdNode.getFirstChild());
|
||||
IIOMetadataNode tiffField = new IIOMetadataNode("TIFFField");
|
||||
ifdNode.appendChild(tiffField);
|
||||
|
||||
assertNodeNotEquals("Modified tree does not differ", nativeTree, nativeTree2);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeTree() throws IOException {
|
||||
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
|
||||
Node nativeTree = metadata.getAsTree(nativeFormat);
|
||||
assertNotNull(nativeTree);
|
||||
|
||||
IIOMetadataNode newTree = new IIOMetadataNode("com_sun_media_imageio_plugins_tiff_image_1.0");
|
||||
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
|
||||
newTree.appendChild(ifdNode);
|
||||
|
||||
createTIFFFieldNode(ifdNode, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_DPI);
|
||||
createTIFFFieldNode(ifdNode, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(300));
|
||||
createTIFFFieldNode(ifdNode, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(30001, 100));
|
||||
|
||||
metadata.mergeTree(nativeFormat, newTree);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||
assertEquals(new Rational(30001, 100), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||
|
||||
Node mergedTree = metadata.getAsTree(nativeFormat);
|
||||
NodeList fields = mergedTree.getFirstChild().getChildNodes();
|
||||
|
||||
// Validate there's one and only one resolution unit, x res and y res
|
||||
// Validate resolution unit == 1, x res & y res
|
||||
assertSingleNodeWithValue(fields, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, String.valueOf(TIFFBaseline.RESOLUTION_UNIT_DPI));
|
||||
assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300");
|
||||
assertSingleNodeWithValue(fields, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, "30001/100");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeTreeStandardFormat() throws IOException {
|
||||
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/zackthecat.tif");
|
||||
|
||||
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||
|
||||
Node standardTree = metadata.getAsTree(standardFormat);
|
||||
assertNotNull(standardTree);
|
||||
|
||||
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
newTree.appendChild(dimensionNode);
|
||||
|
||||
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
|
||||
dimensionNode.appendChild(horizontalPixelSize);
|
||||
horizontalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
|
||||
|
||||
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
|
||||
dimensionNode.appendChild(verticalPixelSize);
|
||||
verticalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
|
||||
|
||||
metadata.mergeTree(standardFormat, newTree);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||
|
||||
// Should keep DPI as unit
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||
assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMergeTreeStandardFormatAspectOnly() throws IOException {
|
||||
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||
|
||||
Node standardTree = metadata.getAsTree(standardFormat);
|
||||
assertNotNull(standardTree);
|
||||
|
||||
IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
newTree.appendChild(dimensionNode);
|
||||
|
||||
IIOMetadataNode aspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
dimensionNode.appendChild(aspectRatio);
|
||||
aspectRatio.setAttribute("value", String.valueOf(3f / 2f));
|
||||
|
||||
metadata.mergeTree(standardFormat, newTree);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
|
||||
assertEquals(new Rational(3, 2), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||
assertEquals(new Rational(1), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||
assertEquals(TIFFBaseline.RESOLUTION_UNIT_NONE, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testMergeTreeUnsupportedFormat() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = "com_foo_bar_tiff_42";
|
||||
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||
}
|
||||
|
||||
@Test(expected = IIOInvalidTreeException.class)
|
||||
public void testMergeTreeFormatMisMatch() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||
}
|
||||
|
||||
@Test(expected = IIOInvalidTreeException.class)
|
||||
public void testMergeTreeInvalid() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
|
||||
}
|
||||
|
||||
// TODO: Test that failed merge leaves metadata unchanged
|
||||
|
||||
@Test
|
||||
public void testSetFromTreeEmpty() throws IOException {
|
||||
// Read from file, set empty to see that all is cleared
|
||||
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
|
||||
root.appendChild(new IIOMetadataNode("TIFFIFD"));
|
||||
|
||||
metadata.setFromTree(nativeFormat, root);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
assertNotNull(ifd);
|
||||
assertEquals(0, ifd.size());
|
||||
|
||||
Node tree = metadata.getAsTree(nativeFormat);
|
||||
|
||||
assertNotNull(tree);
|
||||
assertNotNull(tree.getFirstChild());
|
||||
assertEquals(1, tree.getChildNodes().getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetFromTree() throws IOException {
|
||||
String softwareString = "12M UberTIFF 1.0";
|
||||
|
||||
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
|
||||
|
||||
IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
|
||||
root.appendChild(ifdNode);
|
||||
|
||||
createTIFFFieldNode(ifdNode, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
|
||||
|
||||
metadata.setFromTree(nativeFormat, root);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
assertNotNull(ifd);
|
||||
assertEquals(1, ifd.size());
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
|
||||
Node tree = metadata.getAsTree(nativeFormat);
|
||||
|
||||
assertNotNull(tree);
|
||||
assertNotNull(tree.getFirstChild());
|
||||
assertEquals(1, tree.getChildNodes().getLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetFromTreeStandardFormat() throws IOException {
|
||||
String softwareString = "12M UberTIFF 1.0";
|
||||
String copyrightString = "Copyright (C) TwelveMonkeys, 2015";
|
||||
|
||||
TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.<Entry>emptySet());
|
||||
|
||||
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||
IIOMetadataNode root = new IIOMetadataNode(standardFormat);
|
||||
|
||||
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||
root.appendChild(textNode);
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textNode.appendChild(textEntry);
|
||||
|
||||
textEntry.setAttribute("keyword", "SOFTWARE"); // Spelling should not matter
|
||||
textEntry.setAttribute("value", softwareString);
|
||||
|
||||
textEntry = new IIOMetadataNode("TextEntry");
|
||||
textNode.appendChild(textEntry);
|
||||
|
||||
textEntry.setAttribute("keyword", "copyright"); // Spelling should not matter
|
||||
textEntry.setAttribute("value", copyrightString);
|
||||
|
||||
metadata.setFromTree(standardFormat, root);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
assertNotNull(ifd);
|
||||
assertEquals(2, ifd.size());
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_COPYRIGHT));
|
||||
assertEquals(copyrightString, ifd.getEntryById(TIFF.TAG_COPYRIGHT).getValue());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testSetFromTreeUnsupportedFormat() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = "com_foo_bar_tiff_42";
|
||||
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat));
|
||||
}
|
||||
|
||||
@Test(expected = IIOInvalidTreeException.class)
|
||||
public void testSetFromTreeFormatMisMatch() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
|
||||
}
|
||||
|
||||
@Test(expected = IIOInvalidTreeException.class)
|
||||
public void testSetFromTreeInvalid() throws IOException {
|
||||
IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
|
||||
|
||||
String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||
metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
|
||||
}
|
||||
|
||||
private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {
|
||||
String tagNumber = String.valueOf(tag);
|
||||
String typeName = StringUtil.capitalize(TIFF.TYPE_NAMES[type].toLowerCase());
|
||||
|
||||
boolean foundTag = false;
|
||||
|
||||
for (int i = 0; i < fields.getLength(); i++) {
|
||||
Element field = (Element) fields.item(i);
|
||||
|
||||
if (tagNumber.equals(field.getAttribute("number"))) {
|
||||
assertFalse("Duplicate tag " + tagNumber + " found", foundTag);
|
||||
|
||||
assertEquals(1, field.getChildNodes().getLength());
|
||||
Node containerNode = field.getFirstChild();
|
||||
assertEquals("TIFF" + typeName + "s", containerNode.getNodeName());
|
||||
|
||||
NodeList valueNodes = containerNode.getChildNodes();
|
||||
assertEquals("Unexpected number of values for tag " + tagNumber, expectedValue.length, valueNodes.getLength());
|
||||
|
||||
for (int j = 0; j < expectedValue.length; j++) {
|
||||
Element valueNode = (Element) valueNodes.item(j);
|
||||
assertEquals("TIFF" + typeName, valueNode.getNodeName());
|
||||
assertEquals("Unexpected tag " + tagNumber + " value", expectedValue[j], valueNode.getAttribute("value"));
|
||||
}
|
||||
|
||||
foundTag = true;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("No tag " + tagNumber + " found", foundTag);
|
||||
}
|
||||
|
||||
// TODO: Test that failed set leaves metadata unchanged
|
||||
|
||||
static void createTIFFFieldNode(final IIOMetadataNode parentIFDNode, int tag, short type, Object value) {
|
||||
IIOMetadataNode fieldNode = new IIOMetadataNode("TIFFField");
|
||||
parentIFDNode.appendChild(fieldNode);
|
||||
|
||||
fieldNode.setAttribute("number", String.valueOf(tag));
|
||||
|
||||
switch (type) {
|
||||
case TIFF.TYPE_ASCII:
|
||||
createTIFFFieldContainerNode(fieldNode, "Ascii", value);
|
||||
break;
|
||||
case TIFF.TYPE_BYTE:
|
||||
createTIFFFieldContainerNode(fieldNode, "Byte", value);
|
||||
break;
|
||||
case TIFF.TYPE_SHORT:
|
||||
createTIFFFieldContainerNode(fieldNode, "Short", value);
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
createTIFFFieldContainerNode(fieldNode, "Rational", value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
static void createTIFFFieldContainerNode(final IIOMetadataNode fieldNode, final String type, final Object value) {
|
||||
IIOMetadataNode containerNode = new IIOMetadataNode("TIFF" + type + "s");
|
||||
fieldNode.appendChild(containerNode);
|
||||
|
||||
IIOMetadataNode valueNode = new IIOMetadataNode("TIFF" + type);
|
||||
valueNode.setAttribute("value", String.valueOf(value));
|
||||
containerNode.appendChild(valueNode);
|
||||
}
|
||||
|
||||
private void assertNodeNotEquals(final String message, final Node expected, final Node actual) {
|
||||
// Lame, lazy implementation...
|
||||
try {
|
||||
assertNodeEquals(message, expected, actual);
|
||||
}
|
||||
catch (AssertionError ignore) {
|
||||
return;
|
||||
}
|
||||
|
||||
fail(message);
|
||||
}
|
||||
|
||||
private void assertNodeEquals(final String message, final Node expected, final Node actual) {
|
||||
assertEquals(message + " class differs", expected.getClass(), actual.getClass());
|
||||
assertEquals(message, expected.getNodeValue(), actual.getNodeValue());
|
||||
|
||||
if (expected instanceof IIOMetadataNode) {
|
||||
IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
|
||||
IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
|
||||
|
||||
assertEquals(message, expectedIIO.getUserObject(), actualIIO.getUserObject());
|
||||
}
|
||||
|
||||
NodeList expectedChildNodes = expected.getChildNodes();
|
||||
NodeList actualChildNodes = actual.getChildNodes();
|
||||
|
||||
assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes),
|
||||
expectedChildNodes.getLength(), actualChildNodes.getLength());
|
||||
|
||||
for (int i = 0; i < expectedChildNodes.getLength(); i++) {
|
||||
Node expectedChild = expectedChildNodes.item(i);
|
||||
Node actualChild = actualChildNodes.item(i);
|
||||
|
||||
assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName());
|
||||
assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild);
|
||||
}
|
||||
}
|
||||
|
||||
private String toString(final NodeList list) {
|
||||
if (list.getLength() == 0) {
|
||||
return "[]";
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder("[");
|
||||
for (int i = 0; i < list.getLength(); i++) {
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
|
||||
Node node = list.item(i);
|
||||
builder.append(node.getLocalName());
|
||||
}
|
||||
builder.append("]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user