Compare commits

...

262 Commits

Author SHA1 Message Date
Henrik Plate 66eae4ae00 Downport fix commit (#1080) 2025-01-17 08:54:50 +01:00
Harald Kuhr c8745f4df7 [maven-release-plugin] prepare for next development iteration 2017-02-02 21:30:47 +01:00
Harald Kuhr e8d427ae5f [maven-release-plugin] prepare release twelvemonkeys-3.3.2 2017-02-02 21:30:39 +01:00
Harald Kuhr c181f74fb0 #306 TIFF LZW write issue test case fix for 3.3 branch. 2017-02-02 21:18:56 +01:00
Harald Kuhr c0f66b5584 #306 TIFF LZW write EOF issue
(cherry picked from commit 94d6ad1)
2017-02-02 21:11:30 +01:00
Harald Kuhr 69dcfe9713 #307 PIXTIFF ZIP compression 50013 support
(cherry picked from commit d57e414)
2017-02-02 21:11:07 +01:00
Harald Kuhr 5aac07f221 #307 PIXTIFF ZIP compression 50013 support
(cherry picked from commit 9199083)
2017-02-02 21:09:38 +01:00
Harald Kuhr aa3e2cc019 #312, #313, #314 Fixed ClassCastException when rewriting compressed TIFF images.
(cherry picked from commit 6ea1ea8)
2017-02-02 21:06:07 +01:00
Harald Kuhr b0108fe39b #306 TIFF LZW IndexColorModel issue + sequence index
(cherry picked from commit 762b596)
2017-02-02 20:58:11 +01:00
Harald Kuhr 3f6a27b75e Code clean-up.
(cherry picked from commit 10b8c11)
2017-02-02 20:42:54 +01:00
Harald Kuhr cb2cf0721c Added sequence writing illegal state tests
(cherry picked from commit ed6f70f)
2017-02-02 20:42:26 +01:00
Harald Kuhr f49b57b3de #297 JPEGLossless no supports AC tables only image + multiple tables images
(cherry picked from commit 7a0660c)
2017-02-02 20:36:25 +01:00
Harald Kuhr 22593e37f7 Made JPEG lossless decoding seemingly much faster.
(cherry picked from commit 65e3156)
2017-02-02 20:35:00 +01:00
Harald Kuhr ad86bcda7e #300 TIFF old style JPEG inconsistent metadata
(cherry picked from commit 753afd0)
2017-02-02 20:32:01 +01:00
Harald Kuhr 41e6b041c9 [maven-release-plugin] prepare for next development iteration 2016-11-29 20:31:57 +01:00
Harald Kuhr 4e5127404f [maven-release-plugin] prepare release twelvemonkeys-3.3.1 2016-11-29 20:31:51 +01:00
Harald Kuhr 2cede0e8cc Updating POM versions for bugfix release 2016-11-29 20:25:38 +01:00
Harald Kuhr e189b5e14f #294 Fixed regression, reading JPEGs containing strange APPn markers without identifier string 2016-11-29 20:10:28 +01:00
Harald Kuhr f0316f7ec5 #274 Fixed bug in 16 -> 8 bit downsampling of DQT, causing "Bogus marker length" IIOException 2016-11-29 20:09:42 +01:00
Harald Kuhr 654e111605 [maven-release-plugin] prepare release twelvemonkeys-3.3 2016-11-02 19:36:45 +01:00
Harald Kuhr 6d043d0208 [maven-release-plugin] rollback the release of twelvemonkeys-3.3 2016-11-02 19:31:52 +01:00
Harald Kuhr e4b91ab34c [maven-release-plugin] prepare release twelvemonkeys-3.3 2016-11-02 19:27:22 +01:00
Harald Kuhr 34eb084d24 #212 TIFF subsampling fix 2016-11-02 19:06:12 +01:00
Harald Kuhr 3f6bc722bc Typo in TIFF tag name. 2016-11-02 09:36:11 +01:00
Harald Kuhr 5ab3fdd1d3 Merge pull request #286 from Schmidor/TMI268
Fix #268: Reading with source region for JPEG-in-TIFF
2016-10-20 16:43:54 +02:00
Oliver Schmidtmer 610f9bec9f Fix #268: Reading with source region for JPEG-in-TIFF 2016-10-19 16:00:04 +02:00
Harald Kuhr 17eeda210e #285: Fix for non-spec BMPs without palette. 2016-10-17 20:36:40 +02:00
Harald Kuhr b116b4b5a7 #272: Fix for LZW decoding issue caused by runs way longer than buffer. 2016-10-10 21:46:39 +02:00
Harald Kuhr afd8b28617 #182: Added more test files or better coverage. 2016-09-28 13:01:07 +02:00
Harald Kuhr d4afbee0f5 #282 TIFFImageReader now supports compressed planar data in strips/tiles. 2016-09-28 12:37:47 +02:00
Harald Kuhr 288ad54c42 #261 Code clean up. 2016-09-28 12:13:35 +02:00
Harald Kuhr 5f12c88609 Merge pull request #261 from Schmidor/AffineTransformOp
Extended AffineTransformOp for a Graphics2D fallback on filter-method
2016-09-16 13:52:01 +02:00
Harald Kuhr 51afe2d2e8 #182 Code clean up. 2016-09-09 13:59:03 +02:00
Harald Kuhr 7ac1589186 #182 Fixed bug in JFXXThumbnailReaderTest 2016-09-09 13:59:03 +02:00
Harald Kuhr 7d35400595 #182 Moved lossless support classes to main package for better encapsulation, sorted out license issues. 2016-09-09 13:59:03 +02:00
Harald Kuhr 673f3e5b53 #182 Massive refactorings to clean up metadata and segment handling 2016-09-09 13:59:03 +02:00
Harald Kuhr 15ce9d6b64 #182 Clean up after merge of #215 2016-09-09 13:59:03 +02:00
HermannKroll b1ac99ba1a Added Me to Contributors 2016-09-09 13:59:03 +02:00
HermannKroll ad269053ed add support for JPEG Lossless
JPEG Lossless files which are supported
https://github.com/rii-mango/JPEGLosslessDecoder can be read.

Careful: currently only supports
16, 8-bit grayscale and 24 bit rgb conversion for BufferedImages
2016-09-09 13:59:03 +02:00
Harald Kuhr 13bea23550 #276 Fix DHT inconsistency 2016-08-09 11:47:25 +02:00
Harald Kuhr c7208c2c97 Clean up after merge. 2016-08-09 09:32:58 +02:00
Harald Kuhr 44401d9a0d #275 Fix infinite loop if EOF before header done 2016-08-08 11:27:12 +02:00
Harald Kuhr c18893184b #228: TIFFImageWriter now correctly writes images with sample model translation. 2016-07-07 15:27:08 +02:00
Harald Kuhr 04a39158e5 #257, #229: Fixed LZW writing for < 8 bit, fixed StripByteCounts for uncompressed < 8 bit, disabled Predictor for < 8 bit.
Bonus rework of sequence writing and restored writing of uncompressed data for less fseeking.
2016-07-01 19:32:35 +02:00
Harald Kuhr 6673bb3536 Documentation. 2016-07-01 16:00:35 +02:00
Harald Kuhr 7efd5ca0d8 Merge pull request #271 from escenic/the-area-of-interest
Improved area of interest
2016-06-30 14:28:58 +02:00
Harald Kuhr fd285ca5ee Merge remote-tracking branch 'origin/master' 2016-06-30 14:21:48 +02:00
Harald Kuhr a29960e8ee #248 PSDImageReader now uses correct band indices for grayscale + alpha layers. 2016-06-30 14:21:30 +02:00
Harald Kuhr 00c1285fe0 #248 PSDImageReader now uses correct band indices for grayscale + alpha layers. 2016-06-30 14:17:00 +02:00
Harald Kuhr 478ed62cd1 Refactoring, pulling assertRGBEquals method up. 2016-06-30 14:15:29 +02:00
Torstein Krause Johansen 0db676f1be Re-added test case lost in large merge
- Removed imageio-jmagick (again)

- Removed internal Maven repo

- fixed line endings on a number of files to avoid a humongous merge
  diff when getting changes into upstream

- Re-enabled a few tests
2016-06-29 14:16:44 +02:00
Harald Kuhr 054499b78a #269: Hopefully fix CI build again. :-) 2016-06-28 18:30:30 +02:00
Harald Kuhr b0eb668ed4 #269: Fixed an issue with CMYK colors as CMYK being off. 2016-06-28 18:18:15 +02:00
Torstein Krause Johansen 1e3c8b26f6 Merge branch 'master' into madness 2016-06-28 14:18:30 +02:00
Harald Kuhr 458ef92af5 #253: Fix for non-subsampled YCbCr encoded JPEG-in-TIFF being decoded as RGB. 2016-06-28 12:54:25 +02:00
Torstein Krause Johansen ea5b9c5606 Merge remote-tracking branch 'upstream/master' 2016-06-28 12:43:11 +02:00
Harald Kuhr 788b11e4fa Added TODO for more custom formats. 2016-06-28 10:49:10 +02:00
Harald Kuhr 775cede14d Added test for warning when encountering incorrect JPEGInterchangeFormatLength 2016-06-28 10:47:38 +02:00
Harald Kuhr f4b61820ac #267: Reads monochrome images with gray ICC profile. 2016-06-23 16:15:22 +02:00
Harald Kuhr 9a6096664e #266: Fix NPE for empty streams. 2016-06-23 13:26:58 +02:00
Harald Kuhr c2aa7e3150 #265: Fix for old-style JPEG compressed TIFFs with incorrect JPEGInterchangeFormatLength 2016-06-17 16:10:02 +02:00
Oliver Schmidtmer 2c4c6d5a48 Extended AffineTransformOp for a Graphics2D fallback on filter-method 2016-06-15 23:14:08 +02:00
Harald Kuhr a4a314a0f9 Merge pull request #251 from Schmidor/tiff_missing_photometric
Fallback for missing TIFFTag.PhotometricInterpretation
2016-06-02 12:14:51 +02:00
Harald Kuhr b368da2154 Merge pull request #259 from Schmidor/copy_metadata
TiffWriter: copy image metadata to output
2016-06-02 12:14:04 +02:00
Harald Kuhr e9388e55ec #260 ProviderInfo fix 2016-06-02 11:24:57 +02:00
Harald Kuhr 8dd84930be TIFF metadata fixes. 2016-06-02 11:24:38 +02:00
Harald Kuhr 53487e916b Made PSDMetadataFormat instantiatable... 2016-06-02 11:24:01 +02:00
Harald Kuhr 621313e905 Added missing BSD headers. 2016-06-02 09:43:54 +02:00
Harald Kuhr 7773e7ba70 Initial TIFFStreamMetadata implementation. 2016-06-02 09:43:15 +02:00
Oliver Schmidtmer a6cd991722 TiffWriter: copy image metadata to output 2016-06-01 14:46:33 +02:00
Harald Kuhr 3d68b61f72 #258 SPI typos 2016-06-01 10:12:29 +02:00
Oliver Schmidtmer 454ee32791 Fallback for missing TIFFTag.PhotometricInterpretation 2016-05-31 23:57:58 +02:00
Harald Kuhr c29f895337 #256 ImageTypeSpecifiers fix 2016-05-26 16:15:10 +02:00
Harald Kuhr 77bbc066e3 #249 Remove unnecessary part of test that breaks on 8u60+ 2016-05-25 18:40:29 +02:00
Harald Kuhr b129117ee9 #254 Fix NPE reading TIFF Metadata when BitsPerSample not set 2016-05-25 10:50:42 +02:00
Harald Kuhr 3d36159982 Merge pull request #255 from Schmidor/ccitt_getnextchangingelement
Fix getNextChangingElement
2016-05-25 09:37:51 +02:00
Oliver Schmidtmer ceb2c82e5c Fix getNextChangingElement
The Testimage is distored without this change.
lastChangchingElement must be reduced by 2 as sometimes the nextChanchingElement is bevore the last chanching element. Reset the last bits does not work if the number is higher.
2016-05-24 16:39:59 +02:00
Harald Kuhr d4c9d53ea5 Merge pull request #250 from ankon/improvement/batik-version-property
Make the batik version configurable via a property
2016-05-20 16:47:14 +02:00
Andreas Kohn f8350623ea Make the batik version configurable via a property 2016-05-20 14:48:08 +02:00
Harald Kuhr 723632addf Merge pull request #247 from Eurybiadan/master
Fix for issue #244
2016-05-18 19:55:19 +02:00
Robert F Cooper 21ff12219a Merge branch 'master' of https://github.com/Eurybiadan/TwelveMonkeys.git
# Conflicts:
#	imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
2016-05-18 12:51:10 -04:00
Robert F Cooper eed4242aa4 A colorspace fix (Issue #244).
This commit should work properly.
2016-05-18 12:49:23 -04:00
Robert F Cooper ff0389055b A colorspace fix (Issue #244).
This committ should work properly.
2016-05-18 12:33:03 -04:00
Harald Kuhr 70bb5de4de Merge pull request #243 from Schmidor/faster_ccitt
CCITT speedup on getNextChangingElement
2016-05-11 11:05:40 +02:00
Oliver Schmidtmer 69d939042a CCITT speedup on getNextChangingElement 2016-05-10 22:58:27 +02:00
Harald Kuhr 89dc36d3b7 Merge pull request #236 from Schmidor/TIFFpages_noneImageSubIFD
TIFFPage.write did not expect subIFDs which do not contain image data
2016-05-09 10:05:54 +02:00
Harald Kuhr c373223ca3 Merge pull request #240 from Schmidor/TMI237
TMI #237: Fix for CCITT images with more b/w changes than columns
2016-05-09 09:51:42 +02:00
Oliver Schmidtmer fa0341f302 TMI #237: Fix for CCITT images with more b/w changes than columns 2016-05-07 01:06:53 +02:00
Oliver Schmidtmer 099dbb0073 TIFFPage.write did not expect subIFDs which do not contain image data 2016-05-03 14:18:01 +02:00
Harald Kuhr e0434a1dcb Merge branch 'Schmidor-tiffreader_emptytag' 2016-04-21 15:30:36 +02:00
Harald Kuhr f382f4b5f9 Manual merge of #223 2016-04-21 15:30:20 +02:00
Harald Kuhr d9324ef634 Merge pull request #207 from Schmidor/tiff_sequencewriter
Enable sequence writing on TIFFImageWriter
2016-04-21 15:00:51 +02:00
Harald Kuhr ed434dfb1d Merge pull request #217 from Schmidor/tiffreader_eof
Catch EOF at reading IFD pointer
2016-04-21 14:15:08 +02:00
Harald Kuhr f9229028ee Merge pull request #224 from Schmidor/tiff_oldjpeg_srcregion
Fix reading srcRegion for OLD_JPEG
2016-04-21 14:10:49 +02:00
Harald Kuhr 047884e3d9 #232: Cleanup after merge of #225 2016-04-21 14:05:45 +02:00
Harald Kuhr 2cec177c6d Merge pull request #225 from Schmidor/tiff_ccitt_errortolerance
More tolerant CCITT reading
2016-04-21 13:04:08 +02:00
Harald Kuhr 592ed04cfa Merge pull request #227 from Schmidor/tiffpages_oldjpeg
Support OLD_JPEG and SubIFDs in TIFFUtilities
2016-04-21 13:01:11 +02:00
Oliver Schmidtmer e0e6e263ac Testfile which claims to contain 6 rows, where the CCITT stream ends after 4 2016-02-23 23:25:45 +01:00
Oliver Schmidtmer ac49e206a1 Make IFD ReadOnly again 2016-02-23 23:07:31 +01:00
Oliver Schmidtmer 8387a9ad37 Support OLD_JPEG and SubIFDs in TIFFUtilities 2016-02-19 23:21:54 +01:00
Harald Kuhr 2955054a72 Image now properly copied onto the destination, rather than the other way around... 2016-02-18 18:07:50 +01:00
Oliver Schmidtmer ae548557bc Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys into tiffreader_eof 2016-02-17 22:40:46 +01:00
Oliver Schmidtmer 2258b4def2 More tolerant CCITT reading if the stream contains less lines than are tried to read 2016-02-17 15:08:06 +01:00
Oliver Schmidtmer d64eb40211 Improved test for catching EOF at reading the next-IFD pointer as EOF marker 2016-02-16 23:05:44 +01:00
Oliver Schmidtmer 6796910091 Fix reading bad SubIFDs again 2016-02-16 22:52:57 +01:00
Harald Kuhr a1de9ff448 #220 PlanarConfiguration now default to 1 (Chunky) according to the TIFF 6.0 spec. 2016-02-11 16:49:50 +01:00
Harald Kuhr eeeb22666c #214 PSDImageReader: Read long layer names 2016-02-03 15:48:19 +01:00
Harald Kuhr 1449155987 Minor doc fix. 2016-02-03 11:19:12 +01:00
Harald Kuhr 156fe8bb25 Fixed some access scopes and made classes final. 2016-02-03 10:46:04 +01:00
Oliver Schmidtmer 98493b6c16 Fix reading srcRegion for OLD_JPEG 2016-02-03 00:20:24 +01:00
Oliver Schmidtmer 672aa1a048 Continue reading the metadata if invalid tags were found 2016-02-02 23:52:03 +01:00
Oliver Schmidtmer a709381825 Test for catching EOF at reading the next-IFD pointer as EOF marker 2016-02-02 00:29:44 +01:00
Harald Kuhr 87db620dac Addded contributors. If you feel omitted, please send a pull request. 2016-01-29 15:42:08 +01:00
Harald Kuhr 73f3290e22 Re-adding contrib module 2016-01-29 11:57:39 +01:00
Harald Kuhr 8807ccd367 #213 Add license to POM 2016-01-29 11:54:15 +01:00
Oliver Schmidtmer 0997f5199a catch EOF at reading the next-IFD pointer as EOF marker 2016-01-28 20:12:11 +01:00
Oliver Schmidtmer e4f193400d Remove seeks on uncompressed tiffs to reenable streaming 2016-01-28 00:31:10 +01:00
Oliver Schmidtmer e145de01f3 corrected IFD & strip width calculations for sequence writer 2016-01-20 00:22:46 +01:00
Harald Kuhr a9428a1ecf Merge pull request #206 from Schmidor/TIFFUtilities
Merging Tiff utilities to new contrib module.
2016-01-18 09:12:04 +01:00
Oliver Schmidtmer fc80ac2ee9 Enable sequence writing on TIFFImageWriter 2016-01-17 17:12:35 +01:00
Schmidor c6e8711639 Remove runtime dependency to TIFF plugin from contrib module
Also removed public flag from plugin classes again. Replaced by temporary clones.
2016-01-13 23:00:31 +01:00
Schmidor 342dbe7d83 TIFFUtilities.applyOrientation: Switch to AffineTrandformOp for executing the transformation. 2016-01-13 00:17:32 +01:00
Harald Kuhr 10b8bef387 Fixed release date. 2016-01-04 09:11:54 +01:00
Harald Kuhr d5a35fa818 #195: Faster (and correct) test 2016-01-04 09:11:54 +01:00
Harald Kuhr 5c63dd2168 Merge pull request #200 from Schmidor/tiffmetadata_getfield
add TIFFImageMetadata.getTIFFField
2015-12-22 21:00:03 +01:00
Oliver Schmidtmer 9c011adfc2 add TIFFImageMetadata.getTIFFField 2015-12-22 15:44:30 +01:00
Schmidor d9ba98fb8e Add missing test dependency for TIFFUtilities 2015-12-16 23:47:08 +01:00
Schmidor 8c2af2c3c5 Finish TIFFUtilities.applyOrientation 2015-12-16 23:18:09 +01:00
Harald Kuhr 04a4c6e3ae #195: Fixed AIOOBE related to specific widths 2015-12-16 10:18:26 +01:00
Harald Kuhr 9e5204ddbb Updated README with latest version info. 2015-12-11 14:36:46 +01:00
Harald Kuhr 3b9d51dcc8 Fixed latest version header, it's really 3.2 2015-12-04 18:55:29 +01:00
Harald Kuhr 3214b3e595 Removed done item from todos. 2015-12-02 11:24:48 +01:00
Schmidor ad0d265f4f Init TIFFUtilities for mereging, splitting and rotating tiff pages 2015-12-02 01:48:38 +01:00
Schmidor 57402957ad Add contrib module 2015-12-02 01:46:55 +01:00
Harald Kuhr 6db06414af #195 ArrayIndexOutOfBoundsException for ResampleOp in certain cases 2015-11-27 13:20:44 +01:00
Harald Kuhr d097742002 Updated README with info on Batik versions. 2015-11-13 13:56:05 +01:00
Harald Kuhr 4e765fa61d #190 Updating to Batik 1.8 fixes incorrect size for WMF files. 2015-11-13 13:45:12 +01:00
Harald Kuhr fd73ae09bd #191 Support for SVG files without XML declaration 2015-11-13 13:39:04 +01:00
Harald Kuhr f6847e39c8 #190 Updated for Batik 1.8 2015-11-13 13:36:34 +01:00
Harald Kuhr 9c38ff9bdb Merge pull request #189 from Schmidor/CCITTWriter
Testcase for Merge #188
2015-11-12 09:17:31 +01:00
Schmidor be90082bd3 Testcase for "Fixed an issue with long runlengths in CCITTFax writing #188" 2015-11-12 00:38:41 +01:00
Harald Kuhr 7d531e3836 Merge pull request #188 from Schmidor/CCITTWriter
Fixed an issue with long runlengths in CCITTFax writing
2015-11-11 08:59:57 +01:00
Schmidor 5b6c57d934 Fixed an issue with long runlengths in CCITTFax writing 2015-11-11 01:17:32 +01:00
Harald Kuhr 3aa86bccf3 Updated for latest release. 2015-11-01 21:13:32 +01:00
Harald Kuhr 0feec02bc5 Merge remote-tracking branch 'origin/master' 2015-11-01 20:07:29 +01:00
Harald Kuhr 1ce2a06859 [maven-release-plugin] prepare for next development iteration 2015-11-01 19:51:40 +01:00
Harald Kuhr 9c9d2c700e [maven-release-plugin] prepare release twelvemonkeys-3.2 2015-11-01 19:51:34 +01:00
Harald Kuhr d3cbeae9ba Updating TODOs. 2015-10-28 15:32:15 +01:00
Harald Kuhr 062413d2d8 #187 Removing hardcoded test path/debug code. 2015-10-23 12:53:28 +02:00
Harald Kuhr e62922eb95 #184 Re-read the spec, that says ICC and ITU lab uses D50 by default. 2015-10-22 22:14:48 +02:00
Harald Kuhr 302035443d #173 Support for PhotometricInterpretation 8/CIELab, 9/ICCLab and 10/ITULab 2015-10-22 21:35:36 +02:00
Harald Kuhr 8a38b2fde6 #173 Support for PhotometricInterpretation 8/CIELab, 9/ICCLab and 10/ITULab 2015-10-22 21:35:10 +02:00
Harald Kuhr 30a7283b35 Enabling build status on the front page. 2015-10-16 11:04:07 +02:00
Harald Kuhr 4b363a0d64 Disabling OpenJDK in Travis for now. 2015-10-16 10:56:59 +02:00
Harald Kuhr 1096f9bedd Testting Travis CI 2015-10-16 10:38:20 +02:00
Harald Kuhr 73a609a2cb #183: Minor change, use Map instead of concrete HashMap. 2015-10-15 22:23:45 +02:00
Harald Kuhr 8379f08d78 #183: Fix failing tests after merge. 2015-10-15 22:21:49 +02:00
Harald Kuhr c33cfea02c Merge pull request #183 from Schmidor/CCITTWriter
CCITTFaxEncoderStream for TiffImageWriter
2015-10-15 22:16:46 +02:00
Harald Kuhr b85d0f7d6b #179: Fixed broken offsets for 16 and 32 bits PackBits data. 2015-10-15 19:06:05 +02:00
Harald Kuhr b6e44c5bff #184 Support for PlanarConfiguration 2 + bonus changes. 2015-10-15 17:47:20 +02:00
Schmidor e5c0fead38 Fix BlackIsZero handling in CCITTFaxEncoderStream 2015-10-11 17:59:01 +02:00
Schmidor 306a8ae166 Change PhotometricInterpretation / SamplesPerPixel detection for one bit per pixel ColorSpaces 2015-10-11 17:31:28 +02:00
Schmidor 6c702c447b CCITT Fax writer: fill should only add bits if byte is not empty 2015-10-11 16:34:37 +02:00
Schmidor 237079bcc9 Normalize Black on byte Data 2015-10-11 16:32:39 +02:00
Schmidor 07617b49ce Support CCITT Fax Encoder in TiffImageWriter 2015-10-10 16:31:40 +02:00
Schmidor c8621439c0 CCITT Fax writer: adjust formatting and write finishing bytes on last row instead of on stream closing 2015-10-09 23:32:09 +02:00
Schmidor 585b5faa62 Merge remote-tracking branch 'remotes/haraldk/master' into CCITTWriter 2015-10-08 01:12:13 +02:00
Schmidor 39b92ab19f Work in progress: CCITT Encoder 2015-10-08 00:38:31 +02:00
Schmidor 8e11d95fd6 Work in progress: CCITT Encoder 2015-10-04 00:40:36 +02:00
Harald Kuhr 867ca61755 TMI #172: Fix IIOBE/Buffer overflow issue. 2015-08-26 11:16:35 +02:00
Harald Kuhr 2bdfa4fccb TMI-TIFF: Better ICC profile support + more BitsPerSample configurations. 2015-08-26 10:33:01 +02:00
Harald Kuhr d4e34d6109 TMI #170 Fix General CMM error517/LCMS error 13 (Ignore incompatible ICC profile) 2015-08-25 14:13:35 +02:00
Harald Kuhr 25bef72ac0 TMI #170 Fix General CMM error517/LCMS error 13 (Ignore incompatible ICC profile) 2015-08-25 14:12:47 +02:00
Harald Kuhr e957120480 TMI #169 NullPointerException when reading JPEG image 2015-08-25 12:12:10 +02:00
Harald Kuhr 35edfc519f TMI #168: Change Maven test dependencies from classifier test to type test-jar for better sbt support 2015-08-24 11:09:15 +02:00
Harald Kuhr df735b9a45 TMI #168: Change Maven test dependencies from classifier test to type test-jar for better sbt support 2015-08-24 11:07:17 +02:00
Harald Kuhr 99c5fea005 TMI #166 Remove printStackTrace() from EXIFReader 2015-08-21 15:24:49 +02:00
Harald Kuhr 4b70b0cf1c TMI #165 ICOImageReader prints debug information. 2015-08-21 12:57:29 +02:00
Harald Kuhr 22e490ca40 TMI #164 Support for TIFF with 32 but floating point samples. 2015-08-21 10:49:46 +02:00
Harald Kuhr 211b31e154 TMI #163 Support for TIFF containing signed integral other than 16 bit. 2015-08-21 10:38:38 +02:00
Harald Kuhr f7d9a64e41 TMI #162 Support for TIFF containing gray + alpha. 2015-08-20 17:06:19 +02:00
Harald Kuhr 5c76899c04 Fixed link to point to page 1. 2015-08-14 14:50:35 +02:00
Harald Kuhr d792f0b7c4 Updating links and info to latest release, 3.1.2 2015-08-14 12:54:58 +02:00
Harald Kuhr 077e40acf2 TMI#159: Clean-up after merge + added test cases for the CCITT images. 2015-08-14 09:58:15 +02:00
Harald Kuhr 40b11710ea Merge pull request #160 from Schmidor/master
Fix for Issue #159
2015-08-14 09:32:31 +02:00
Schmidor d86af8bd82 Remove debug output 2015-08-13 23:45:38 +02:00
Schmidor 60e3d6e5af Fix ArrayIndexOutOfBoundsException in CCITTFaxDecoderStream.decode2D #159 2015-08-13 23:27:46 +02:00
Oliver Schmidtmer 6ef5a5a7ee Merge pull request #2 from haraldk/master
merge from upstream
2015-08-13 18:05:16 +02:00
Harald Kuhr 61b07cb9cb TMI-139: Support for writing TIFF files with custom resolution value (made TIFFImageMetadata class public). 2015-08-12 11:03:31 +02:00
Harald Kuhr c913ef445b TMI-139: Support for writing TIFF files with custom resolution value. 2015-08-12 10:48:58 +02:00
Harald Kuhr 517fc770bd TMI-136: Better TIFF (IFD) format write support. 2015-08-12 10:23:15 +02:00
Harald Kuhr 998851c9fb TMI-158: Fixed build instructions. Building with Java 8 should now work without any special settings. 2015-08-04 08:28:29 +02:00
Oliver Schmidtmer 40555641af Merge pull request #1 from haraldk/master
merge from upstream
2015-08-03 13:58:12 +02:00
Harald Kuhr e1b5e6bfc9 TMI-157: Added imageio-hdr to BOM. 2015-07-31 13:57:22 +02:00
Harald Kuhr bef0b762fa TMI-157: Added license headers, and readRaster method for access to the RGBE data. 2015-07-29 09:57:33 +02:00
Harald Kuhr 9bb67742b7 TMI-157: Radiance RGBE (.HDR) support 2015-07-28 22:21:45 +02:00
Harald Kuhr fd4745f6a6 TMI-156: Now correctly interprets alpha in TGA format + bonus thumbnail & metadata fixes. 2015-07-27 15:07:26 +02:00
Harald Kuhr 4eb7426596 TMI-154: Fix for AccessControlException when reading "Generic CMYK.icc" 2015-07-27 11:58:35 +02:00
Harald Kuhr 1ee22e120d TMI-61: Re-added some validations, removed printStacktraces, and some minor code style issues after merge. 2015-07-16 23:50:34 +02:00
Harald Kuhr d8d2764eb1 TMI-TIFF: Added format name synonyms. 2015-07-16 23:22:09 +02:00
Harald Kuhr 94f2b81d2a Merge pull request #148 from Schmidor/master
TMI-61: CCITT Group 3/4 Fax Decoder
2015-07-16 23:17:13 +02:00
Harald Kuhr d7c8df184e TMI-JPEG: Reading inverted Adobe JPEGs 2015-07-13 12:28:27 +02:00
Harald Kuhr d1cb941f06 TMI-150: Fix for 24 bit images with bitmask. 2015-07-11 13:07:20 +02:00
Harald Kuhr eef4c72daa Code clean-up. 2015-07-08 23:21:13 +02:00
Schmidor bad9aebdee Work in progress: CCITT Encoder 2015-07-05 18:15:43 +02:00
Schmidor 6facef3142 CCITTFax: Output Zero for White 2015-07-05 17:57:04 +02:00
Schmidor 1e6227bee5 CCITT FAX Encoding: Support FillOrder Reversed 2015-07-04 00:42:09 +02:00
Schmidor 62862d835a CITT Group 3/4 Support - corrected start index in 2d decoding 2015-07-04 00:13:27 +02:00
Schmidor 37e1723891 CITT Group 3/4 Support - libTiff generated files which are the test datastream sources 2015-07-02 23:37:06 +02:00
Schmidor 2b22b02523 CITT Group 3/4 Support - some cleanup, group 3 /T.4 should work now 2015-07-02 23:35:19 +02:00
Schmidor a2042e75bf CITT Group 3/4 Support - Pass Mode / 2D Change referencing in work 2015-07-01 01:10:56 +02:00
Harald Kuhr 1a43958aeb TMI-139: Work in progress: TIFF image metadata. 2015-06-22 11:15:44 +02:00
Harald Kuhr f4cc310096 TMI-139: Work in progress: TIFF image metadata. 2015-06-22 11:11:37 +02:00
Harald Kuhr 7e65164b87 TMI-146: Now correctly sets ExtraSamples only when there are more components in the raster than color components in the color space. 2015-06-17 13:24:45 +02:00
Harald Kuhr 51ca99d433 Updating readme to latest release. 2015-06-11 12:47:36 +02:00
Harald Kuhr c80f514131 Updated POM to 3.2-SNAPSHOT 2015-06-11 11:32:46 +02:00
Harald Kuhr da8a013575 TMI-143: Support for CMYK JPEG in TIFF. 2015-06-11 11:28:31 +02:00
Harald Kuhr 3bdc117aa3 Updated POM to 3.2-SNAPSHOT 2015-06-09 12:12:46 +02:00
Harald Kuhr f645e46495 Updated POM to 3.2-SNAPSHOT 2015-06-08 14:12:04 +02:00
Harald Kuhr 8052fbc5b8 TMI-142: Added missing resource file, now making sure the TIFFImageWriter is registered. 2015-06-08 11:11:45 +02:00
Harald Kuhr b2142cb449 Updated to new format name style. 2015-06-05 20:11:54 +02:00
Harald Kuhr 821c20c09a TMI-136: Clean-up and added TODOs. 2015-06-05 10:58:54 +02:00
Harald Kuhr 194f34894f Added more PSD resource + clean-up. 2015-06-05 10:50:16 +02:00
Harald Kuhr f6d5a60600 TMI-136: Added abstract MetadataWriter, along with preliminary IPTCWriter + test cases. Retrofit EXIFWriter. Loads of small changes and clean-up. 2015-06-05 10:49:31 +02:00
Harald Kuhr bbaa3e1186 TMI-140: JPEG with corrupted ICC profile (new kind) can now be read. 2015-05-28 23:01:51 +02:00
Harald Kuhr c97ebae303 Fixed JavaDoc (you're welcome Sigi ;-)). 2015-05-27 15:53:50 +02:00
Harald Kuhr 1ed5b3899b TM-138: DateUtil doesn't support sub-hour offset timezones. 2015-05-27 15:50:35 +02:00
Harald Kuhr 203b330c99 TM-138: DateUtil doesn't support sub-hour offset timezones. 2015-05-27 15:47:26 +02:00
Harald Kuhr 9b71a0cba7 TMI-134: Cannot read PSD images with PSD Layer Mask data size 28 2015-05-05 11:40:54 +02:00
Harald Kuhr 1a8948ece9 TMI-TIFF: Faster LZW encoder. 2015-04-30 10:21:35 +02:00
Harald Kuhr 051a1dcb5b Preparing JPEGImageReader for extension. 2015-04-30 10:20:35 +02:00
Harald Kuhr 2db58dc73d Added download links to old version. 2015-04-24 12:45:11 +02:00
Harald Kuhr 835d8a11dd Added download links to old version. 2015-04-24 12:43:02 +02:00
Harald Kuhr f3b6d40f19 Fixed typo. 2015-04-12 20:41:00 +02:00
Harald Kuhr 7f34f83768 Updated links and versions (added new plugins to list). 2015-04-12 20:19:17 +02:00
Harald Kuhr 7d0e02fce8 Updated links and versions (now with correct versions...). 2015-04-10 13:20:59 +02:00
Harald Kuhr 429923ec0d Merge remote-tracking branch 'origin/master' 2015-04-10 13:07:19 +02:00
Harald Kuhr 4dbe10846f Updated links and versions. 2015-04-10 13:06:59 +02:00
Harald Kuhr 475fdec33f [maven-release-plugin] prepare for next development iteration 2015-04-10 12:16:14 +02:00
Shihab Uddin e14cbbd853 Merge remote-tracking branch 'upstream/master'
Conflicts:
	pom.xml
2015-03-30 13:53:30 +02:00
Rune Bremnes a4ca20e2f6 Added distributionManagement. 2014-03-04 15:04:01 +01:00
Rune Bremnes 0518cc813b Reenabling tests after sync from upstream. 2014-03-04 14:43:41 +01:00
Rune Bremnes 90758ba882 Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys 2014-03-04 14:37:02 +01:00
Rune Bremnes a03d1951a3 Ignoring some more tests. 2014-03-03 16:14:22 +01:00
Rune Bremnes 1402e78144 Clean up after merge. 2014-03-03 16:09:01 +01:00
Rune Bremnes 113480b9e7 Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys
Conflicts:
	imageio/imageio-reference/pom.xml
	imageio/imageio-thumbsdb/pom.xml
2014-03-03 12:33:01 +01:00
Rune Bremnes 0a7f250566 Fix reading jpeg images where last scanline is higher than the y
source subsampling offset.
2014-02-24 13:27:16 +01:00
Rune Bremnes 1e03850960 Added failing testcase for JPEGImageReader. 2014-02-24 12:42:31 +01:00
Rune Bremnes cb276c8aff Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys
Conflicts:
	sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java
	sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java
	servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java
	servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java
	servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java
	servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java
	servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java
	servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java
	servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java
	servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java
	servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java
2014-02-24 10:28:26 +01:00
Rune Bremnes c5929d9479 Merge remote-tracking branch 'upstream/master'
Conflicts:
	servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java
	servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java
	servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java
	servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java
	servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java
2013-08-14 09:45:32 +02:00
Shihab Uddin f28ad2d396 building 3.0-ece-1 2012-09-26 13:42:04 +02:00
Shihab Uddin a8e5906569 use escenic repo 2012-09-26 13:39:46 +02:00
Shihab Uddin 5bd8c37c2d fixed merge conflicts 2012-09-26 11:51:25 +02:00
Erlend Hamnaberg e72700b032 Use rectangle instead of package protected method. 2012-09-26 11:48:26 +02:00
Erlend Hamnaberg 2f06f2de6d Cleanup of AreaOfInterest
Conflicts:
	servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java
2012-09-26 11:47:53 +02:00
Erlend Hamnaberg 8137165bac Cleanup of AreaOfInterest
- Extracted AreaOfInterest into an interface
- Added an AreaOfInterestFactory.
- Use AreaOfInterestFactory in ImageServletResponseImpl
- fixed version

Conflicts:
	servlet/pom.xml
	servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java
2012-09-26 11:45:04 +02:00
Erlend Hamnaberg 3628f3b392 Cleanup of AreaOfInterest 2012-09-26 11:42:13 +02:00
Erlend Hamnaberg be959ce3f3 Rule Of thirds:
- enable with system property
 - extracted AreaOfInterest into a separate class.

Conflicts:
	servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java
	servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java
2012-09-26 11:40:42 +02:00
Shihab Uddin d6e508662c Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys 2012-09-26 11:17:30 +02:00
Erlend Hamnaberg 0d8920aec4 Merge commit 'upstream/master'
Conflicts:
	imageio/imageio-reference/pom.xml
	imageio/imageio-thumbsdb/pom.xml
	servlet/pom.xml
2010-04-20 11:40:30 +02:00
Erlend Hamnaberg 03d8e06819 line feed changes 2010-04-20 11:07:43 +02:00
Øyvind Løkling 5131e02e6c Released and tagg for internal use 2010-01-05 14:42:38 +01:00
325 changed files with 17170 additions and 2834 deletions
+6
View File
@@ -0,0 +1,6 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling
# - openjdk7
+97 -35
View File
@@ -1,6 +1,8 @@
## Latest
TwelveMonkeys ImageIO 3.0.2 is released.
Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys)
TwelveMonkeys ImageIO [3.2.1](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.2.1%22) is released (Dec. 11th, 2015).
## About
@@ -20,7 +22,7 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b
Mainstream format support
#### MS Windows/IBM OS/2 Device Independent Bitmap (BMP) *3.1*
#### BMP - MS Windows/IBM OS/2 Device Independent Bitmap
* Read support for all known versions of the DIB/BMP format
* Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE
@@ -32,6 +34,8 @@ Mainstream format support
#### JPEG
* Read support for the following JPEG "flavors":
* All JFIF compliant JPEGs
* All Exif compliant JPEGs
* YCbCr JPEGs without JFIF segment (converted to RGB, using embedded ICC profile)
* CMYK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
* Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
@@ -65,7 +69,7 @@ without success so far).
Alternatively, if you have or know of a JPEG-2000 implementation in Java with a suitable license, get in touch. :-)
#### NetPBM Portable Any Map (PNM) *3.1*
#### PNM - NetPBM Portable Any Map
* Read support for the following file types:
* PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel
@@ -78,7 +82,7 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* PAM in 'P7' (binary) format
* Standard metadata support
#### Adobe Photoshop Document (PSD)
#### PSD - Adobe Photoshop Document
* Read support for the following file types:
* Monochrome, 1 channel, 1 bit
@@ -89,16 +93,16 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* CMYK, 4-5 channels, 8, 16 and 32 bit
* Read support for the following compression types:
* Uncompressed
* RLE (PackBits)
* RLE (PackBits)<
* Layer support
* Image layers only, in all of the above types
* Thumbnail support
* JPEG
* RAW (RGB)
* Support for "Large Document Format" (PSB)
* Native metadata support
* Native and Standard metadata support
#### Aldus/Adobe Tagged Image File Format (TIFF)
#### TIFF - Aldus/Adobe Tagged Image File Format
* Read support for the following "Baseline" TIFF file types:
* Class B (Bi-level), all relevant compression types, 1 bit per sample
@@ -107,30 +111,42 @@ Alternatively, if you have or know of a JPEG-2000 implementation in Java with a
* Class R (RGB), all relevant compression types, 8 or 16 bits per sample, unsigned integer
* Read support for the following TIFF extensions:
* Tiling
* Class F (Facsimile), CCITT Modified Huffman RLE, T4 and T6 (type 2, 3 and 4) compressions.
* LZW Compression (type 5)
* "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined
* JPEG Compression (type 7)
* ZLib (aka Adobe-style Deflate) Compression (type 8)
* Deflate Compression (type 32946)
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression
* Alpha channel (ExtraSamples type 1/Associated Alpha)
* Alpha channel (ExtraSamples type 1/Associated Alpha and type 2/Unassociated Alpha)
* CMYK data (PhotometricInterpretation type 5/Separated)
* YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
* CIELab data in TIFF, ITU and ICC variants (PhotometricInterpretation type 9, 10 and 11)
* Planar data (PlanarConfiguration type 2/Planar)
* ICC profiles (ICCProfile)
* BitsPerSample values up to 16 for most PhotometricInterpretations
* Multiple images (pages) in one file
* Write support for most "Baseline" TIFF options
* Uncompressed, PackBits, ZLib and Deflate
* Currently missing the CCITT fax encodings
* Additional support for CCITT T4 and and T6 compressions.
* Additional support for LZW and JPEG (type 7) compressions
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate
* Native and Standard metadata support
Legacy formats
#### Commodore Amiga/Electronic Arts Interchange File Format (IFF)
#### HDR - Radiance High Dynamic Range RGBE Format
* Legacy format, allows reading popular image from the Commodore Amiga computer.
* Read support for the most common RGBE (.hdr) format
* Samples are converted to 32 bit floating point (`float`) and normalized using a global tone mapper by default.
* Support for custom global tone mappers
* Alternatively, use a "null-tone mapper", for unnormalized data (allows local tone mapping)
* Unconverted RGBE samples accessible using `readRaster`
* Standard metadata support
#### IFF - Commodore Amiga/Electronic Arts Interchange File Format
* Legacy format, allows reading popular image format from the Commodore Amiga computer.
* Read support for the following file types:
* ILBM Indexed color, 1-8 interleaved bit planes, including 6 bit EHB
* ILBM Gray, 8 bit interleaved bit planes
@@ -146,7 +162,7 @@ Legacy formats
* Uncompressed
* RLE (PackBits)
#### ZSoft Paintbrush Format (PCX) *3.1*
#### PCX - ZSoft Paintbrush Format
* Read support for the following file types:
* Indexed color, 1, 2, 4 or 8 bits per pixel, bit planes or interleaved
@@ -158,7 +174,7 @@ Legacy formats
* RLE compressed
* Standard metadata support
#### Apple Mac Paint Picture Format (PICT)
#### PICT - Apple Mac Paint Picture Format
* Legacy format, especially useful for reading OS X clipboard data.
* Read support for the following file types:
@@ -169,7 +185,7 @@ Legacy formats
* Write support for RGB pixel data:
* QuickDraw pixmap
#### Silicon Graphics Image Format (SGI) *3.1*
#### SGI - Silicon Graphics Image Format
* Read support for the following file types:
* 1, 2, 3 or 4 channel image data
@@ -179,7 +195,7 @@ Legacy formats
* RLE compressed
* Standard metadata support
#### Truevision TGA Image Format (TGA) *3.1*
#### TGA - Truevision TGA Image Format
* Read support for the following file types:
* ColorMapped
@@ -192,35 +208,39 @@ Legacy formats
Icon/other formats
#### Apple Icon Image (ICNS)
#### ICNS - Apple Icon Image
* Read support for the following icon types:
* All known "native" icon types
* Large PNG encoded icons
* Large JPEG 2000 encoded icons (requires JPEG 2000 ImageIO plugin or fallback to `sips` command line tool)
#### MS Windows Icon and Cursor Formats (ICO & CUR)
#### ICO & CUR - MS Windows Icon and Cursor Formats
* Read support for the following file types:
* ICO Indexed color, 1, 4 and 8 bit
* ICO RGB, 16, 24 and 32 bit
* CUR Indexed color, 1, 4 and 8 bit
* CUR RGB, 16, 24 and 32 bit
* *3.1* Note: These formats are now part of the BMP plugin
#### MS Windows Thumbs DB (Thumbs.db)
#### Thumbs.db - MS Windows Thumbs DB
* Read support
Other formats, using 3rd party libraries
#### Scalable Vector Graphics (SVG)
#### SVG - Scalable Vector Graphics
* Read-only support using Batik
#### MS Windows MetaFile (WMF)
#### WMF - MS Windows MetaFile
* Limited read-only support using Batik
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](http://xmlgraphics.apache.org/security.html), and make sure you use
either version 1.6.1, 1.7.1 or 1.8+.*
## Basic usage
@@ -419,9 +439,9 @@ Build the project (using [Maven](http://maven.apache.org/download.cgi)):
$ mvn package
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
It's possible to build using OpenJDK, but some tests will fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether. To build using JDK 8, you need to pass `-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider` to revert to the color manangement system used in Java 7.
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
@@ -459,12 +479,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.0.2</version> <!-- Alternatively, build your own version -->
<version>3.2.1</version> <!-- Alternatively, build your own version -->
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.0.2</version> <!-- Alternatively, build your own version -->
<version>3.2.1</version> <!-- Alternatively, build your own version -->
</dependency>
</dependencies>
@@ -472,25 +492,66 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.0.2.jar
twelvemonkeys-common-io-3.0.2.jar
twelvemonkeys-common-image-3.0.2.jar
twelvemonkeys-imageio-core-3.0.2.jar
twelvemonkeys-imageio-metadata-3.0.2.jar
twelvemonkeys-imageio-jpeg-3.0.2.jar
twelvemonkeys-imageio-tiff-3.0.2.jar
twelvemonkeys-common-lang-3.2.1.jar
twelvemonkeys-common-io-3.2.1.jar
twelvemonkeys-common-image-3.2.1.jar
twelvemonkeys-imageio-core-3.2.1.jar
twelvemonkeys-imageio-metadata-3.2.1.jar
twelvemonkeys-imageio-jpeg-3.2.1.jar
twelvemonkeys-imageio-tiff-3.2.1.jar
### Links to prebuilt binaries
##### Latest version (3.2.x)
Requires Java 7 or later.
Common dependencies
* [common-lang-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.2.1/common-lang-3.2.1.jar)
* [common-io-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.2.1/common-io-3.2.1.jar)
* [common-image-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.2.1/common-image-3.2.1.jar)
ImageIO dependencies
* [imageio-core-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.2.1/imageio-core-3.2.1.jar)
* [imageio-metadata-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.2.1/imageio-metadata-3.2.1.jar)
ImageIO plugins
* [imageio-bmp-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.2.1/imageio-bmp-3.2.1.jar)
* [imageio-jpeg-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.2.1/imageio-jpeg-3.2.1.jar)
* [imageio-tiff-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.2.1/imageio-tiff-3.2.1.jar)
* [imageio-pnm-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.2.1/imageio-pnm-3.2.1.jar)
* [imageio-psd-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.2.1/imageio-psd-3.2.1.jar)
* [imageio-hdr-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.2.1/imageio-hdr-3.2.1.jar)
* [imageio-iff-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.2.1/imageio-iff-3.2.1.jar)
* [imageio-pcx-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.2.1/imageio-pcx-3.2.1.jar)
* [imageio-pict-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.2.1/imageio-pict-3.2.1.jar)
* [imageio-sgi-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.2.1/imageio-sgi-3.2.1.jar)
* [imageio-tga-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.2.1/imageio-tga-3.2.1.jar)
* [imageio-icns-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.2.1/imageio-icns-3.2.1.jar)
* [imageio-thumbsdb-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.2.1/imageio-thumbsdb-3.2.1.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.2.1/imageio-batik-3.2.1.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.2.1/imageio-clippath-3.2.1.jar)
Servlet support
* [servlet-3.2.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.2.1/servlet-3.2.1.jar)
##### Old version (3.0.x)
Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8*.
Common dependencies
* [common-lang-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar)
* [common-io-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar)
* [common-image-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar)
ImageIO dependencies
* [imageio-core-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar)
* [imageio-metadata-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar)
ImageIO plugins
* [imageio-jpeg-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar)
* [imageio-tiff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar)
@@ -500,14 +561,15 @@ ImageIO plugins
* [imageio-icns-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar)
* [imageio-ico-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar)
* [imageio-thumbsdb-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar)
* [imageio-jmagick-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar)
Servlet support
* [servlet-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
## License
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
+6 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
@@ -58,6 +58,11 @@
<artifactId>imageio-bmp</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-hdr</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-icns</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
@@ -0,0 +1,151 @@
/*
* Copyright (c) 2016, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.image;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
/**
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
* <p>Currently only a modification on {@link #filter(BufferedImage, BufferedImage)} is done, which does a Graphics2D fallback for the native lib.</p>
*
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
* @author last modified by $Author$
* @version $Id$
*/
public class AffineTransformOp implements BufferedImageOp, RasterOp {
final java.awt.image.AffineTransformOp delegate;
public static final int TYPE_NEAREST_NEIGHBOR = java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
public static final int TYPE_BILINEAR = java.awt.image.AffineTransformOp.TYPE_BILINEAR;
public static final int TYPE_BICUBIC = java.awt.image.AffineTransformOp.TYPE_BICUBIC;
/**
* @param xform The {@link AffineTransform} to use for the operation.
* @param hints The {@link RenderingHints} object used to specify the interpolation type for the operation.
*/
public AffineTransformOp(final AffineTransform xform, final RenderingHints hints) {
delegate = new java.awt.image.AffineTransformOp(xform, hints);
}
/**
* @param xform The {@link AffineTransform} to use for the operation.
* @param interpolationType One of the integer interpolation type constants defined by this class: {@link #TYPE_NEAREST_NEIGHBOR}, {@link #TYPE_BILINEAR}, {@link #TYPE_BICUBIC}.
*/
public AffineTransformOp(final AffineTransform xform, final int interpolationType) {
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
}
@Override
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
try {
return delegate.filter(src, dst);
}
catch (ImagingOpException ex) {
if (dst == null) {
dst = createCompatibleDestImage(src, src.getColorModel());
}
Graphics2D g2d = null;
try {
g2d = dst.createGraphics();
int interpolationType = delegate.getInterpolationType();
if (interpolationType > 0) {
Object interpolationValue = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
switch (interpolationType) {
case java.awt.image.AffineTransformOp.TYPE_BILINEAR:
interpolationValue = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
break;
case java.awt.image.AffineTransformOp.TYPE_BICUBIC:
interpolationValue = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
break;
}
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, interpolationValue);
}
else if (getRenderingHints() != null) {
g2d.setRenderingHints(getRenderingHints());
}
g2d.drawImage(src, delegate.getTransform(), null);
return dst;
}
finally {
if (g2d != null) {
g2d.dispose();
}
}
}
}
@Override
public Rectangle2D getBounds2D(final BufferedImage src) {
return delegate.getBounds2D(src);
}
@Override
public BufferedImage createCompatibleDestImage(final BufferedImage src, final ColorModel destCM) {
return delegate.createCompatibleDestImage(src, destCM);
}
@Override
public WritableRaster filter(final Raster src, final WritableRaster dest) {
return delegate.filter(src, dest);
}
@Override
public Rectangle2D getBounds2D(final Raster src) {
return delegate.getBounds2D(src);
}
@Override
public WritableRaster createCompatibleDestRaster(final Raster src) {
return delegate.createCompatibleDestRaster(src);
}
@Override
public Point2D getPoint2D(final Point2D srcPt, final Point2D dstPt) {
return delegate.getPoint2D(srcPt, dstPt);
}
@Override
public RenderingHints getRenderingHints() {
return delegate.getRenderingHints();
}
}
@@ -358,8 +358,9 @@ public final class ImageUtil {
}
/**
* Creates a copy of the given image. The image will have the same
* color model and raster type, but will not share image (pixel) data.
* Creates a deep copy of the given image. The image will have the same
* color model and raster type, but will not share image (pixel) data
* with the input image.
*
* @param pImage the image to clone.
*
@@ -378,7 +379,7 @@ public final class ImageUtil {
cm.createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()),
cm.isAlphaPremultiplied(), null);
drawOnto(pImage, img);
drawOnto(img, pImage);
return img;
}
@@ -1336,7 +1336,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
//contribX.n = 0;
contribX.p = new Contributor[(int) (width * 2.0 + 1.0)];
contribX.p = new Contributor[(int) (width * 2.0 + 1.0 + 0.5)];
center = (double) i / xscale;
int left = (int) Math.ceil(center - width);// Note: Assumes width <= .5
@@ -1387,7 +1387,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
else {
/* Expanding image */
//contribX.n = 0;
contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0)];
contribX.p = new Contributor[(int) (fwidth * 2.0 + 1.0 + 0.5)];
center = (double) i / xscale;
int left = (int) Math.ceil(center - fwidth);
@@ -1465,7 +1465,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
for (int i = 0; i < dstHeight; i++) {
//contribY[i].n = 0;
contribY[i].p = new Contributor[(int) (width * 2.0 + 1)];
contribY[i].p = new Contributor[(int) (width * 2.0 + 1 + 0.5)];
double center = (double) i / yscale;
int left = (int) Math.ceil(center - width);
@@ -1516,7 +1516,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
else {
for (int i = 0; i < dstHeight; ++i) {
//contribY[i].n = 0;
contribY[i].p = new Contributor[(int) (fwidth * 2 + 1)];
contribY[i].p = new Contributor[(int) (fwidth * 2 + 1 + 0.5)];
double center = (double) i / yscale;
double left = Math.ceil(center - fwidth);
@@ -0,0 +1,255 @@
package com.twelvemonkeys.image;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.*;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
/**
* AffineTransformOpTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: AffineTransformOpTest.java,v 1.0 03/06/16 harald.kuhr Exp$
*/
public class AffineTransformOpTest {
// Some notes:
// It would be nice to have the following classes from imageio-core available:
// - ColorSpaces (for CMYK testing)
// - ImageTypeSpecifiers (for correct specs)
// Would perhaps be better to use parameterized test case
// Is it enough to test only (quadrant) rotation? Or should we test scale/translate/arbitrary rotation etc?
// TYPE_INT_RGB == 1 (min), TYPE_BYTE_INDEXED == 13 (max), TYPE_CUSTOM (0) excluded
private static final List<Integer> TYPES = Arrays.asList(
BufferedImage.TYPE_INT_RGB,
BufferedImage.TYPE_INT_ARGB,
BufferedImage.TYPE_INT_ARGB_PRE,
BufferedImage.TYPE_INT_BGR,
BufferedImage.TYPE_3BYTE_BGR,
BufferedImage.TYPE_4BYTE_ABGR,
BufferedImage.TYPE_4BYTE_ABGR_PRE,
BufferedImage.TYPE_USHORT_565_RGB,
BufferedImage.TYPE_USHORT_555_RGB,
BufferedImage.TYPE_BYTE_GRAY,
BufferedImage.TYPE_USHORT_GRAY,
BufferedImage.TYPE_BYTE_BINARY,
BufferedImage.TYPE_BYTE_BINARY
);
private static final ColorSpace GRAY = ColorSpace.getInstance(ColorSpace.CS_GRAY);
private static final ColorSpace S_RGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
// Most of these will fail using the standard Op
private static final List<ImageTypeSpecifier> SPECS = Arrays.asList(
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_USHORT, true, false),
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_SHORT, true, false),
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_INT, true, false),
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_FLOAT, true, false),
ImageTypeSpecifier.createInterleaved(GRAY, new int[] {0, 1}, DataBuffer.TYPE_DOUBLE, true, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_USHORT, false, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_SHORT, false, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_INT, false, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2}, DataBuffer.TYPE_DOUBLE, false, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_USHORT, true, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_SHORT, true, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_INT, true, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_FLOAT, true, false),
ImageTypeSpecifier.createInterleaved(S_RGB, new int[] {0, 1, 2, 3}, DataBuffer.TYPE_DOUBLE, true, false)
);
private final int width = 30;
private final int height = 20;
@Test
public void testGetPoint2D() {
AffineTransform rotateInstance = AffineTransform.getRotateInstance(2.1);
BufferedImageOp original = new java.awt.image.AffineTransformOp(rotateInstance, null);
BufferedImageOp fallback = new com.twelvemonkeys.image.AffineTransformOp(rotateInstance, null);
Point2D point = new Point2D.Double(39.7, 42.91);
assertEquals(original.getPoint2D(point, null), fallback.getPoint2D(point, null));
}
@Test
public void testGetBounds2D() {
AffineTransform shearInstance = AffineTransform.getShearInstance(33.77, 77.33);
BufferedImageOp original = new java.awt.image.AffineTransformOp(shearInstance, null);
BufferedImageOp fallback = new com.twelvemonkeys.image.AffineTransformOp(shearInstance, null);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
assertEquals(original.getBounds2D(image), fallback.getBounds2D(image));
}
// TODO: ...etc. For all delegated methods, just test that it does exactly what the original does.
// It won't test much for now, but it will make sure we don't accidentally break things in the future.
@Test
public void testFilterRotateBIStandard() {
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
for (Integer type : TYPES) {
BufferedImage image = new BufferedImage(width, height, type);
BufferedImage jreResult = jreOp.filter(image, null);
BufferedImage tmResult = tmOp.filter(image, null);
assertNotNull("No result!", tmResult);
assertEquals("Bad type", jreResult.getType(), tmResult.getType());
assertEquals("Incorrect color model", jreResult.getColorModel(), tmResult.getColorModel());
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
}
}
@Test
public void testFilterRotateBICustom() {
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
for (ImageTypeSpecifier spec : SPECS) {
BufferedImage image = spec.createBufferedImage(width, height);
BufferedImage tmResult = tmOp.filter(image, null);
assertNotNull("No result!", tmResult);
BufferedImage jreResult = null;
try {
jreResult = jreOp.filter(image, null);
}
catch (ImagingOpException ignore) {
// We expect this to fail for certain cases, that's why we crated the class in the first place
}
if (jreResult != null) {
assertEquals("Bad type", jreResult.getType(), tmResult.getType());
assertEquals("Incorrect color model", jreResult.getColorModel(), tmResult.getColorModel());
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
}
else {
assertEquals("Bad type", spec.getBufferedImageType(), tmResult.getType());
assertEquals("Incorrect color model", spec.getColorModel(), tmResult.getColorModel());
assertEquals("Incorrect width", height, tmResult.getWidth());
assertEquals("Incorrect height", width, tmResult.getHeight());
}
}
}
// Test RasterOp variants
@Test
public void testGetBounds2DRaster() {
AffineTransform shearInstance = AffineTransform.getShearInstance(33.77, 77.33);
RasterOp original = new java.awt.image.AffineTransformOp(shearInstance, null);
RasterOp fallback = new com.twelvemonkeys.image.AffineTransformOp(shearInstance, null);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
assertEquals(original.getBounds2D(image.getRaster()), fallback.getBounds2D(image.getRaster()));
}
@Test
public void testFilterRotateRasterStandard() {
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
for (Integer type : TYPES) {
Raster raster = new BufferedImage(width, height, type).getRaster();
Raster jreResult = null;
Raster tmResult = null;
try {
jreResult = jreOp.filter(raster, null);
}
catch (ImagingOpException ignore) {
// We expect this to fail for certain cases, that's why we crated the class in the first place
}
try {
tmResult = tmOp.filter(raster, null);
}
catch (ImagingOpException e) {
// Only fail if JRE AffineOp produces a result and our version not
if (jreResult != null) {
fail("No result!");
}
else {
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
System.err.println("type: " + type);
continue;
}
}
if (jreResult != null) {
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
}
else {
assertEquals("Incorrect width", height, tmResult.getWidth());
assertEquals("Incorrect height", width, tmResult.getHeight());
}
}
}
@Test
public void testFilterRotateRasterCustom() {
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
for (ImageTypeSpecifier spec : SPECS) {
Raster raster = spec.createBufferedImage(width, height).getRaster();
Raster jreResult = null;
Raster tmResult = null;
try {
jreResult = jreOp.filter(raster, null);
}
catch (ImagingOpException ignore) {
// We expect this to fail for certain cases, that's why we crated the class in the first place
}
try {
tmResult = tmOp.filter(raster, null);
}
catch (ImagingOpException e) {
// Only fail if JRE AffineOp produces a result and our version not
if (jreResult != null) {
fail("No result!");
}
else {
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
System.err.println("spec: " + spec);
continue;
}
}
if (jreResult != null) {
assertEquals("Incorrect width", jreResult.getWidth(), tmResult.getWidth());
assertEquals("Incorrect height", jreResult.getHeight(), tmResult.getHeight());
}
else {
assertEquals("Incorrect width", height, tmResult.getWidth());
assertEquals("Incorrect height", width, tmResult.getHeight());
}
}
}
}
@@ -103,11 +103,6 @@ public class ImageUtilTestCase {
// Should have same dimensions
assertEquals(scaled.getWidth(null), bufferedScaled.getWidth());
assertEquals(scaled.getHeight(null), bufferedScaled.getHeight());
// Hmmm...
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|| bufferedScaled.getPropertyNames() == null
|| bufferedScaled.getPropertyNames().length == 0);
}
@Test
@@ -69,7 +69,7 @@ public class ResampleOpTestCase {
}
private void assertResampleBufferedImageTypes(final int pFilterType) {
List<String> exceptions = new ArrayList<String>();
List<String> exceptions = new ArrayList<>();
// Test all image types in BufferedImage
for (int type = BufferedImage.TYPE_INT_ARGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) {
@@ -304,6 +304,30 @@ public class ResampleOpTestCase {
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
}
// https://github.com/haraldk/TwelveMonkeys/issues/195
@Test
public void testAIOOBEHeight() {
BufferedImage myImage = new BufferedImage(100, 354, BufferedImage.TYPE_INT_ARGB);
for (int i = 19; i > 0; i--) {
ResampleOp resampler = new ResampleOp(100, i, ResampleOp.FILTER_LANCZOS);
BufferedImage resizedImage = resampler.filter(myImage, null);
assertNotNull(resizedImage);
}
}
// https://github.com/haraldk/TwelveMonkeys/issues/195
@Test
public void testAIOOBEWidth() {
BufferedImage myImage = new BufferedImage(2832, 283, BufferedImage.TYPE_INT_ARGB);
for (int i = 145; i > 143; i--) {
ResampleOp resampler = new ResampleOp(i, 14, ResampleOp.FILTER_LANCZOS);
BufferedImage resizedImage = resampler.filter(myImage, null);
assertNotNull(resizedImage);
}
}
@Ignore("Not for general unit testing")
@Test
public void testTime() {
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -22,7 +22,7 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
@@ -142,7 +142,7 @@ public final class DateUtil {
* @param pTime time
* @return the time rounded to the closest second.
*/
public static long roundToSecond(long pTime) {
public static long roundToSecond(final long pTime) {
return (pTime / SECOND) * SECOND;
}
@@ -152,7 +152,7 @@ public final class DateUtil {
* @param pTime time
* @return the time rounded to the closest minute.
*/
public static long roundToMinute(long pTime) {
public static long roundToMinute(final long pTime) {
return (pTime / MINUTE) * MINUTE;
}
@@ -162,9 +162,20 @@ public final class DateUtil {
* @param pTime time
* @return the time rounded to the closest hour.
*/
public static long roundToHour(long pTime) {
// TODO: What if timezone offset is sub hour? Are there any? I think so...
return ((pTime / HOUR) * HOUR);
public static long roundToHour(final long pTime) {
return roundToHour(pTime, TimeZone.getDefault());
}
/**
* Rounds the given time down to the closest hour, using the given timezone.
*
* @param pTime time
* @param pTimeZone the timezone to use when rounding
* @return the time rounded to the closest hour.
*/
public static long roundToHour(final long pTime, final TimeZone pTimeZone) {
int offset = pTimeZone.getOffset(pTime);
return ((pTime / HOUR) * HOUR) - offset;
}
/**
@@ -173,7 +184,7 @@ public final class DateUtil {
* @param pTime time
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime) {
public static long roundToDay(final long pTime) {
return roundToDay(pTime, TimeZone.getDefault());
}
@@ -184,7 +195,7 @@ public final class DateUtil {
* @param pTimeZone the timezone to use when rounding
* @return the time rounded to the closest day.
*/
public static long roundToDay(long pTime, TimeZone pTimeZone) {
public static long roundToDay(final long pTime, final TimeZone pTimeZone) {
int offset = pTimeZone.getOffset(pTime);
return (((pTime + offset) / DAY) * DAY) - offset;
}
@@ -29,11 +29,15 @@
package com.twelvemonkeys.lang;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.TimeZone;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* DateUtilTest
@@ -42,9 +46,30 @@ import static org.junit.Assert.*;
* @author last modified by $Author: haraldk$
* @version $Id: DateUtilTest.java,v 1.0 11.04.12 16:21 haraldk Exp$
*/
@RunWith(Parameterized.class)
public class DateUtilTest {
private static Calendar getCalendar(long time) {
Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
private final TimeZone timeZone;
@Parameterized.Parameters
public static List<Object[]> timeZones() {
return Arrays.asList(new Object[][] {
{TimeZone.getTimeZone("UTC")},
{TimeZone.getTimeZone("CET")},
{TimeZone.getTimeZone("IST")}, // 30 min off
});
}
public DateUtilTest(final TimeZone timeZone) {
this.timeZone = timeZone;
}
private Calendar getCalendar(long time) {
return getCalendar(time, TimeZone.getDefault());
}
private Calendar getCalendar(long time, final TimeZone timeZone) {
Calendar calendar = Calendar.getInstance(timeZone);
calendar.setTimeInMillis(time);
return calendar;
@@ -74,6 +99,15 @@ public class DateUtilTest {
assertEquals(0, calendar.get(Calendar.MINUTE));
}
@Test
public void testRoundToHourTZ() {
Calendar calendar = getCalendar(DateUtil.roundToHour(System.currentTimeMillis(), timeZone), timeZone);
assertEquals(0, calendar.get(Calendar.MILLISECOND));
assertEquals(0, calendar.get(Calendar.SECOND));
assertEquals(0, calendar.get(Calendar.MINUTE));
}
@Test
public void testRoundToDay() {
Calendar calendar = getCalendar(DateUtil.roundToDay(System.currentTimeMillis()));
@@ -84,6 +118,16 @@ public class DateUtilTest {
assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY));
}
@Test
public void testRoundToDayTZ() {
Calendar calendar = getCalendar(DateUtil.roundToDay(System.currentTimeMillis(), timeZone), timeZone);
assertEquals(0, calendar.get(Calendar.MILLISECOND));
assertEquals(0, calendar.get(Calendar.SECOND));
assertEquals(0, calendar.get(Calendar.MINUTE));
assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY));
}
@Test
public void testCurrentTimeSecond() {
Calendar calendar = getCalendar(DateUtil.currentTimeSecond());
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
@@ -32,7 +32,7 @@
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<type>test-jar</type>
<scope>test</scope>
</dependency>
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
<name>TwelveMonkeys :: Contrib</name>
<description>
Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-image</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,603 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.contrib.tiff;
import com.twelvemonkeys.image.AffineTransformOp;
import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* TIFFUtilities for manipulation TIFF Images and Metadata
*
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
* @author last modified by $Author$
* @version $Id$
*/
public class TIFFUtilities {
private TIFFUtilities() {
}
/**
* Merges all pages from the input TIFF files into one TIFF file at the
* output location.
*
* @param inputFiles
* @param outputFile
* @throws IOException
*/
public static void merge(List<File> inputFiles, File outputFile) throws IOException {
ImageOutputStream output = null;
try {
output = ImageIO.createImageOutputStream(outputFile);
for (File file : inputFiles) {
ImageInputStream input = null;
try {
input = ImageIO.createImageInputStream(file);
List<TIFFPage> pages = getPages(input);
writePages(output, pages);
}
finally {
if (input != null) {
input.close();
}
}
}
}
finally {
if (output != null) {
output.flush();
output.close();
}
}
}
/**
* Splits all pages from the input TIFF file to one file per page in the
* output directory.
*
* @param inputFile
* @param outputDirectory
* @return generated files
* @throws IOException
*/
public static List<File> split(File inputFile, File outputDirectory) throws IOException {
ImageInputStream input = null;
List<File> outputFiles = new ArrayList<>();
try {
input = ImageIO.createImageInputStream(inputFile);
List<TIFFPage> pages = getPages(input);
int pageNo = 1;
for (TIFFPage tiffPage : pages) {
ArrayList<TIFFPage> outputPages = new ArrayList<TIFFPage>(1);
ImageOutputStream outputStream = null;
try {
File outputFile = new File(outputDirectory, String.format("%04d", pageNo) + ".tif");
outputStream = ImageIO.createImageOutputStream(outputFile);
outputPages.clear();
outputPages.add(tiffPage);
writePages(outputStream, outputPages);
outputFiles.add(outputFile);
}
finally {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
}
++pageNo;
}
}
finally {
if (input != null) {
input.close();
}
}
return outputFiles;
}
/**
* Rotates all pages of a TIFF file by changing TIFF.TAG_ORIENTATION.
* <p>
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
* displayed. Other metadata, such as width and height, relate to the image
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
* applying TIFF.TAG_ORIENTATION.
* </p>
*
* @param imageInput
* @param imageOutput
* @param degree Rotation amount, supports 90, 180 and 270.
* @throws IOException
*/
public static void rotatePages(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree)
throws IOException {
rotatePage(imageInput, imageOutput, degree, -1);
}
/**
* Rotates a page of a TIFF file by changing TIFF.TAG_ORIENTATION.
* <p>
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
* displayed. Other metadata, such as width and height, relate to the image
* as how it is stored. The ImageIO TIFF plugin does not handle orientation.
* Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
* applying TIFF.TAG_ORIENTATION.
* </p>
*
* @param imageInput
* @param imageOutput
* @param degree Rotation amount, supports 90, 180 and 270.
* @param pageIndex page which should be rotated or -1 for all pages.
* @throws IOException
*/
public static void rotatePage(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree, int pageIndex)
throws IOException {
ImageInputStream input = null;
try {
List<TIFFPage> pages = getPages(imageInput);
if (pageIndex != -1) {
pages.get(pageIndex).rotate(degree);
}
else {
for (TIFFPage tiffPage : pages) {
tiffPage.rotate(degree);
}
}
writePages(imageOutput, pages);
}
finally {
if (input != null) {
input.close();
}
}
}
public static List<TIFFPage> getPages(ImageInputStream imageInput) throws IOException {
ArrayList<TIFFPage> pages = new ArrayList<TIFFPage>();
CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
int pageCount = IFDs.directoryCount();
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput));
}
return pages;
}
public static void writePages(ImageOutputStream imageOutput, List<TIFFPage> pages) throws IOException {
EXIFWriter exif = new EXIFWriter();
long nextPagePos = imageOutput.getStreamPosition();
if (nextPagePos == 0) {
exif.writeTIFFHeader(imageOutput);
nextPagePos = imageOutput.getStreamPosition();
imageOutput.writeInt(0);
}
else {
// already has pages, so remember place of EOF to replace with
// IFD offset
nextPagePos -= 4;
}
for (TIFFPage tiffPage : pages) {
long ifdOffset = tiffPage.write(imageOutput, exif);
long tmp = imageOutput.getStreamPosition();
imageOutput.seek(nextPagePos);
imageOutput.writeInt((int) ifdOffset);
imageOutput.seek(tmp);
nextPagePos = tmp;
imageOutput.writeInt(0);
}
}
public static BufferedImage applyOrientation(BufferedImage input, int orientation) {
boolean flipExtends = false;
int w = input.getWidth();
int h = input.getHeight();
double cW = w / 2.0;
double cH = h / 2.0;
AffineTransform orientationTransform = new AffineTransform();
switch (orientation) {
case TIFFBaseline.ORIENTATION_TOPLEFT:
// normal
return input;
case TIFFExtension.ORIENTATION_TOPRIGHT:
// flipped vertically
orientationTransform.translate(cW, cH);
orientationTransform.scale(-1, 1);
orientationTransform.translate(-cW, -cH);
break;
case TIFFExtension.ORIENTATION_BOTRIGHT:
// rotated 180
orientationTransform.quadrantRotate(2, cW, cH);
break;
case TIFFExtension.ORIENTATION_BOTLEFT:
// flipped horizontally
orientationTransform.translate(cW, cH);
orientationTransform.scale(1, -1);
orientationTransform.translate(-cW, -cH);
break;
case TIFFExtension.ORIENTATION_LEFTTOP:
orientationTransform.translate(cW, cH);
orientationTransform.scale(-1, 1);
orientationTransform.quadrantRotate(1);
orientationTransform.translate(-cW, -cH);
flipExtends = true;
break;
case TIFFExtension.ORIENTATION_RIGHTTOP:
// rotated 90
orientationTransform.quadrantRotate(1, cW, cH);
flipExtends = true;
break;
case TIFFExtension.ORIENTATION_RIGHTBOT:
orientationTransform.translate(cW, cH);
orientationTransform.scale(1, -1);
orientationTransform.quadrantRotate(1);
orientationTransform.translate(-cW, -cH);
flipExtends = true;
break;
case TIFFExtension.ORIENTATION_LEFTBOT:
// rotated 270
orientationTransform.quadrantRotate(3, cW, cH);
flipExtends = true;
break;
}
int newW, newH;
if (flipExtends) {
newW = h;
newH = w;
}
else {
newW = w;
newH = h;
}
AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
transform.concatenate(orientationTransform);
AffineTransformOp transformOp = new AffineTransformOp(transform, null);
return transformOp.filter(input, null);
}
public static class TIFFPage {
private Directory IFD;
private ImageInputStream stream;
private TIFFPage(Directory IFD, ImageInputStream stream) {
this.IFD = IFD;
this.stream = stream;
}
private long write(ImageOutputStream outputStream, EXIFWriter exifWriter) throws IOException {
List<Entry> newIFD = writeDirectoryData(IFD, outputStream);
return exifWriter.writeIFD(newIFD, outputStream);
}
private List<Entry> writeDirectoryData(Directory IFD, ImageOutputStream outputStream) throws IOException {
ArrayList<Entry> newIFD = new ArrayList<Entry>();
Iterator<Entry> it = IFD.iterator();
while (it.hasNext()) {
Entry e = it.next();
if (e.getValue() instanceof Directory) {
List<Entry> subIFD = writeDirectoryData((Directory) e.getValue(), outputStream);
new TIFFEntry((Integer) e.getIdentifier(), TIFF.TYPE_IFD, new AbstractDirectory(subIFD) {
});
}
newIFD.add(e);
}
long[] offsets = new long[0];
long[] byteCounts = new long[0];
int[] newOffsets = new int[0];
Entry stripOffsetsEntry = IFD.getEntryById(TIFF.TAG_STRIP_OFFSETS);
Entry stripByteCountsEntry = IFD.getEntryById(TIFF.TAG_STRIP_BYTE_COUNTS);
if (stripOffsetsEntry != null && stripByteCountsEntry != null) {
offsets = getValueAsLongArray(stripOffsetsEntry);
byteCounts = getValueAsLongArray(stripByteCountsEntry);
newOffsets = writeData(offsets, byteCounts, outputStream);
newIFD.remove(stripOffsetsEntry);
newIFD.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, newOffsets));
}
Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
if (oldJpegData != null && oldJpegData.valueCount() > 0 && oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) {
if (!Arrays.equals(getValueAsLongArray(oldJpegData), offsets) || !Arrays.equals(getValueAsLongArray(oldJpegDataLength), byteCounts)) {
// data already written from TIFF.TAG_STRIP_OFFSETS
offsets = getValueAsLongArray(oldJpegData);
byteCounts = getValueAsLongArray(oldJpegDataLength);
newOffsets = writeData(offsets, byteCounts, outputStream);
}
newIFD.remove(oldJpegData);
newIFD.add(new TIFFEntry(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, newOffsets));
}
Entry oldJpegTable;
long[] tableOffsets;
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
tableOffsets = getValueAsLongArray(oldJpegTable);
byteCounts = new long[tableOffsets.length];
Arrays.fill(byteCounts, 64);
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
newIFD.remove(oldJpegTable);
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_AC_TABLES, newOffsets));
}
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
tableOffsets = getValueAsLongArray(oldJpegTable);
byteCounts = new long[tableOffsets.length];
Arrays.fill(byteCounts, 64);
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
newIFD.remove(oldJpegTable);
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_Q_TABLES, newOffsets));
}
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
tableOffsets = getValueAsLongArray(oldJpegTable);
byteCounts = new long[tableOffsets.length];
Arrays.fill(byteCounts, 64);
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
newIFD.remove(oldJpegTable);
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_DC_TABLES, newOffsets));
}
return newIFD;
}
private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException {
int[] newOffsets = new int[offsets.length];
for (int i = 0; i < offsets.length; i++) {
newOffsets[i] = (int) outputStream.getStreamPosition();
stream.seek(offsets[i]);
byte[] buffer = new byte[(int) byteCounts[i]];
stream.readFully(buffer);
outputStream.write(buffer);
}
return newOffsets;
}
private long[] getValueAsLongArray(Entry entry) throws IIOException {
//TODO: code duplication from TIFFReader, should be extracted to metadata api
long[] value;
if (entry.valueCount() == 1) {
// For single entries, this will be a boxed type
value = new long[] {((Number) entry.getValue()).longValue()};
}
else if (entry.getValue() instanceof short[]) {
short[] shorts = (short[]) entry.getValue();
value = new long[shorts.length];
for (int i = 0, length = value.length; i < length; i++) {
value[i] = shorts[i];
}
}
else if (entry.getValue() instanceof int[]) {
int[] ints = (int[]) entry.getValue();
value = new long[ints.length];
for (int i = 0, length = value.length; i < length; i++) {
value[i] = ints[i];
}
}
else if (entry.getValue() instanceof long[]) {
value = (long[]) entry.getValue();
}
else {
throw new IIOException(String.format("Unsupported %s type: %s (%s)", entry.getFieldName(), entry.getTypeName(), entry.getValue().getClass()));
}
return value;
}
/**
* Rotates the image by changing TIFF.TAG_ORIENTATION.
* <p>
* NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do
* be displayed. Other metadata, such as width and height, relate to the
* image as how it is stored. The ImageIO TIFF plugin does not handle
* orientation. Use
* {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
* applying TIFF.TAG_ORIENTATION.
* </p>
*
* @param degree Rotation amount, supports 90, 180 and 270.
*/
public void rotate(int degree) {
Validate.isTrue(degree % 90 == 0 && degree > 0 && degree < 360,
"Only rotations by 90, 180 and 270 degree are supported");
ArrayList<Entry> newIDFData = new ArrayList<>();
Iterator<Entry> it = IFD.iterator();
while (it.hasNext()) {
newIDFData.add(it.next());
}
short orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
Entry orientationEntry = IFD.getEntryById(TIFF.TAG_ORIENTATION);
if (orientationEntry != null) {
orientation = ((Number) orientationEntry.getValue()).shortValue();
newIDFData.remove(orientationEntry);
}
int steps = degree / 90;
for (int i = 0; i < steps; i++) {
switch (orientation) {
case TIFFBaseline.ORIENTATION_TOPLEFT:
orientation = TIFFExtension.ORIENTATION_RIGHTTOP;
break;
case TIFFExtension.ORIENTATION_TOPRIGHT:
orientation = TIFFExtension.ORIENTATION_RIGHTBOT;
break;
case TIFFExtension.ORIENTATION_BOTRIGHT:
orientation = TIFFExtension.ORIENTATION_LEFTBOT;
break;
case TIFFExtension.ORIENTATION_BOTLEFT:
orientation = TIFFExtension.ORIENTATION_LEFTTOP;
break;
case TIFFExtension.ORIENTATION_LEFTTOP:
orientation = TIFFExtension.ORIENTATION_TOPRIGHT;
break;
case TIFFExtension.ORIENTATION_RIGHTTOP:
orientation = TIFFExtension.ORIENTATION_BOTRIGHT;
break;
case TIFFExtension.ORIENTATION_RIGHTBOT:
orientation = TIFFExtension.ORIENTATION_BOTLEFT;
break;
case TIFFExtension.ORIENTATION_LEFTBOT:
orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
break;
}
}
newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
IFD = new AbstractDirectory(newIDFData) {
};
}
}
/**
* TODO: Temporary clone, to be removed after TMI204 has been closed
*/
public static final class TIFFEntry extends AbstractEntry {
// TODO: Expose a merge of this and the EXIFEntry class...
private final short type;
private static short guessType(final Object val) {
// TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
Object value = Validate.notNull(val);
boolean array = value.getClass().isArray();
if (array) {
value = Array.get(value, 0);
}
// Note: This "narrowing" is to keep data consistent between read/write.
// TODO: Check for negative values and use signed types?
if (value instanceof Byte) {
return TIFF.TYPE_BYTE;
}
if (value instanceof Short) {
if (!array && (Short) value < Byte.MAX_VALUE) {
return TIFF.TYPE_BYTE;
}
return TIFF.TYPE_SHORT;
}
if (value instanceof Integer) {
if (!array && (Integer) value < Short.MAX_VALUE) {
return TIFF.TYPE_SHORT;
}
return TIFF.TYPE_LONG;
}
if (value instanceof Long) {
if (!array && (Long) value < Integer.MAX_VALUE) {
return TIFF.TYPE_LONG;
}
}
if (value instanceof Rational) {
return TIFF.TYPE_RATIONAL;
}
if (value instanceof String) {
return TIFF.TYPE_ASCII;
}
// TODO: More types
throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
}
public TIFFEntry(final int identifier, final Object value) {
this(identifier, guessType(value), value);
}
TIFFEntry(int identifier, short type, Object value) {
super(identifier, value);
this.type = type;
}
@Override
public String getTypeName() {
return TIFF.TYPE_NAMES[type];
}
}
/**
* TODO: Temporary clone, to be removed after TMI204 has been closed
*/
public interface TIFFExtension {
int ORIENTATION_TOPRIGHT = 2;
int ORIENTATION_BOTRIGHT = 3;
int ORIENTATION_BOTLEFT = 4;
int ORIENTATION_LEFTTOP = 5;
int ORIENTATION_RIGHTTOP = 6;
int ORIENTATION_RIGHTBOT = 7;
int ORIENTATION_LEFTBOT = 8;
}
/**
* TODO: Temporary clone, to be removed after TMI204 has been closed
*/
public interface TIFFBaseline {
int ORIENTATION_TOPLEFT = 1;
}
}
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.contrib.tiff;
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
import com.twelvemonkeys.io.FileUtil;
import org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Node;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.xml.xpath.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
/**
* TIFFUtilitiesTest
*
* @author <a href="mailto:mail@schmidor.de">Oliver Schmidtmer</a>
* @author last modified by $Author$
* @version $Id$
*/
public class TIFFUtilitiesTest {
@Test
public void testMerge() throws IOException {
// Files from ImageIO TIFF Plugin
InputStream stream1 = getClassLoaderResource("/tiff/ccitt/group3_1d.tif").openStream();
InputStream stream2 = getClassLoaderResource("/tiff/ccitt/group3_2d.tif").openStream();
InputStream stream3 = getClassLoaderResource("/tiff/ccitt/group4.tif").openStream();
File file1 = File.createTempFile("imageiotest", ".tif");
File file2 = File.createTempFile("imageiotest", ".tif");
File file3 = File.createTempFile("imageiotest", ".tif");
File output = File.createTempFile("imageiotest", ".tif");
byte[] data;
data = FileUtil.read(stream1);
FileUtil.write(file1, data);
stream1.close();
data = FileUtil.read(stream2);
FileUtil.write(file2, data);
stream2.close();
data = FileUtil.read(stream3);
FileUtil.write(file3, data);
stream3.close();
List<File> input = Arrays.asList(file1, file2, file3);
TIFFUtilities.merge(input, output);
ImageInputStream iis = ImageIO.createImageInputStream(output);
ImageReader reader = ImageIO.getImageReaders(iis).next();
reader.setInput(iis);
Assert.assertEquals(3, reader.getNumImages(true));
iis.close();
output.delete();
file1.delete();
file2.delete();
file3.delete();
}
@Test
public void testSplit() throws IOException {
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
File inputFile = File.createTempFile("imageiotest", "tif");
byte[] data = FileUtil.read(inputStream);
FileUtil.write(inputFile, data);
inputStream.close();
File outputDirectory = Files.createTempDirectory("imageio").toFile();
TIFFUtilities.split(inputFile, outputDirectory);
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
File[] outputFiles = outputDirectory.listFiles();
Assert.assertEquals(3, outputFiles.length);
for (File outputFile : outputFiles) {
ImageInputStream iis = ImageIO.createImageInputStream(outputFile);
reader.setInput(iis);
Assert.assertEquals(1, reader.getNumImages(true));
iis.close();
outputFile.delete();
}
outputDirectory.delete();
inputFile.delete();
}
@Test
public void testRotate() throws IOException, XPathExpressionException {
ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
File inputFile = File.createTempFile("imageiotest", ".tif");
byte[] data = FileUtil.read(inputStream);
FileUtil.write(inputFile, data);
inputStream.close();
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xPath.compile("TIFFIFD/TIFFField[@number='274']/TIFFBytes/TIFFByte/@value");
// rotate all pages
ImageInputStream inputTest1 = ImageIO.createImageInputStream(inputFile);
File outputTest1 = File.createTempFile("imageiotest", ".tif");
ImageOutputStream iosTest1 = ImageIO.createImageOutputStream(outputTest1);
TIFFUtilities.rotatePages(inputTest1, iosTest1, 90);
iosTest1.close();
ImageInputStream checkTest1 = ImageIO.createImageInputStream(outputTest1);
reader.setInput(checkTest1);
for (int i = 0; i < 3; i++) {
Node metaData = reader.getImageMetadata(i)
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
}
checkTest1.close();
// rotate single page further
ImageInputStream inputTest2 = ImageIO.createImageInputStream(outputTest1);
File outputTest2 = File.createTempFile("imageiotest", ".tif");
ImageOutputStream iosTest2 = ImageIO.createImageOutputStream(outputTest2);
TIFFUtilities.rotatePage(inputTest2, iosTest2, 90, 1);
iosTest2.close();
ImageInputStream checkTest2 = ImageIO.createImageInputStream(outputTest2);
reader.setInput(checkTest2);
for (int i = 0; i < 3; i++) {
Node metaData = reader.getImageMetadata(i)
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
Assert.assertEquals(orientation, i == 1
? TIFFExtension.ORIENTATION_BOTRIGHT
: TIFFExtension.ORIENTATION_RIGHTTOP);
}
checkTest2.close();
}
@Test
public void testApplyOrientation() throws IOException {
InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
File inputFile = File.createTempFile("imageiotest", "tif");
byte[] data = FileUtil.read(inputStream);
FileUtil.write(inputFile, data);
inputStream.close();
BufferedImage image = ImageIO.read(inputFile);
// rotate by 90
BufferedImage image90 = TIFFUtilities.applyOrientation(image, TIFFExtension.ORIENTATION_RIGHTTOP);
// rotate by 270
BufferedImage image360 = TIFFUtilities.applyOrientation(image90, TIFFExtension.ORIENTATION_LEFTBOT);
byte[] original = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] rotated = ((DataBufferByte) image360.getData().getDataBuffer()).getData();
Assert.assertArrayEquals(original, rotated);
}
protected URL getClassLoaderResource(final String pName) {
return getClass().getResource(pName);
}
}
+39 -8
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -23,27 +23,54 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>batik</groupId>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-rasterizer-ext</artifactId>
<version>1.6-1</version>
<version>${batik.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extensions</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extension</artifactId>
<version>${batik.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>batik</groupId>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>xmlgraphics-commons</artifactId>
<version>2.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-anim</artifactId>
<version>${batik.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-svggen</artifactId>
<version>1.6-1</version>
<version>${batik.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>batik</groupId>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.6-1</version>
<version>${batik.version}</version>
<scope>provided</scope>
<!--
@@ -59,4 +86,8 @@
</exclusions>
</dependency>
</dependencies>
<properties>
<batik.version>1.8</batik.version>
</properties>
</project>
@@ -31,9 +31,9 @@ package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.bridge.*;
import org.apache.batik.dom.svg.SVGDOMImplementation;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.gvt.CanvasGraphicsNode;
@@ -392,7 +392,7 @@ public class SVGImageReader extends ImageReaderBase {
String ref = new ParsedURL(uri).getRef();
try {
Px = ViewBox.getViewTransform(ref, root, width, height);
Px = ViewBox.getViewTransform(ref, root, width, height, null);
}
catch (BridgeException ex) {
@@ -34,6 +34,7 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Locale;
@@ -51,6 +52,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates an {@code SVGImageReaderSpi}.
*/
@SuppressWarnings("WeakerAccess")
public SVGImageReaderSpi() {
super(new SVGProviderInfo());
}
@@ -59,10 +61,10 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
return pSource instanceof ImageInputStream && SVG_READER_AVAILABLE && canDecode((ImageInputStream) pSource);
}
@SuppressWarnings("StatementWithEmptyBody")
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
// NOTE: This test is quite quick as it does not involve any parsing,
// however it requires the doctype to be "svg", which may not be correct
// in all cases...
// however it may not recognize all kinds of SVG documents.
try {
pInput.mark();
@@ -74,54 +76,80 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
// Skip over leading WS
}
if (!((b == '<') && (pInput.read() == '?') && (pInput.read() == 'x') && (pInput.read() == 'm')
&& (pInput.read() == 'l'))) {
// If it's not a tag, this can't be valid XML
if (b != '<') {
return false;
}
// Okay, we have XML. But, is it really SVG?
boolean docTypeFound = false;
while (!docTypeFound) {
while (pInput.read() != '<') {
// Skip over, until begin tag
}
// Algorithm for detecting SVG:
// - Skip until begin tag '<' and read 4 bytes
// - if next is "?" skip until "?>" and start over
// - else if next is "!--" skip until "-->" and start over
// - else if next is "!DOCTYPE " skip any whitespace
// - compare next 3 bytes against "svg", return result
// - else
// - compare next 3 bytes against "svg", return result
if ((b = pInput.read()) != '!') {
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g'
&& (Character.isWhitespace((char) (b = pInput.read())) || b == ':')) {
byte[] buffer = new byte[4];
while (true) {
pInput.readFully(buffer);
if (buffer[0] == '?') {
// This is the XML declaration or a processing instruction
while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) {
// Skip until end of XML declaration or processing instruction or EOF
}
}
else if (buffer[0] == '!') {
if (buffer[1] == '-' && buffer[2] == '-') {
// This is a comment
while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) {
// Skip until end of comment or EOF
}
}
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
&& pInput.read() == 'T' && pInput.read() == 'Y'
&& pInput.read() == 'P' && pInput.read() == 'E') {
// This is the DOCTYPE declaration
while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over WS
}
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
// It's SVG, identified by DOCTYPE
return true;
}
// DOCTYPE found, but not SVG
return false;
}
// Something else, we'll skip
}
else {
// This is a normal tag
if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g'
&& (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) {
// It's SVG, identified by root tag
// TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)!
return true;
}
// If this is not a comment, or the DOCTYPE declaration, the doc has no DOCTYPE and it can't be svg
// If the tag is not "svg", this isn't SVG
return false;
}
// There might be comments before the doctype, unfortunately...
// If next is "--", this is a comment
if ((b = pInput.read()) == '-' && pInput.read() == '-') {
while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
// Skip until end of comment
}
}
// If we are lucky, this is DOCTYPE declaration
if (b == 'D' && pInput.read() == 'O' && pInput.read() == 'C' && pInput.read() == 'T'
&& pInput.read() == 'Y' && pInput.read() == 'P' && pInput.read() == 'E') {
docTypeFound = true;
while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over WS
}
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
return true;
}
while ((pInput.readByte() & 0xFF) != '<') {
// Skip over, until next begin tag or EOF
}
}
}
catch (EOFException ignore) {
// Possible for small files...
return false;
}
finally {
//noinspection ThrowFromFinallyBlock
pInput.reset();
}
}
@@ -0,0 +1,70 @@
package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* SVGImageReaderSpiTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: SVGImageReaderSpiTest.java,v 1.0 08/08/16 harald.kuhr Exp$
*/
public class SVGImageReaderSpiTest {
private static final String[] VALID_INPUTS = {
"/svg/Android_robot.svg", // Minimal, no xml dec, no namespace
"/svg/batikLogo.svg", // xml dec, comments, namespace
"/svg/blue-square.svg", // xml dec, namespace
"/svg/red-square.svg",
};
private static final String[] INVALID_INPUTS = {
"<xml>",
"<",
"<?",
"<?1",
"<?12",
"<?123", // #275 Infinite loop issue
"<!--",
"<!-- ", // #275 Infinite loop issue
"<?123?>", // #275 Infinite loop issue
"<svg",
};
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
private final ImageReaderSpi provider = new SVGImageReaderSpi();
@Test
public void canDecodeInput() throws Exception {
for (String validInput : VALID_INPUTS) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(validInput))) {
assertTrue("Can't read valid input: " + validInput, provider.canDecodeInput(input));
}
}
}
// Test will time out, if EOFs are not properly detected, see #275
@Test(timeout = 5000)
public void canDecodeInputInvalid() throws Exception {
for (String invalidInput : INVALID_INPUTS) {
try (ImageInputStream input = new ByteArrayImageInputStream(invalidInput.getBytes(StandardCharsets.UTF_8))) {
assertFalse("Claims to read invalid input:" + invalidInput, provider.canDecodeInput(input));
}
}
}
}
@@ -59,7 +59,8 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
return Arrays.asList(
new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)),
new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100))
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400))
);
}
@@ -119,6 +120,13 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
super.testReadWithSourceRegionParamEqualImage();
}
@Test
@Ignore("Known issue: Subsampled reading not supported")
@Override
public void testReadWithSubsampleParamPixels() throws IOException {
super.testReadWithSubsampleParamPixels();
}
@Test
public void testRepeatedRead() throws IOException {
Dimension dim = new Dimension(100, 100);
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* SVGProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: SVGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class SVGProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new SVGProviderInfo();
}
}
@@ -51,8 +51,7 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
protected List<TestData> getTestData() {
return Collections.singletonList(
// TODO: Dimensions does not look right...
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(841, 673))
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(133, 106))
);
}
@@ -87,4 +86,11 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
public void testReadWithSourceRegionParamEqualImage() throws IOException {
super.testReadWithSourceRegionParamEqualImage();
}
@Test
@Ignore("Known issue: Subsampled reading not supported")
@Override
public void testReadWithSubsampleParamPixels() throws IOException {
super.testReadWithSubsampleParamPixels();
}
}
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.wmf;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* WMFProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: WMFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class WMFProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new WMFProviderInfo();
}
}
@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-147 -70 294 345">
<g fill="#a4c639">
<use stroke-width="14.4" xlink:href="#b" stroke="#FFF"/>
<use xlink:href="#a" transform="scale(-1,1)"/>
<g id="a" stroke="#FFF" stroke-width="7.2">
<rect rx="6.5" transform="rotate(29)" height="86" width="13" y="-86" x="14"/>
<rect id="c" rx="24" height="133" width="48" y="41" x="-143"/>
<use y="97" x="85" xlink:href="#c"/>
</g>
<g id="b">
<ellipse cy="41" rx="91" ry="84"/>
<rect rx="22" height="182" width="182" y="20" x="-91"/>
</g>
</g>
<g stroke="#FFF" stroke-width="7.2" fill="#FFF">
<path d="m-95 44.5h190"/><circle cx="-42" r="4"/><circle cx="42" r="4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 706 B

+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
</dependencies>
</project>
@@ -160,13 +160,15 @@ public final class BMPImageReader extends ImageReaderBase {
}
}
// There might be more entries in the color map, but we ignore these for reading
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
if (colors.length > 0) {
// There might be more entries in the color map, but we ignore these for reading
int mapSize = Math.min(colors.length, 1 << header.getBitCount());
// Compute bits for > 8 bits (used only for meta data)
int bits = header.getBitCount() <= 8 ? header.getBitCount() : mapSize <= 256 ? 8 : 16;
// Compute bits for > 8 bits (used only for meta data)
int bits = header.getBitCount() <= 8 ? header.getBitCount() : mapSize <= 256 ? 8 : 16;
colorMap = new IndexColorModel(bits, mapSize, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
colorMap = new IndexColorModel(bits, mapSize, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
}
}
}
@@ -690,12 +692,14 @@ public final class BMPImageReader extends ImageReaderBase {
if (imageMetadata != null) {
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
}
} catch (Throwable t) {
}
catch (Throwable t) {
if (args.length > 1) {
System.err.println("---");
System.err.println("---> " + t.getClass().getSimpleName() + ": " + t.getMessage() + " for " + arg);
System.err.println("---");
} else {
}
else {
throwAs(RuntimeException.class, t);
}
}
@@ -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");
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
protected final DIBHeader header;
protected BufferedImage image;
protected BitmapMask mask;
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
Validate.notNull(pEntry, "entry");
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
protected final int getBitCount() {
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
}
public final void setMask(final BitmapMask mask) {
this.mask = mask;
}
public final boolean hasMask() {
return header.getHeight() == getHeight() * 2;
}
}
@@ -28,8 +28,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
protected final int[] bits;
protected final int[] colors;
private BitmapMask mask;
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(pEntry, pHeader);
bits = new int[getWidth() * getHeight()];
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
// This is slightly obscure, and should probably be moved..
Hashtable<String, Object> properties = null;
if (entry instanceof DirectoryEntry.CUREntry) {
properties = new Hashtable<String, Object>(1);
properties = new Hashtable<>(1);
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
}
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
//System.out.println("Image: " + image);
return image;
}
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
IndexColorModel createColorModel() {
// NOTE: This is a hack to make room for transparent pixel for mask
int bits = getBitCount();
int colors = this.colors.length;
int trans = -1;
int transparent = -1;
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
if (colors > (1 << getBitCount())) {
int index = findTransIndexMaybeRemap(this.colors, this.bits);
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
if (index == -1) {
// No duplicate found, increase bitcount
bits++;
trans = this.colors.length - 1;
transparent = this.colors.length - 1;
}
else {
// Found a duplicate, use it as trans
trans = index;
// Found a duplicate, use it as transparent
transparent = index;
colors--;
}
}
// NOTE: Setting hasAlpha to true, makes things work on 1.2
return new InverseColorMapIndexColorModel(
bits, colors, this.colors, 0, true, trans,
return new IndexColorModel(
bits, colors, this.colors, 0, true, transparent,
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
);
}
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
// Look for unused colors, to use as transparent
final boolean[] used = new boolean[pColors.length - 1];
for (int pBit : pBits) {
if (!used[pBit]) {
used[pBit] = true;
boolean[] used = new boolean[colors.length - 1];
for (int bit : bits) {
if (!used[bit]) {
used[bit] = true;
}
}
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
}
// Try to find duplicates in colormap, and remap
int trans = -1;
int transparent = -1;
int duplicate = -1;
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
for (int j = i + 1; j < pColors.length - 1; j++) {
if (pColors[i] == pColors[j]) {
trans = j;
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
for (int j = i + 1; j < colors.length - 1; j++) {
if (colors[i] == colors[j]) {
transparent = j;
duplicate = i;
break;
}
}
}
if (trans != -1) {
if (transparent != -1) {
// Remap duplicate
for (int i = 0; i < pBits.length; i++) {
if (pBits[i] == trans) {
pBits[i] = duplicate;
for (int i = 0; i < bits.length; i++) {
if (bits[i] == transparent) {
bits[i] = duplicate;
}
}
}
return trans;
return transparent;
}
public BufferedImage getImage() {
if (image == null) {
image = createImageIndexed();
}
return image;
}
public void setMask(final BitmapMask pMask) {
mask = pMask;
}
}
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/
class BitmapMask extends BitmapDescriptor {
protected final BitmapIndexed mask;
protected final BitmapIndexed bitMask;
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
super(pParent, pHeader);
mask = new BitmapIndexed(pParent, pHeader);
bitMask = new BitmapIndexed(pParent, pHeader);
}
boolean isTransparent(final int pX, final int pY) {
// NOTE: 1: Fully transparent, 0: Opaque...
return mask.bits[pX + pY * getWidth()] != 0;
return bitMask.bits[pX + pY * getWidth()] != 0;
}
public BufferedImage getImage() {
return mask.getImage();
return bitMask.getImage();
}
}
@@ -28,7 +28,9 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/**
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
}
public BufferedImage getImage() {
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
// might still have bitmask, but we don't read or use it.
if (mask != null) {
image = createMaskedImage();
mask = null;
}
return image;
}
private BufferedImage createMaskedImage() {
BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D graphics = masked.createGraphics();
try {
graphics.drawImage(image, 0, 0, null);
}
finally {
graphics.dispose();
}
WritableRaster alphaRaster = masked.getAlphaRaster();
byte[] trans = {0x0};
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
if (mask.isTransparent(x, y)) {
alphaRaster.setDataElements(x, y, trans);
}
}
}
return masked;
}
}
@@ -66,14 +66,15 @@ import java.util.List;
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
// TODO: Support loading icons from DLLs, see
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry
// (seem impossible as the PNGs are all true color)
abstract class DIBImageReader extends ImageReaderBase {
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
private Directory directory;
// TODO: Review these, make sure we don't have a memory leak
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
private ImageReader pngImageReader;
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
return getImageTypesPNG(entry);
}
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
List<ImageTypeSpecifier> types = new ArrayList<>();
DIBHeader header = getHeader(entry);
// Use data from header to create specifier
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
break;
case 16:
// TODO: May have mask?!
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
break;
case 24:
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
specifier = new BitmapRGB(entry, header).hasMask()
? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
break;
case 32:
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
else {
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
@@ -335,7 +340,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
readBitmapIndexed1(mask.mask, true);
readBitmapIndexed1(mask.bitMask, true);
pBitmap.setMask(mask);
}
@@ -370,7 +375,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
}
// NOTE: If we are reading the mask, we don't abort or progress
// NOTE: If we are reading the mask, we don't abort or report progress
if (!pAsMask) {
if (abortRequested()) {
processReadAborted();
@@ -455,7 +460,7 @@ abstract class DIBImageReader extends ImageReaderBase {
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555;
// Will create TYPE_USHORT_555
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster(
@@ -480,6 +485,8 @@ abstract class DIBImageReader extends ImageReaderBase {
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
// TODO: Might be mask!?
}
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
@@ -494,16 +501,19 @@ abstract class DIBImageReader extends ImageReaderBase {
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
);
int scanlineStride = pBitmap.getWidth() * 3;
// BMP rows are padded to 4 byte boundary
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
WritableRaster raster = Raster.createInterleavedRaster(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
);
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
// TODO: Possibly read padding byte here!
for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
imageInput.readFully(pixels, offset, scanlineStride);
imageInput.skipBytes(rowSizeBytes - scanlineStride);
if (abortRequested()) {
processReadAborted();
@@ -512,6 +522,14 @@ abstract class DIBImageReader extends ImageReaderBase {
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
// 24 bit icons usually have a bit mask
if (pBitmap.hasMask()) {
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
readBitmapIndexed1(mask.bitMask, true);
pBitmap.setMask(mask);
}
}
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
@@ -535,6 +553,9 @@ abstract class DIBImageReader extends ImageReaderBase {
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
// There might be a mask here as well, but we'll ignore it,
// and use the 8 bit alpha channel in the ARGB pixel data
}
private Directory getDirectory() throws IOException {
@@ -1,20 +1,19 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.InOrder;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.*;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
@@ -108,7 +107,8 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
new TestData(getClassLoaderResource("/bmp/blauesglas_24.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331))
new TestData(getClassLoaderResource("/bmp/blauesglas_32_bitmask888_reversed.bmp"), new Dimension(301, 331)),
new TestData(getClassLoaderResource("/bmp/24bitpalette.bmp"), new Dimension(320, 200))
);
}
@@ -173,6 +173,58 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
}
}
@Ignore("Known issue: Subsampled reading is currently broken")
@Test
public void testReadWithSubsampleParamPixelsIndexed8() throws IOException {
ImageReader reader = createReader();
TestData data = getTestData().get(0);
reader.setInput(data.getInputStream());
ImageReadParam param = reader.getDefaultReadParam();
BufferedImage image = null;
BufferedImage subsampled = null;
try {
image = reader.read(0, param);
param.setSourceSubsampling(2, 2, 0, 0);
subsampled = reader.read(0, param);
}
catch (IOException e) {
failBecause("Image could not be read", e);
}
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
}
// TODO: 1. Subsampling is currently broken, should fix it.
// 2. BMPs are (normally) stored bottom/up, meaning y subsampling offsets will differ from normal
// subsampling of the same data with an offset... Should we deal with this in the reader? Yes?
@Ignore("Known issue: Subsampled reading is currently broken")
@Test
@Override
public void testReadWithSubsampleParamPixels() throws IOException {
ImageReader reader = createReader();
TestData data = getTestData().get(19); // RGB 24
reader.setInput(data.getInputStream());
ImageReadParam param = reader.getDefaultReadParam();
BufferedImage image = null;
BufferedImage subsampled = null;
try {
image = reader.read(0, param);
param.setSourceSubsampling(2, 2, 0, 0);
subsampled = reader.read(0, param);
}
catch (IOException e) {
failBecause("Image could not be read", e);
}
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
}
@Test(expected = IIOException.class)
public void testReadCorruptCausesIIOException() throws IOException {
// See https://bugs.openjdk.java.net/browse/JDK-8066904
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* BMPProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: BMPProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class BMPProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new BMPProviderInfo();
}
}
@@ -119,4 +119,11 @@ public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader>
public void testNotBadCaching() throws IOException {
super.testNotBadCaching();
}
@Test
@Ignore("Known issue: Subsampled reading currently not supported")
@Override
public void testReadWithSubsampleParamPixels() throws IOException {
super.testReadWithSubsampleParamPixels();
}
}
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* CURProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: CURProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class CURProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new CURProviderInfo();
}
}
@@ -39,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
)
),
// Problematic icon that reports 24 bit in the descriptor, but has separate 1 bit ''mask (height 2 x icon height)!
new TestData(getClassLoaderResource("/ico/rgb24bitmask.ico"), new Dimension(32, 32))
);
}
@@ -74,4 +76,11 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
public void testNotBadCaching() throws IOException {
super.testNotBadCaching();
}
@Test
@Ignore("Known issue: Subsampled reading currently not supported")
@Override
public void testReadWithSubsampleParamPixels() throws IOException {
super.testReadWithSubsampleParamPixels();
}
}
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* ICOProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: ICOProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class ICOProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new ICOProviderInfo();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
@@ -20,7 +20,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -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))
);
}
@@ -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);
}
}
@@ -52,7 +52,7 @@ import java.util.Properties;
* <p />
* Color profiles may be configured by placing a property-file
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
* on the classpath, specifying the full path to the profile.
* on the classpath, specifying the full path to the profiles.
* ICC color profiles are probably already present on your system, or
* can be downloaded from
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
@@ -100,7 +100,8 @@ public final class ColorSpaces {
*
* @param profile the ICC color profile. May not be {@code null}.
* @return an ICC color space
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws IllegalArgumentException if {@code profile} is {@code null}.
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
@@ -161,6 +162,11 @@ public final class ColorSpaces {
if (cs == null) {
cs = new ICC_ColorSpace(profile);
// Validate the color space, to avoid caching bad color spaces
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {1f, 0f, 0f});
cache.put(key, cs);
}
@@ -195,7 +201,7 @@ public final class ColorSpaces {
* @return {@code true} if known to be offending, {@code false} otherwise
* @throws IllegalArgumentException if {@code profile} is {@code null}
*/
public static boolean isOffendingColorProfile(final ICC_Profile profile) {
static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
@@ -213,6 +219,26 @@ public final class ColorSpaces {
return data[ICC_Profile.icHdrRenderingIntent] != 0;
}
/**
* Tests whether an ICC color profile is valid.
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
* <p />
* <em>
* Note that this method only tests if a color conversion using this profile is known to fail.
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
* </em>
*
* @param profile the ICC color profile. May not be {@code null}.
* @return {@code profile} if valid.
* @throws IllegalArgumentException if {@code profile} is {@code null}
* @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_Profile validateProfile(final ICC_Profile profile) {
createColorSpace(profile); // Creating a color space will fail if the profile is bad
return profile;
}
/**
* Returns the color space specified by the given color space constant.
* <p />
@@ -317,7 +343,7 @@ public final class ColorSpaces {
try {
return ICC_Profile.getInstance(profilePath);
}
catch (IOException ignore) {
catch (SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
@@ -0,0 +1,84 @@
/*
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.color;
import java.awt.color.ColorSpace;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
/**
* ComponentColorModel subclass that correctly handles full 16 bit {@code TYPE_SHORT} signed integral samples.
**
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: UInt32ColorModel.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/
public final class Int16ComponentColorModel extends ComponentColorModel {
private final ComponentColorModel delegate;
public Int16ComponentColorModel(final ColorSpace cs, final boolean hasAlpha, boolean isAlphaPremultiplied) {
super(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_SHORT);
delegate = new ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_USHORT);
}
private void remap(final short[] s, final int i) {
// MIN ... -1 -> 0 ... MAX
// 0 ... MAX -> MIN ... -1
short sample = s[i];
if (sample < 0) {
s[i] = (short) (sample - Short.MIN_VALUE);
}
else {
s[i] = (short) (sample + Short.MIN_VALUE);
}
}
@Override
public int getRed(final Object inData) {
remap((short[]) inData, 0);
return delegate.getRed(inData);
}
@Override
public int getGreen(final Object inData) {
remap((short[]) inData, 1);
return delegate.getGreen(inData);
}
@Override
public int getBlue(final Object inData) {
remap((short[]) inData, 2);
return delegate.getBlue(inData);
}
}
@@ -0,0 +1,94 @@
package com.twelvemonkeys.imageio.color;
/**
* Fast YCbCr to RGB conversion.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author Original code by Werner Randelshofer (used by permission).
*/
public final class YCbCrConverter {
/**
* Define tables for YCC->RGB color space conversion.
*/
private final static int SCALEBITS = 16;
private final static int MAXJSAMPLE = 255;
private final static int CENTERJSAMPLE = 128;
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
/**
* Initializes tables for YCC->RGB color space conversion.
*/
private static void buildYCCtoRGBtable() {
if (ColorSpaces.DEBUG) {
System.err.println("Building YCC conversion table");
}
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
// Cr=>R value is nearest int to 1.40200 * x
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cb=>B value is nearest int to 1.77200 * x
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
// Cr=>G value is scaled-up -0.71414 * x
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
// Cb=>G value is scaled-up -0.34414 * x
// We also add in ONE_HALF so that need not do it in inner loop
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
}
}
static {
buildYCCtoRGBtable();
}
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
double y;
double cb;
double cr;
if (referenceBW == null) {
// Default case
y = (yCbCr[offset] & 0xff);
cb = (yCbCr[offset + 1] & 0xff) - 128;
cr = (yCbCr[offset + 2] & 0xff) - 128;
}
else {
// Custom values
y = ((yCbCr[offset] & 0xff) - referenceBW[0]) * 255.0 / (referenceBW[1] - referenceBW[0]);
cb = ((yCbCr[offset + 1] & 0xff) - referenceBW[2]) * 127.0 / (referenceBW[3] - referenceBW[2]);
cr = ((yCbCr[offset + 2] & 0xff) - referenceBW[4]) * 127.0 / (referenceBW[5] - referenceBW[4]);
}
double lumaRed = coefficients[0];
double lumaGreen = coefficients[1];
double lumaBlue = coefficients[2];
int red = (int) Math.round(cr * (2.0 - 2.0 * lumaRed) + y);
int blue = (int) Math.round(cb * (2.0 - 2.0 * lumaBlue) + y);
int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
rgb[offset] = clamp(red);
rgb[offset + 2] = clamp(blue);
rgb[offset + 1] = clamp(green);
}
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
int y = yCbCr[offset] & 0xff;
int cr = yCbCr[offset + 2] & 0xff;
int cb = yCbCr[offset + 1] & 0xff;
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
}
private static byte clamp(int val) {
return (byte) Math.max(0, Math.min(255, val));
}
}
@@ -52,7 +52,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
private final String[] writerSpiClassNames;
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
private final boolean supportsStandardStreamMetadata;
private final String nativeStreameMetadataFormatName;
private final String nativeStreamMetadataFormatName;
private final String nativeStreamMetadataFormatClassName;
private final String[] extraStreamMetadataFormatNames;
private final String[] extraStreamMetadataFormatClassNames;
@@ -97,7 +97,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
this.writerClassName = writerClassName;
this.writerSpiClassNames = writerSpiClassNames;
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
this.nativeStreameMetadataFormatName = nativeStreameMetadataFormatName;
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
@@ -149,7 +149,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
}
public String nativeStreamMetadataFormatName() {
return nativeStreameMetadataFormatName;
return nativeStreamMetadataFormatName;
}
public String nativeStreamMetadataFormatClassName() {
@@ -5,7 +5,8 @@ import javax.imageio.stream.ImageOutputStreamImpl;
import java.io.IOException;
/**
* SubImageOutputStream.
* ImageInputStream that writes through a delegate, but keeps local position and bit offset.
* Note: Flushing or closing this stream will *not* have an effect on the delegate.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
@@ -28,14 +28,19 @@
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* Factory class for creating {@code ImageTypeSpecifier}s.
* In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
* in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
*
* @see javax.imageio.ImageTypeSpecifier
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@@ -54,6 +59,20 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
@@ -79,7 +98,11 @@ public final class ImageTypeSpecifiers {
}
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) {
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
}
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
}
@@ -89,7 +112,11 @@ public final class ImageTypeSpecifiers {
}
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) {
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
}
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
}
@@ -98,6 +125,34 @@ public final class ImageTypeSpecifiers {
return ImageTypeSpecifier.createGrayscale(bits, dataType, false, isAlphaPremultiplied);
}
public static ImageTypeSpecifier createPackedGrayscale(final ColorSpace colorSpace, final int bits, final int dataType) {
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_GRAY, colorSpace, "ColorSpace must be TYPE_GRAY");
isTrue(bits == 1 || bits == 2 || bits == 4, bits, "bits must be 1, 2, or 4: %s");
isTrue(dataType == DataBuffer.TYPE_BYTE, dataType, "dataType must be TYPE_BYTE: %s");
int numEntries = 1 << bits;
byte[] arr = new byte[numEntries];
byte[] arg = new byte[numEntries];
byte[] arb = new byte[numEntries];
// Scale array values according to color profile..
for (int i = 0; i < numEntries; i++) {
float[] gray = new float[]{i / (float) (numEntries - 1)};
float[] rgb = colorSpace.toRGB(gray);
arr[i] = (byte) (rgb[0] * 255);
arg[i] = (byte) (rgb[1] * 255);
arb[i] = (byte) (rgb[2]* 255);
}
ColorModel colorModel = new IndexColorModel(bits, numEntries, arr, arg, arb);
SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
return new ImageTypeSpecifier(colorModel, sampleModel);
}
public static ImageTypeSpecifier createIndexed(final byte[] redLUT, final byte[] greenLUT,
final byte[] blueLUT, final byte[] alphaLUT,
final int bits, final int dataType) {
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.imageio.color.Int16ComponentColorModel;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
import java.awt.image.DataBuffer;
import java.awt.image.PixelInterleavedSampleModel;
/**
* ImageTypeSpecifier for interleaved 16 bit signed integral samples.
*
* @see com.twelvemonkeys.imageio.color.Int16ColorModel
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$
*/
final class Int16ImageTypeSpecifier extends ImageTypeSpecifier {
Int16ImageTypeSpecifier(final ColorSpace cs, int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
super(
new Int16ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied),
new PixelInterleavedSampleModel(
DataBuffer.TYPE_SHORT, 1, 1,
cs.getNumComponents() + (hasAlpha ? 1 : 0),
cs.getNumComponents() + (hasAlpha ? 1 : 0),
bandOffsets
)
);
}
}
@@ -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);
}
}
@@ -0,0 +1,160 @@
package com.twelvemonkeys.imageio.spi;
import org.hamcrest.Description;
import org.junit.Test;
import org.junit.internal.matchers.TypeSafeMatcher;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.Assert.*;
/**
* ReaderWriterProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: ReaderWriterProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public abstract class ReaderWriterProviderInfoTest {
private final ReaderWriterProviderInfo providerInfo = createProviderInfo();
protected abstract ReaderWriterProviderInfo createProviderInfo();
protected final ReaderWriterProviderInfo getProviderInfo() {
return providerInfo;
}
@Test
public void readerClassName() throws Exception {
assertClassExists(providerInfo.readerClassName(), ImageReader.class);
}
@Test
public void readerSpiClassNames() throws Exception {
assertClassesExist(providerInfo.readerSpiClassNames(), ImageReaderSpi.class);
}
@Test
public void inputTypes() throws Exception {
assertNotNull(providerInfo.inputTypes());
}
@Test
public void writerClassName() throws Exception {
assertClassExists(providerInfo.writerClassName(), ImageWriter.class);
}
@Test
public void writerSpiClassNames() throws Exception {
assertClassesExist(providerInfo.writerSpiClassNames(), ImageWriterSpi.class);
}
@Test
public void outputTypes() throws Exception {
assertNotNull(providerInfo.outputTypes());
}
@Test
public void nativeStreamMetadataFormatClassName() throws Exception {
assertClassExists(providerInfo.nativeStreamMetadataFormatClassName(), IIOMetadataFormat.class);
}
@Test
public void extraStreamMetadataFormatClassNames() throws Exception {
assertClassesExist(providerInfo.extraStreamMetadataFormatClassNames(), IIOMetadataFormat.class);
}
@Test
public void nativeImageMetadataFormatClassName() throws Exception {
assertClassExists(providerInfo.nativeImageMetadataFormatClassName(), IIOMetadataFormat.class);
}
@Test
public void extraImageMetadataFormatClassNames() throws Exception {
assertClassesExist(providerInfo.extraImageMetadataFormatClassNames(), IIOMetadataFormat.class);
}
@Test
public void formatNames() {
String[] names = providerInfo.formatNames();
assertNotNull(names);
assertFalse(names.length == 0);
List<String> list = asList(names);
for (String name : list) {
assertNotNull(name);
assertFalse(name.isEmpty());
assertTrue(list.contains(name.toLowerCase()));
assertTrue(list.contains(name.toUpperCase()));
}
}
@Test
public void suffixes() {
String[] suffixes = providerInfo.suffixes();
assertNotNull(suffixes);
assertFalse(suffixes.length == 0);
for (String suffix : suffixes) {
assertNotNull(suffix);
assertFalse(suffix.isEmpty());
}
}
@Test
public void mimeTypes() {
String[] mimeTypes = providerInfo.mimeTypes();
assertNotNull(mimeTypes);
assertFalse(mimeTypes.length == 0);
for (String mimeType : mimeTypes) {
assertNotNull(mimeType);
assertFalse(mimeType.isEmpty());
assertTrue(mimeType.length() > 1);
assertTrue(mimeType.indexOf('/') > 0);
assertTrue(mimeType.indexOf('/') < mimeType.length() - 1);
}
}
public static <T> void assertClassExists(final String className, final Class<T> type) {
if (className != null) {
try {
final Class<?> cl = Class.forName(className);
assertThat(cl, new TypeSafeMatcher<Class<?>>() {
@Override
public boolean matchesSafely(Class<?> item) {
return type.isAssignableFrom(cl);
}
@Override
public void describeTo(Description description) {
description.appendText("is subclass of ").appendValue(type);
}
});
}
catch (ClassNotFoundException e) {
e.printStackTrace();
fail("Class not found: " + e.getMessage());
}
}
}
public static <T> void assertClassesExist(final String[] classNames, final Class<T> type) {
if (classNames != null) {
for (String className : classNames) {
assertClassExists(className, type);
}
}
}
}
@@ -28,7 +28,6 @@
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.junit.Ignore;
import org.junit.Test;
@@ -46,6 +45,7 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
@@ -98,7 +98,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
return false;
}
private static void failBecause(String message, Throwable exception) {
protected static void failBecause(String message, Throwable exception) {
AssertionError error = new AssertionError(message);
error.initCause(exception);
throw error;
@@ -471,7 +471,6 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
assertEquals("Read image has wrong height: ", (data.getDimension(0).height + 4) / 5, image.getHeight());
}
@Ignore
@Test
public void testReadWithSubsampleParamPixels() throws IOException {
ImageReader reader = createReader();
@@ -479,28 +478,66 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
reader.setInput(data.getInputStream());
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(Math.min(100, reader.getWidth(0)), Math.min(100, reader.getHeight(0))));
BufferedImage image = null;
BufferedImage subsampled = null;
try {
image = reader.read(0, param);
param.setSourceSubsampling(2, 2, 1, 1); // Hmm.. Seems to be the offset the fake version (ReplicateScaleFilter) uses
param.setSourceSubsampling(2, 2, 0, 0);
subsampled = reader.read(0, param);
}
catch (IOException e) {
failBecause("Image could not be read", e);
}
BufferedImage expected = ImageUtil.toBuffered(IIOUtil.fakeSubsampling(image, param));
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
}
// JPanel panel = new JPanel();
// panel.add(new JLabel("Expected", new BufferedImageIcon(expected, 300, 300), JLabel.CENTER));
// panel.add(new JLabel("Actual", new BufferedImageIcon(subsampled, 300, 300), JLabel.CENTER));
// JOptionPane.showConfirmDialog(null, panel);
// TODO: Subsample all test data
// TODO: Subsample with varying ratios and offsets
assertImageDataEquals("Subsampled image data does not match expected", expected, subsampled);
protected final void assertSubsampledImageDataEquals(String message, BufferedImage expected, BufferedImage actual, ImageReadParam param) throws IOException {
assertNotNull("Expected image was null", expected);
assertNotNull("Actual image was null!", actual);
if (expected == actual) {
return;
}
int xOff = param.getSubsamplingXOffset();
int yOff = param.getSubsamplingYOffset();
int xSub = param.getSourceXSubsampling();
int ySub = param.getSourceYSubsampling();
assertEquals("Subsampled image has wrong width: ", (expected.getWidth() - xOff + xSub - 1) / xSub, actual.getWidth());
assertEquals("Subsampled image has wrong height: ", (expected.getHeight() - yOff + ySub - 1) / ySub, actual.getHeight());
assertEquals("Subsampled has different type", expected.getType(), actual.getType());
for (int y = 0; y < actual.getHeight(); y++) {
for (int x = 0; x < actual.getWidth(); x++) {
int expectedRGB = expected.getRGB(xOff + x * xSub, yOff + y * ySub);
int actualRGB = actual.getRGB(x, y);
try {
assertEquals(String.format("%s alpha at (%d, %d)", message, x, y), (expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 5);
assertEquals(String.format("%s red at (%d, %d)", message, x, y), (expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5);
assertEquals(String.format("%s green at (%d, %d)", message, x, y), (expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5);
assertEquals(String.format("%s blue at (%d, %d)", message, x, y), expectedRGB & 0xff, actualRGB & 0xff, 5);
}
catch (AssertionError e) {
File tempExpected = File.createTempFile("junit-expected-", ".png");
System.err.println("tempExpected.getAbsolutePath(): " + tempExpected.getAbsolutePath());
ImageIO.write(expected, "PNG", tempExpected);
File tempActual = File.createTempFile("junit-actual-", ".png");
System.err.println("tempActual.getAbsolutePath(): " + tempActual.getAbsolutePath());
ImageIO.write(actual, "PNG", tempActual);
assertEquals(String.format("%s ARGB at (%d, %d)", message, x, y), String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB));
}
}
}
}
protected final void assertImageDataEquals(String message, BufferedImage expected, BufferedImage actual) {
@@ -1602,6 +1639,21 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
return getClass().getResource(pName);
}
/**
* Slightly fuzzy RGB equals method. Variable tolerance.
*/
public static void assertRGBEquals(String message, int expectedRGB, int actualRGB, int tolerance) {
try {
assertEquals((expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, tolerance);
assertEquals((expectedRGB ) & 0xff, (actualRGB ) & 0xff, tolerance);
}
catch (AssertionError e) {
assertEquals(message, String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB));
}
}
static final protected class TestData {
private final Object input;
private final List<Dimension> sizes;
@@ -1,12 +1,11 @@
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.lang.Validate;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.*;
import static org.junit.Assert.assertEquals;
@@ -40,7 +39,7 @@ public class ImageTypeSpecifiersTest {
}
@Test
public void testCreatePacked() {
public void testCreatePacked32() {
// TYPE_INT_RGB
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
@@ -61,31 +60,70 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
);
}
@Test
public void testCreatePacked16() {
// TYPE_USHORT_555_RGB
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "SHORT 555 RGB" (impossible for some reason)
// assertEquals(
// ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false),
// ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false)
// );
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
// TYPE_USHORT_565_RGB
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB"
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB PRE"
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
);
// Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32)
assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
}
@Test
public void testCreatePacked8() {
// "BYTE 332 RGB"
assertEquals(
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
);
// "BYTE 2222 ARGB"
assertEquals(
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
);
// "BYTE 2222 ARGB PRE"
assertEquals(
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
);
// Extra: Make sure color models bits is actually 8 (ImageTypeSpecifiers equivalent returns 32)
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
}
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
final int redMask, final int greenMask, final int blueMask, final int alphaMask,
final int transferType, final boolean isAlphaPremultiplied) {
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel =
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
@Test
@@ -337,11 +375,7 @@ public class ImageTypeSpecifiersTest {
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false), // NOTE: Unsigned TYPE_SHORT makes no sense...
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true),
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
);
}
@@ -400,19 +434,11 @@ public class ImageTypeSpecifiersTest {
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false),
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, true),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, false),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
);
assertEquals(
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, true),
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
);
}
@@ -437,6 +463,30 @@ public class ImageTypeSpecifiersTest {
);
}
@Test
public void testCreatePackedGrayscale1() {
assertEquals(
ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 1, DataBuffer.TYPE_BYTE)
);
}
@Test
public void testCreatePackedGrayscale2() {
assertEquals(
ImageTypeSpecifier.createGrayscale(2, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 2, DataBuffer.TYPE_BYTE)
);
}
@Test
public void testCreatePackedGrayscale4() {
assertEquals(
ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 4, DataBuffer.TYPE_BYTE)
);
}
@Test
public void testCreateIndexedByteArrays1to8() {
for (int bits = 1; bits <= 8; bits <<= 1) {
@@ -562,7 +612,6 @@ public class ImageTypeSpecifiersTest {
}
private static byte[] createByteLut(final int count) {
byte[] lut = new byte[count];
for (int i = 0; i < count; i++) {
@@ -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();
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
<description>
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
/**
* HDR.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
*/
interface HDR {
byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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());
}
}
@@ -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";
}
}
@@ -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
}
@@ -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();
}
}
}
}
@@ -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);
}
}
}
@@ -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)));
}
}
}
@@ -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) {
}
}
@@ -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);
}
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
@@ -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"
);
}
}
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* HDRProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: HDRProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class HDRProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new HDRProviderInfo();
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
</dependencies>
</project>
@@ -47,7 +47,7 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo {
"image/x-apple-icons", // Common extension MIME
},
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.ics.ICNImageReaderSpi"},
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
null, null,
false, null, null, null, null,
true, null, null, null, null
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.icns;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* ICNSProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: ICNSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class ICNSProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new ICNSProviderInfo();
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* IFFProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: IFFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class IFFProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new IFFProviderInfo();
}
}
+3 -1
View File
@@ -1,4 +1,6 @@
Copyright (c) 2013, Harald Kuhr
Copyright (c) 2016, Harald Kuhr
Copyright (C) 2015, Michael Martinez (JPEG Lossless decoder)
Copyright (C) 2004, Helmut Dersch (Java JPEG decoder)
All rights reserved.
Redistribution and use in source and binary forms, with or without
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1.0</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -20,7 +20,7 @@
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
@@ -28,6 +28,11 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import java.io.DataInput;
import java.io.IOException;
/**
* AdobeDCTSegment
*
@@ -35,44 +40,44 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
* @author last modified by $Author: haraldk$
* @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$
*/
class AdobeDCTSegment {
public static final int Unknown = 0;
public static final int YCC = 1;
public static final int YCCK = 2;
final class AdobeDCT extends Application {
static final int Unknown = 0;
static final int YCC = 1;
static final int YCCK = 2;
final int version;
final int flags0;
final int flags1;
final int transform;
AdobeDCTSegment(int version, int flags0, int flags1, int transform) {
private AdobeDCT(int version, int flags0, int flags1, int transform) {
super(JPEG.APP14, "Adobe", new byte[]{'A', 'd', 'o', 'b', 'e', 0, (byte) version, (byte) (flags0 >> 8), (byte) (flags0 & 0xff), (byte) (flags1 >> 8), (byte) (flags1 & 0xff), (byte) transform});
this.version = version; // 100 or 101
this.flags0 = flags0;
this.flags1 = flags1;
this.transform = transform;
}
public int getVersion() {
return version;
}
public int getFlags0() {
return flags0;
}
public int getFlags1() {
return flags1;
}
public int getTransform() {
return transform;
}
@Override
public String toString() {
return String.format(
"AdobeDCT[ver: %d.%02d, flags: %s %s, transform: %d]",
getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform()
version / 100, version % 100, Integer.toBinaryString(flags0), Integer.toBinaryString(flags1), transform
);
}
public static AdobeDCT read(final DataInput data, final int length) throws IOException {
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
data.skipBytes(6); // A, d, o, b, e, \0
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
return new AdobeDCT(
data.readUnsignedByte(),
data.readUnsignedShort(),
data.readUnsignedShort(),
data.readUnsignedByte()
);
}
}
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2016, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
/**
* Application.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: Application.java,v 1.0 22/08/16 harald.kuhr Exp$
*/
class Application extends Segment {
final String identifier;
final byte[] data;
Application(final int marker, final String identifier, final byte[] data) {
super(marker);
this.identifier = identifier; // NOTE: Some JPEGs contain APP segments without NULL-terminated identifier
this.data = data;
}
InputStream data() {
int offset = identifier.length() + 1;
return new ByteArrayInputStream(data, offset, data.length - offset);
}
@Override
public String toString() {
return "APP" + (marker & 0x0f) + "/" + identifier + "[length: " + data.length + "]";
}
public static Application read(final int marker, final String identifier, final DataInput data, final int length) throws IOException {
switch (marker) {
case JPEG.APP0:
// JFIF
if ("JFIF".equals(identifier)) {
return JFIF.read(data, length);
}
case JPEG.APP1:
// JFXX
if ("JFXX".equals(identifier)) {
return JFXX.read(data, length);
}
// TODO: Exif?
case JPEG.APP2:
// ICC_PROFILE
if ("ICC_PROFILE".equals(identifier)) {
return ICCProfile.read(data, length);
}
case JPEG.APP14:
// Adobe
if ("Adobe".equals(identifier)) {
return AdobeDCT.read(data, length);
}
default:
// Generic APPn segment
byte[] bytes = new byte[length - 2];
data.readFully(bytes);
return new Application(marker, identifier, bytes);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Harald Kuhr
* Copyright (c) 2016, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,39 +28,36 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import java.util.Arrays;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import java.io.DataInput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* SOFSegment
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$
*/
final class SOFSegment {
final int marker;
final int samplePrecision;
final int lines; // height
final int samplesPerLine; // width
final SOFComponent[] components;
* Comment.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: Comment.java,v 1.0 23/08/16 harald.kuhr Exp$
*/
class Comment extends Segment {
final String comment;
SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) {
this.marker = marker;
this.samplePrecision = samplePrecision;
this.lines = lines;
this.samplesPerLine = samplesPerLine;
this.components = components;
}
final int componentsInFrame() {
return components.length;
private Comment(final String comment) {
super(JPEG.COM);
this.comment = comment;
}
@Override
public String toString() {
return String.format(
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
);
return "COM[" + comment + "]";
}
public static Segment read(final DataInput data, final int length) throws IOException {
byte[] ascii = new byte[length - 2];
data.readFully(ascii);
return new Comment(new String(ascii, StandardCharsets.UTF_8)); // Strictly, it is ASCII, but UTF-8 is compatible
}
}
@@ -28,6 +28,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
@@ -61,7 +62,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
private transient SoftReference<BufferedImage> cachedThumbnail;
public EXIFThumbnailReader(ThumbnailReadProgressListener progressListener, ImageReader jpegReader, int imageIndex, int thumbnailIndex, Directory ifd, ImageInputStream stream) {
EXIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final Directory ifd, final ImageInputStream stream) {
super(progressListener, imageIndex, thumbnailIndex);
this.reader = Validate.notNull(jpegReader);
this.ifd = ifd;
@@ -95,14 +96,14 @@ final class EXIFThumbnailReader extends ThumbnailReader {
}
}
private BufferedImage readJPEGCached(boolean pixelsExposed) throws IOException {
private BufferedImage readJPEGCached(final boolean pixelsExposed) throws IOException {
BufferedImage thumbnail = cachedThumbnail != null ? cachedThumbnail.get() : null;
if (thumbnail == null) {
thumbnail = readJPEG();
}
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
return thumbnail;
}
@@ -132,14 +133,10 @@ final class EXIFThumbnailReader extends ThumbnailReader {
input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
try {
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input);
try {
try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) {
return readJPEGThumbnail(reader, stream);
}
finally {
stream.close();
}
}
finally {
input.close();
@@ -195,15 +192,15 @@ final class EXIFThumbnailReader extends ThumbnailReader {
break;
case 6:
// YCbCr
for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
for (int i = 0; i < thumbSize; i += 3) {
YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
}
break;
default:
throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation);
}
return ThumbnailReader.readRawThumbnail(thumbData, thumbData.length, 0, w, h);
return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h);
}
throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail");
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
/**
* Frame
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$
*/
final class Frame extends Segment {
final int samplePrecision; // Sample precision
final int lines; // Height
final int samplesPerLine; // Width
final Component[] components; // Components specifications
private Frame(final int marker, final int samplePrecision, final int lines, final int samplesPerLine, final Component[] components) {
super(marker);
this.samplePrecision = samplePrecision;
this.lines = lines;
this.samplesPerLine = samplesPerLine;
this.components = components;
}
int process() {
return marker & 0xff - 0xc0;
}
int componentsInFrame() {
return components.length;
}
Component getComponent(final int id) {
for (Component component : components) {
if (component.id == id) {
return component;
}
}
throw new IllegalArgumentException(String.format("No such component id: %d", id));
}
@Override
public String toString() {
return String.format(
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
process(), marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
);
}
static Frame read(final int marker, final DataInput data, final int length) throws IOException {
int samplePrecision = data.readUnsignedByte();
int lines = data.readUnsignedShort();
int samplesPerLine = data.readUnsignedShort();
int componentsInFrame = data.readUnsignedByte();
int expected = 8 + componentsInFrame * 3;
if (length != expected) {
throw new IIOException(String.format("Unexpected SOF length: %d != %d", length, expected));
}
Component[] components = new Component[componentsInFrame];
for (int i = 0; i < componentsInFrame; i++) {
int id = data.readUnsignedByte();
int sub = data.readUnsignedByte();
int qtSel = data.readUnsignedByte();
components[i] = new Component(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
}
return new Frame(marker, samplePrecision, lines, samplesPerLine, components);
}
static Frame read(final int marker, final ImageInputStream data) throws IOException {
int length = data.readUnsignedShort();
return read(marker, new SubImageInputStream(data, length), length);
}
public static final class Component {
final int id;
final int hSub; // Horizontal sampling factor
final int vSub; // Vertical sampling factor
final int qtSel; // Quantization table destination selector
Component(int id, int hSub, int vSub, int qtSel) {
this.id = id;
this.hSub = hSub;
this.vSub = vSub;
this.qtSel = qtSel;
}
@Override
public String toString() {
// Use id either as component number or component name, based on value
Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id;
return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel);
}
}
}
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2016, Harald Kuhr
* Copyright (C) 2015, Michael Martinez
* Copyright (C) 2004, Helmut Dersch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is present
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is present
static final int MSB = 0x80000000;
private HuffmanTable() {
super(JPEG.DHT);
}
void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int t = 0; t < 4; t++) {
for (int c = 0; c < 2; c++) {
if (tc[t][c] != 0) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
}
}
}
}
// Build_HuffTab()
// Parameter: t table ID
// c table class ( 0 for DC, 1 for AC )
// L[i] # of codewords which length is i
// V[i][j] Huffman Value (length=i)
// Effect:
// build up HuffTab[t][c] using L and V.
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
int temp = 256;
int k = 0;
for (int i = 0; i < 8; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
for (int n = 0; n < (temp >> (i + 1)); n++) {
tab[k] = V[i][j] | ((i + 1) << 8);
k++;
}
}
}
for (int i = 1; k < 256; i++, k++) {
tab[k] = i | MSB;
}
int currentTable = 1;
k = 0;
for (int i = 8; i < 16; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
for (int n = 0; n < (temp >> (i - 7)); n++) {
tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
k++;
}
if (k >= 256) {
if (k > 256) {
throw new IIOException("JPEG Huffman Table error");
}
k = 0;
currentTable++;
}
}
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("DHT[");
for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) {
if (builder.length() > 4) {
builder.append(", ");
}
builder.append("id: ");
builder.append(t);
builder.append(", class: ");
builder.append(c);
}
}
}
builder.append(']');
return builder.toString();
}
public static Segment read(final DataInput data, final int length) throws IOException {
int count = 2;
HuffmanTable table = new HuffmanTable();
while (count < length) {
int temp = data.readUnsignedByte();
count++;
int t = temp & 0x0F;
if (t > 3) {
throw new IIOException("Unexpected JPEG Huffman Table Id (> 3):" + t);
}
int c = temp >> 4;
if (c > 2) {
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
}
table.th[t] = 1;
table.tc[t][c] = 1;
for (int i = 0; i < 16; i++) {
table.l[t][c][i] = data.readUnsignedByte();
count++;
}
for (int i = 0; i < 16; i++) {
for (int j = 0; j < table.l[t][c][i]; j++) {
if (count > length) {
throw new IIOException("JPEG Huffman Table format error");
}
table.v[t][c][i][j] = data.readUnsignedByte();
count++;
}
}
}
if (count != length) {
throw new IIOException("JPEG Huffman Table format error, bad segment length: " + length);
}
return table;
}
}
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import java.io.DataInput;
import java.io.IOException;
/**
* ICCProfile.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: ICCProfile.java,v 1.0 22/08/16 harald.kuhr Exp$
*/
final class ICCProfile extends Application {
private ICCProfile(final byte[] data) {
super(JPEG.APP2, "ICC_PROFILE", data);
}
// TODO: Create util method to concat all ICC segments to one and return ICC_Profile (move from JPEGImageReader)
// If so, how to deal with warnings from the original code? Throw exceptions instead?
@Override
public String toString() {
return "ICC_PROFILE[" + data[0] + "/" + data[1] + " length: " + data.length + "]";
}
public static ICCProfile read(DataInput data, int length) throws IOException {
byte[] bytes = new byte[length - 2];
data.readFully(bytes);
return new ICCProfile(bytes);
}
}
@@ -28,9 +28,10 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import java.io.*;
import java.nio.ByteBuffer;
/**
* JFIFSegment
@@ -39,7 +40,7 @@ import java.io.InputStream;
* @author last modified by $Author: haraldk$
* @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$
*/
class JFIFSegment {
final class JFIF extends Application {
final int majorVersion;
final int minorVersion;
final int units;
@@ -49,7 +50,9 @@ class JFIFSegment {
final int yThumbnail;
final byte[] thumbnail;
private JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) {
private JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail, byte[] data) {
super(JPEG.APP0, "JFIF", data);
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.units = units;
@@ -86,20 +89,41 @@ class JFIFSegment {
return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail);
}
public static JFIFSegment read(final InputStream data) throws IOException {
DataInputStream stream = new DataInputStream(data);
public static JFIF read(final DataInput data, int length) throws IOException {
if (length < 2 + 5 + 9) {
throw new EOFException();
}
data.readFully(new byte[5]);
byte[] bytes = new byte[length - 2 - 5];
data.readFully(bytes);
int x, y;
return new JFIFSegment(
stream.readUnsignedByte(),
stream.readUnsignedByte(),
stream.readUnsignedByte(),
stream.readUnsignedShort(),
stream.readUnsignedShort(),
x = stream.readUnsignedByte(),
y = stream.readUnsignedByte(),
JPEGImageReader.readFully(stream, x * y * 3)
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return new JFIF(
buffer.get() & 0xff,
buffer.get() & 0xff,
buffer.get() & 0xff,
buffer.getShort() & 0xffff,
buffer.getShort() & 0xffff,
x = buffer.get() & 0xff,
y = buffer.get() & 0xff,
getBytes(buffer, x * y * 3),
bytes
);
}
private static byte[] getBytes(ByteBuffer buffer, int len) {
if (len == 0) {
return null;
}
byte[] dst = new byte[len];
buffer.get(dst);
return dst;
}
}
@@ -39,9 +39,9 @@ import java.io.IOException;
* @version $Id: JFIFThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$
*/
final class JFIFThumbnailReader extends ThumbnailReader {
private final JFIFSegment segment;
private final JFIF segment;
public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIFSegment segment) {
JFIFThumbnailReader(final ThumbnailReadProgressListener progressListener, final int imageIndex, final int thumbnailIndex, final JFIF segment) {
super(progressListener, imageIndex, thumbnailIndex);
this.segment = segment;
}
@@ -28,9 +28,9 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import java.io.DataInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
* JFXXSegment
@@ -39,7 +39,7 @@ import java.io.InputStream;
* @author last modified by $Author: haraldk$
* @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$
*/
class JFXXSegment {
final class JFXX extends Application {
public static final int JPEG = 0x10;
public static final int INDEXED = 0x11;
public static final int RGB = 0x13;
@@ -47,7 +47,9 @@ class JFXXSegment {
final int extensionCode;
final byte[] thumbnail;
private JFXXSegment(int extensionCode, byte[] thumbnail) {
private JFXX(final int extensionCode, final byte[] thumbnail, final byte[] data) {
super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", data);
this.extensionCode = extensionCode;
this.thumbnail = thumbnail;
}
@@ -70,12 +72,16 @@ class JFXXSegment {
}
}
public static JFXXSegment read(InputStream data, int length) throws IOException {
DataInputStream stream = new DataInputStream(data);
public static JFXX read(final DataInput data, final int length) throws IOException {
data.readFully(new byte[5]);
return new JFXXSegment(
stream.readUnsignedByte(),
JPEGImageReader.readFully(stream, length - 1)
byte[] bytes = new byte[length - 2 - 5];
data.readFully(bytes);
return new JFXX(
bytes[0] & 0xff,
bytes.length - 1 > 0 ? Arrays.copyOfRange(bytes, 1, bytes.length - 1) : null,
bytes
);
}
}
@@ -50,11 +50,11 @@ import java.lang.ref.SoftReference;
final class JFXXThumbnailReader extends ThumbnailReader {
private final ImageReader reader;
private final JFXXSegment segment;
private final JFXX segment;
private transient SoftReference<BufferedImage> cachedThumbnail;
protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) {
JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, final ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXX segment) {
super(progressListener, imageIndex, thumbnailIndex);
this.reader = Validate.notNull(jpegReader);
this.segment = segment;
@@ -66,13 +66,13 @@ final class JFXXThumbnailReader extends ThumbnailReader {
BufferedImage thumbnail;
switch (segment.extensionCode) {
case JFXXSegment.JPEG:
case JFXX.JPEG:
thumbnail = readJPEGCached(true);
break;
case JFXXSegment.INDEXED:
case JFXX.INDEXED:
thumbnail = readIndexed();
break;
case JFXXSegment.RGB:
case JFXX.RGB:
thumbnail = readRGB();
break;
default:
@@ -85,7 +85,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
return thumbnail;
}
public IIOMetadata readMetadata() throws IOException {
IIOMetadata readMetadata() throws IOException {
ImageInputStream input = new ByteArrayImageInputStream(segment.thumbnail);
try {
@@ -119,10 +119,10 @@ final class JFXXThumbnailReader extends ThumbnailReader {
@Override
public int getWidth() throws IOException {
switch (segment.extensionCode) {
case JFXXSegment.RGB:
case JFXXSegment.INDEXED:
case JFXX.RGB:
case JFXX.INDEXED:
return segment.thumbnail[0] & 0xff;
case JFXXSegment.JPEG:
case JFXX.JPEG:
return readJPEGCached(false).getWidth();
default:
throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
@@ -132,10 +132,10 @@ final class JFXXThumbnailReader extends ThumbnailReader {
@Override
public int getHeight() throws IOException {
switch (segment.extensionCode) {
case JFXXSegment.RGB:
case JFXXSegment.INDEXED:
case JFXX.RGB:
case JFXX.INDEXED:
return segment.thumbnail[1] & 0xff;
case JFXXSegment.JPEG:
case JFXX.JPEG:
return readJPEGCached(false).getHeight();
default:
throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode));
@@ -0,0 +1,211 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode;
import java.util.List;
/**
* JPEGImage10Metadata.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGImage10Metadata.java,v 1.0 10/08/16 harald.kuhr Exp$
*/
class JPEGImage10Metadata extends AbstractMetadata {
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place....
private final List<Segment> segments;
JPEGImage10Metadata(List<Segment> segments) {
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
this.segments = segments;
}
@Override
protected Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
root.appendChild(jpegVariety);
// TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
root.appendChild(markerSequence);
for (Segment segment : segments)
switch (segment.marker) {
// SOF3 is the only one supported by now
case JPEG.SOF3:
Frame sofSegment = (Frame) segment;
IIOMetadataNode sof = new IIOMetadataNode("sof");
sof.setAttribute("process", String.valueOf(sofSegment.marker & 0xf));
sof.setAttribute("samplePrecision", String.valueOf(sofSegment.samplePrecision));
sof.setAttribute("numLines", String.valueOf(sofSegment.lines));
sof.setAttribute("samplesPerLine", String.valueOf(sofSegment.samplesPerLine));
sof.setAttribute("numFrameComponents", String.valueOf(sofSegment.componentsInFrame()));
for (Frame.Component component : sofSegment.components) {
IIOMetadataNode componentSpec = new IIOMetadataNode("componentSpec");
componentSpec.setAttribute("componentId", String.valueOf(component.id));
componentSpec.setAttribute("HsamplingFactor", String.valueOf(component.hSub));
componentSpec.setAttribute("VsamplingFactor", String.valueOf(component.vSub));
componentSpec.setAttribute("QtableSelector", String.valueOf(component.qtSel));
sof.appendChild(componentSpec);
}
markerSequence.appendChild(sof);
break;
case JPEG.DHT:
HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dht = new IIOMetadataNode("dht");
// Uses fixed tables...
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) {
if (huffmanTable.tc[i][j] != 0) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(j));
dhtable.setAttribute("htableId", String.valueOf(i));
dht.appendChild(dhtable);
}
}
}
markerSequence.appendChild(dht);
break;
case JPEG.DQT:
markerSequence.appendChild(new IIOMetadataNode("dqt"));
// TODO:
break;
case JPEG.SOS:
Scan scan = (Scan) segment;
IIOMetadataNode sos = new IIOMetadataNode("sos");
sos.setAttribute("numScanComponents", String.valueOf(scan.components.length));
sos.setAttribute("startSpectralSelection", String.valueOf(scan.spectralSelStart));
sos.setAttribute("endSpectralSelection", String.valueOf(scan.spectralSelEnd));
sos.setAttribute("approxHigh", String.valueOf(scan.approxHigh));
sos.setAttribute("approxLow", String.valueOf(scan.approxLow));
for (Scan.Component component : scan.components) {
IIOMetadataNode spec = new IIOMetadataNode("scanComponentSpec");
spec.setAttribute("componentSelector", String.valueOf(component.scanCompSel));
spec.setAttribute("dcHuffTable", String.valueOf(component.dcTabSel));
spec.setAttribute("acHuffTable", String.valueOf(component.acTabSel));
sos.appendChild(spec);
}
markerSequence.appendChild(sos);
break;
case JPEG.COM:
IIOMetadataNode com = new IIOMetadataNode("com");
com.setAttribute("comment", ((Comment) segment).comment);
markerSequence.appendChild(com);
break;
case JPEG.APP14:
if (segment instanceof AdobeDCT) {
AdobeDCT adobe = (AdobeDCT) segment;
IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
app14Adobe.setAttribute("version", String.valueOf(adobe.version));
app14Adobe.setAttribute("flags0", String.valueOf(adobe.flags0));
app14Adobe.setAttribute("flags1", String.valueOf(adobe.flags1));
app14Adobe.setAttribute("transform", String.valueOf(adobe.transform));
markerSequence.appendChild(app14Adobe);
break;
}
// Else, fall through to unknown segment
default:
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
unknown.setAttribute("MarkerTag", String.valueOf(segment.marker & 0xFF));
unknown.setUserObject(((Application) segment).data);
markerSequence.appendChild(unknown);
break;
}
return root;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
for (Segment segment : segments) {
if (segment instanceof Frame) {
Frame sofSegment = (Frame) segment;
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "Gray" : "RGB"); // TODO YCC, YCCK, CMYK etc
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame()));
chroma.appendChild(numChannels);
break;
}
}
return chroma;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "JPEG"); // ...or "JPEG-LOSSLESS" (which is the name used by the JAI JPEGImageWriter for it's compression name)?
compression.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only
compression.appendChild(lossless);
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
numProgressiveScans.setAttribute("value", "1"); // TODO!
compression.appendChild(numProgressiveScans);
return compression;
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "normal"); // TODO
dimension.appendChild(imageOrientation);
return dimension;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text");
for (Segment segment : segments) {
if (segment instanceof Comment) {
IIOMetadataNode com = new IIOMetadataNode("TextEntry");
com.setAttribute("keyword", "comment");
com.setAttribute("value", ((Comment) segment).comment);
text.appendChild(com);
}
}
return text.hasChildNodes() ? text : null;
}
}
@@ -1,7 +1,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -11,9 +10,7 @@ import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
/**
@@ -39,7 +36,7 @@ final class JPEGImage10MetadataCleaner {
IIOMetadata cleanMetadata(final IIOMetadata imageMetadata) throws IOException {
// We filter out pretty much everything from the stream..
// Meaning we have to read get *all APP segments* and re-insert into metadata.
List<JPEGSegment> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
List<Application> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
// NOTE: There's a bug in the merging code in JPEGMetadata mergeUnknownNode that makes sure all "unknown" nodes are added twice in certain conditions.... ARGHBL...
// DONE: 1: Work around
@@ -70,11 +67,11 @@ final class JPEGImage10MetadataCleaner {
IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
JFIFSegment jfifSegment = reader.getJFIF();
JFXXSegment jfxxSegment = reader.getJFXX();
AdobeDCTSegment adobeDCT = reader.getAdobeDCT();
JFIF jfifSegment = reader.getJFIF();
JFXX jfxx = reader.getJFXX();
AdobeDCT adobeDCT = reader.getAdobeDCT();
ICC_Profile embeddedICCProfile = reader.getEmbeddedICCProfile(true);
SOFSegment sof = reader.getSOF();
Frame sof = reader.getSOF();
boolean hasRealJFIF = false;
boolean hasRealJFXX = false;
@@ -104,17 +101,17 @@ final class JPEGImage10MetadataCleaner {
hasRealICC = true;
}
if (jfxxSegment != null) {
if (jfxx != null) {
IIOMetadataNode JFXX = new IIOMetadataNode("JFXX");
jfif.appendChild(JFXX);
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxxSegment.extensionCode));
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode));
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxxSegment);
JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxx);
IIOMetadataNode jfifThumb;
switch (jfxxSegment.extensionCode) {
case JFXXSegment.JPEG:
switch (jfxx.extensionCode) {
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.JPEG:
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
IIOMetadata thumbMeta = thumbnailReader.readMetadata();
@@ -123,14 +120,14 @@ final class JPEGImage10MetadataCleaner {
app0JFXX.appendChild(jfifThumb);
break;
case JFXXSegment.INDEXED:
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.INDEXED:
jfifThumb = new IIOMetadataNode("JFIFthumbPalette");
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
app0JFXX.appendChild(jfifThumb);
break;
case JFXXSegment.RGB:
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.RGB:
jfifThumb = new IIOMetadataNode("JFIFthumbRGB");
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
@@ -138,7 +135,7 @@ final class JPEGImage10MetadataCleaner {
break;
default:
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxxSegment.extensionCode));
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxx.extensionCode));
}
JFXX.appendChild(app0JFXX);
@@ -156,12 +153,12 @@ final class JPEGImage10MetadataCleaner {
}
// Special case: Broken AdobeDCT segment, inconsistent with SOF, use values from SOF
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() < 4 ||
adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() < 3)) {
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() < 4 ||
adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() < 3)) {
reader.processWarningOccurred(String.format(
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " +
"Ignoring Adobe App14 marker.",
adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
sof.marker & 0xf, sof.componentsInFrame()
));
@@ -176,46 +173,43 @@ final class JPEGImage10MetadataCleaner {
}
Node next = null;
for (JPEGSegment segment : appSegments) {
for (Application segment : appSegments) {
// Except real app0JFIF, app0JFXX, app2ICC and app14Adobe, add all the app segments that we filtered away as "unknown" markers
if (segment.marker() == JPEG.APP0 && "JFIF".equals(segment.identifier()) && hasRealJFIF) {
if (segment.marker == JPEG.APP0 && "JFIF".equals(segment.identifier) && hasRealJFIF) {
continue;
}
else if (segment.marker() == JPEG.APP0 && "JFXX".equals(segment.identifier()) && hasRealJFXX) {
else if (segment.marker == JPEG.APP0 && "JFXX".equals(segment.identifier) && hasRealJFXX) {
continue;
}
else if (segment.marker() == JPEG.APP1 && "Exif".equals(segment.identifier()) /* always inserted */) {
else if (segment.marker == JPEG.APP1 && "Exif".equals(segment.identifier) /* always inserted */) {
continue;
}
else if (segment.marker() == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier()) && hasRealICC) {
else if (segment.marker == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier) && hasRealICC) {
continue;
}
else if (segment.marker() == JPEG.APP14 && "Adobe".equals(segment.identifier()) /* always inserted */) {
else if (segment.marker == JPEG.APP14 && "Adobe".equals(segment.identifier) /* always inserted */) {
continue;
}
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
unknown.setAttribute("MarkerTag", Integer.toString(segment.marker() & 0xff));
unknown.setAttribute("MarkerTag", Integer.toString(segment.marker & 0xff));
DataInputStream stream = new DataInputStream(segment.data());
unknown.setUserObject(segment.data);
try {
String identifier = segment.identifier();
int off = identifier != null ? identifier.length() + 1 : 0;
byte[] data = new byte[off + segment.length()];
if (identifier != null) {
System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length());
}
stream.readFully(data, off, segment.length());
unknown.setUserObject(data);
}
finally {
stream.close();
}
// try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(segment.data))) {
// String identifier = segment.identifier;
// int off = identifier != null ? identifier.length() + 1 : 0;
//
// byte[] data = new byte[off + segment.data.length];
//
// if (identifier != null) {
// System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length());
// }
//
// stream.readFully(data, off, segment.data.length);
//
// unknown.setUserObject(data);
// }
if (next == null) {
// To be semi-compatible with the functionality in mergeTree,
@@ -271,12 +265,12 @@ final class JPEGImage10MetadataCleaner {
dht.getParentNode().insertBefore(acTables, dht.getNextSibling());
// Split into 2 dht nodes, one for AC and one for DC
for (int i = 0; i < dhtables.getLength(); i++) {
for (int i = dhtables.getLength() - 1; i >= 0 ; i--) {
Element dhtable = (Element) dhtables.item(i);
String tableClass = dhtable.getAttribute("class");
if ("1".equals(tableClass)) {
dht.removeChild(dhtable);
acTables.appendChild(dhtable);
acTables.insertBefore(dhtable, acTables.getFirstChild());
}
}
}
@@ -29,6 +29,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
@@ -48,14 +49,14 @@ import java.util.Locale;
* @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$
*/
public class JPEGImageReaderSpi extends ImageReaderSpiBase {
private ImageReaderSpi delegateProvider;
protected ImageReaderSpi delegateProvider;
/**
* Constructor for use by {@link javax.imageio.spi.IIORegistry} only.
* The instance created will not work without being properly registered.
*/
public JPEGImageReaderSpi() {
super(new JPEGProviderInfo());
this(new JPEGProviderInfo());
}
/**
@@ -69,6 +70,15 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
this.delegateProvider = Validate.notNull(delegateProvider);
}
/**
* Constructor for subclasses.
*
* @param info
*/
protected JPEGImageReaderSpi(final ReaderWriterProviderInfo info) {
super(info);
}
static ImageReaderSpi lookupDelegateProvider(final ServiceRegistry registry) {
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, true);
@@ -83,7 +93,7 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
return null;
}
@SuppressWarnings({"unchecked"})
@SuppressWarnings({"unchecked", "deprecation"})
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
if (delegateProvider == null) {
@@ -0,0 +1,743 @@
/*
* Copyright (c) 2016, Harald Kuhr
* Copyright (C) 2015, Michael Martinez
* Copyright (C) 2004, Helmut Dersch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
final class JPEGLosslessDecoder {
private final ImageInputStream input;
private final JPEGImageReader listenerDelegate;
private final Frame frame;
private final List<HuffmanTable> huffTables;
private final QuantizationTable quantTable;
private Scan scan;
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
private final int IDCT_Source[] = new int[64];
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
private boolean restarting;
private int marker;
private int markerIndex;
private int numComp;
private int restartInterval;
private int selection;
private int xDim, yDim;
private int xLoc;
private int yLoc;
private int mask;
private int[] outputData;
private int[] outputRedData;
private int[] outputGreenData;
private int[] outputBlueData;
private static final int IDCT_P[] = {
0, 5, 40, 16, 45, 2, 7, 42,
21, 56, 8, 61, 18, 47, 1, 4,
41, 23, 58, 13, 32, 24, 37, 10,
63, 17, 44, 3, 6, 43, 20, 57,
15, 34, 29, 48, 53, 26, 39, 9,
60, 19, 46, 22, 59, 12, 33, 31,
50, 55, 25, 36, 11, 62, 14, 35,
28, 49, 52, 27, 38, 30, 51, 54
};
private static final int TABLE[] = {
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
private static final int RESTART_MARKER_END = 0xFFD7;
private static final int MAX_HUFFMAN_SUBTREE = 50;
private static final int MSB = 0x80000000;
int getDimX() {
return xDim;
}
int getDimY() {
return yDim;
}
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
Validate.notNull(segments);
frame = get(segments, Frame.class);
scan = get(segments, Scan.class);
QuantizationTable qt = get(segments, QuantizationTable.class);
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
RestartInterval dri = get(segments, RestartInterval.class);
restartInterval = dri != null ? dri.interval : 0;
input = data;
this.listenerDelegate = listenerDelegate;
}
private <T> List<T> getAll(final List<Segment> segments, final Class<T> type) {
ArrayList<T> list = new ArrayList<>();
for (Segment segment : segments) {
if (type.isInstance(segment)) {
list.add(type.cast(segment));
}
}
return list;
}
private <T> T get(final List<Segment> segments, final Class<T> type) {
for (Segment segment : segments) {
if (type.isInstance(segment)) {
return type.cast(segment);
}
}
return null;
}
int[][] decode() throws IOException {
int current, scanNum = 0;
final int pred[] = new int[10];
int[][] outputRef;
xLoc = 0;
yLoc = 0;
current = input.readUnsignedShort();
if (current != JPEG.SOI) { // SOI
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
}
for (HuffmanTable huffTable : huffTables) {
huffTable.buildHuffTables(HuffTab);
}
quantTable.enhanceTables(TABLE);
current = input.readUnsignedShort();
do {
// Skip until first SOS
while (current != JPEG.SOS) {
input.skipBytes(input.readUnsignedShort() - 2);
current = input.readUnsignedShort();
}
int precision = frame.samplePrecision;
if (precision == 8) {
mask = 0xFF;
}
else {
mask = 0xFFFF;
}
Frame.Component[] components = frame.components;
scan = readScan();
numComp = scan.components.length;
selection = scan.spectralSelStart;
final Scan.Component[] scanComps = scan.components;
final int[][] quantTables = quantTable.quantTables;
for (int i = 0; i < numComp; i++) {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTables[component.qtSel];
nBlock[i] = component.vSub * component.hSub;
int dcTabSel = scanComps[i].dcTabSel;
int acTabSel = scanComps[i].acTabSel;
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
if (useACForDC(dcTabSel)) {
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
dcTab[i] = HuffTab[dcTabSel][1];
acTab[i] = HuffTab[acTabSel][0];
}
else {
// All good
dcTab[i] = HuffTab[dcTabSel][0];
acTab[i] = HuffTab[acTabSel][1];
}
}
xDim = frame.samplesPerLine;
yDim = frame.lines;
outputRef = new int[numComp][];
// TODO: Support 4 components (RGBA/YCCA/CMYK/YCCK), others?
if (numComp == 1) {
outputData = new int[xDim * yDim];
outputRef[0] = outputData;
}
else {
outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
outputGreenData = new int[xDim * yDim];
outputBlueData = new int[xDim * yDim];
outputRef[0] = outputRedData;
outputRef[1] = outputGreenData;
outputRef[2] = outputBlueData;
}
scanNum++;
while (true) { // Decode one scan
int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1];
for (int i = 0; i < 10; i++) {
pred[i] = (1 << (precision - 1));
}
if (restartInterval == 0) {
current = decode(pred, temp, index);
while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) {
output(pred);
current = decode(pred, temp, index);
}
break; //current=MARKER
}
for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) {
restarting = (mcuNum == 0);
current = decode(pred, temp, index);
output(pred);
if (current != 0) {
break;
}
}
if (current == 0) {
if (markerIndex != 0) {
current = (0xFF00 | marker);
markerIndex = 0;
}
else {
current = input.readUnsignedShort();
}
}
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
break; //current=MARKER
}
}
if ((current == JPEG.DNL) && (scanNum == 1)) { //DNL
readNumber();
current = input.readUnsignedShort();
}
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
return outputRef;
}
private void processWarningOccured(String warning) {
listenerDelegate.processWarningOccurred(warning);
}
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
for (HuffmanTable huffTable : huffTables) {
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
return true;
}
}
}
return false;
}
private boolean isLossless() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
for (Frame.Component component : components) {
if (component.id == sel) {
return component;
}
}
throw new IllegalArgumentException("No such component id: " + sel);
}
private Scan readScan() throws IOException {
int length = input.readUnsignedShort();
return Scan.read(input, length);
}
private int decode(final int prev[], final int temp[], final int index[]) throws IOException {
if (numComp == 1) {
return decodeSingle(prev, temp, index);
}
else if (numComp == 3) {
return decodeRGB(prev, temp, index);
}
else {
return -1;
}
}
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException {
// At the beginning of the first line and
// at the beginning of each restart interval the prediction value of 2P 1 is used, where P is the input precision.
if (restarting) {
restarting = false;
prev[0] = (1 << (frame.samplePrecision - 1));
}
else {
switch (selection) {
case 2:
prev[0] = getPreviousY(outputData);
break;
case 3:
prev[0] = getPreviousXY(outputData);
break;
case 4:
prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
break;
case 5:
prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 6:
prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 7:
prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
break;
default:
prev[0] = getPreviousX(outputData);
break;
}
}
for (int i = 0; i < nBlock[0]; i++) {
int value = getHuffmanValue(dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
int n = getn(prev, value, temp, index);
int nRestart = (n >> 8);
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
return nRestart;
}
prev[0] += n;
}
return 0;
}
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
switch (selection) {
case 2:
prev[0] = getPreviousY(outputRedData);
prev[1] = getPreviousY(outputGreenData);
prev[2] = getPreviousY(outputBlueData);
break;
case 3:
prev[0] = getPreviousXY(outputRedData);
prev[1] = getPreviousXY(outputGreenData);
prev[2] = getPreviousXY(outputBlueData);
break;
case 4:
prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData);
prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData);
prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData);
break;
case 5:
prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1);
prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
break;
case 6:
prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1);
prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
break;
case 7:
prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2);
prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2);
prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2);
break;
default:
prev[0] = getPreviousX(outputRedData);
prev[1] = getPreviousX(outputGreenData);
prev[2] = getPreviousX(outputBlueData);
break;
}
int value, actab[], dctab[];
int qtab[];
for (int ctrC = 0; ctrC < numComp; ctrC++) {
qtab = qTab[ctrC];
actab = acTab[ctrC];
dctab = dcTab[ctrC];
for (int i = 0; i < nBlock[ctrC]; i++) {
for (int k = 0; k < IDCT_Source.length; k++) {
IDCT_Source[k] = 0;
}
value = getHuffmanValue(dctab, temp, index);
if (value >= 0xFF00) {
return value;
}
prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index);
IDCT_Source[0] *= qtab[0];
for (int j = 1; j < 64; j++) {
value = getHuffmanValue(actab, temp, index);
if (value >= 0xFF00) {
return value;
}
j += (value >> 4);
if ((value & 0x0F) == 0) {
if ((value >> 4) == 0) {
break;
}
}
else {
IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j];
}
}
}
}
return 0;
}
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
// HuffTab[*][*][0-256] is always the only 1st-layer table.
//
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
//
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// ):
// return: Huffman Value of table
// 0xFF?? if it receives a MARKER
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// temp temp storage for remainded bits
// index index to bit of temp
// in FILE pointer
// Effect:
// temp store new remainded bits
// index change to new index
// in change to new position
// NOTE:
// Initial by temp=0; index=0;
// NOTE: (explain temp and index)
// temp: is always in the form at calling time or returning time
// | byte 4 | byte 3 | byte 2 | byte 1 |
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
// ^index=3 (from 0 to 15)
// 321
// NOTE (marker and marker_index):
// If get a MARKER from 'in', marker=the low-byte of the MARKER
// and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue()
// will not be called
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException {
int code, input;
final int mask = 0xFFFF;
if (index[0] < 8) {
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
}
else {
index[0] -= 8;
}
code = table[temp[0] >> index[0]];
if ((code & MSB) != 0) {
if (markerIndex != 0) {
markerIndex = 0;
return 0xFF00 | marker;
}
temp[0] &= (mask >> (16 - index[0]));
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])];
index[0] += 8;
}
index[0] += 8 - (code >> 8);
if (index[0] < 0) {
throw new IIOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()");
}
if (index[0] < markerIndex) {
markerIndex = 0;
return 0xFF00 | marker;
}
temp[0] &= (mask >> (16 - index[0]));
return code & 0xFF;
}
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException {
int result;
final int one = 1;
final int n_one = -1;
final int mask = 0xFFFF;
int input;
if (n == 0) {
return 0;
}
if (n == 16) {
if (pred[0] >= 0) {
return -32768;
}
else {
return 32768;
}
}
index[0] -= n;
if (index[0] >= 0) {
if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
}
else {
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
if (index[0] < 0) {
if (markerIndex != 0) {
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
}
if (index[0] < 0) {
throw new IOException("index=" + index[0] + " in getn()");
}
if (index[0] < markerIndex) {
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
}
if (result < (one << (n - 1))) {
result += (n_one << n) + 1;
}
return result;
}
private int getPreviousX(final int data[]) {
if (xLoc > 0) {
return data[((yLoc * xDim) + xLoc) - 1];
}
else if (yLoc > 0) {
return getPreviousY(data);
}
else {
return (1 << (frame.samplePrecision - 1));
}
}
private int getPreviousXY(final int data[]) {
if ((xLoc > 0) && (yLoc > 0)) {
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
}
else {
return getPreviousY(data);
}
}
private int getPreviousY(final int data[]) {
if (yLoc > 0) {
return data[((yLoc - 1) * xDim) + xLoc];
}
else {
return getPreviousX(data);
}
}
private boolean isLastPixel() {
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
}
private void output(final int pred[]) {
if (numComp == 1) {
outputSingle(pred);
}
else {
outputRGB(pred);
}
}
private void outputSingle(final int pred[]) {
if ((xLoc < xDim) && (yLoc < yDim)) {
outputData[(yLoc * xDim) + xLoc] = mask & pred[0];
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
private void outputRGB(final int pred[]) {
if ((xLoc < xDim) && (yLoc < yDim)) {
outputRedData[(yLoc * xDim) + xLoc] = pred[0];
outputGreenData[(yLoc * xDim) + xLoc] = pred[1];
outputBlueData[(yLoc * xDim) + xLoc] = pred[2];
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
private int readNumber() throws IOException {
final int Ld = input.readUnsignedShort();
if (Ld != 4) {
throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]");
}
return input.readUnsignedShort();
}
int getNumComponents() {
return numComp;
}
int getPrecision() {
return frame.samplePrecision;
}
}

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