Compare commits

..

170 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 d3cbeae9ba Updating TODOs. 2015-10-28 15:32:15 +01: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
200 changed files with 8695 additions and 1239 deletions
+31 -31
View File
@@ -2,7 +2,7 @@
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](http://search.maven.org/#search%7Cga%7C1%7Cg%3Acom.twelvemonkeys*%20AND%20v%3A%223.2%22) is released (Nov. 1st, 2015).
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
@@ -479,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.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.2</version> <!-- Alternatively, build your own version -->
<version>3.2.1</version> <!-- Alternatively, build your own version -->
</dependency>
</dependencies>
@@ -492,13 +492,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.2.jar
twelvemonkeys-common-io-3.2.jar
twelvemonkeys-common-image-3.2.jar
twelvemonkeys-imageio-core-3.2.jar
twelvemonkeys-imageio-metadata-3.2.jar
twelvemonkeys-imageio-jpeg-3.2.jar
twelvemonkeys-imageio-tiff-3.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
@@ -507,37 +507,37 @@ To depend on the JPEG and TIFF plugin in your IDE or program, add all of the fol
Requires Java 7 or later.
Common dependencies
* [common-lang-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.2/common-lang-3.2.jar)
* [common-io-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.2/common-io-3.2.jar)
* [common-image-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.2/common-image-3.2.jar)
* [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.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.2/imageio-core-3.2.jar)
* [imageio-metadata-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.2/imageio-metadata-3.2.jar)
* [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.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.2/imageio-bmp-3.2.jar)
* [imageio-jpeg-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.2/imageio-jpeg-3.2.jar)
* [imageio-tiff-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.2/imageio-tiff-3.2.jar)
* [imageio-pnm-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.2/imageio-pnm-3.2.jar)
* [imageio-psd-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.2/imageio-psd-3.2.jar)
* [imageio-hdr-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.2/imageio-hdr-3.2.jar)
* [imageio-iff-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.2/imageio-iff-3.2.jar)
* [imageio-pcx-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.2/imageio-pcx-3.2.jar)
* [imageio-pict-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.2/imageio-pict-3.2.jar)
* [imageio-sgi-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.2/imageio-sgi-3.2.jar)
* [imageio-tga-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.2/imageio-tga-3.2.jar)
* [imageio-icns-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.2/imageio-icns-3.2.jar)
* [imageio-thumbsdb-3.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.2/imageio-thumbsdb-3.2.jar)
* [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.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.2/imageio-batik-3.2.jar)
* [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.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.2/imageio-clippath-3.2.jar)
* [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.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.2/servlet-3.2.jar)
* [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)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.2.2-SNAPSHOT</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);
@@ -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
@@ -306,7 +306,7 @@ public class ResampleOpTestCase {
// https://github.com/haraldk/TwelveMonkeys/issues/195
@Test
public void testAIOOBE() {
public void testAIOOBEHeight() {
BufferedImage myImage = new BufferedImage(100, 354, BufferedImage.TYPE_INT_ARGB);
for (int i = 19; i > 0; i--) {
@@ -316,6 +316,18 @@ public class ResampleOpTestCase {
}
}
// 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() {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+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);
}
}
+28 -13
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -26,40 +26,51 @@
<type>test-jar</type>
</dependency>
<!-- Batik 1.6 contains a mysterious xml-apis:xml-apis:1.1.2 that doesn't exist, override with never version -->
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-rasterizer-ext</artifactId>
<version>${batik.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extensions</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Nothing works without Xerces. Not sure why neither 1.6 or 1.8 versions needed this... -->
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.4.0</version>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-extension</artifactId>
<version>${batik.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-rasterizer</artifactId>
<version>1.6.1</version>
<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>org.apache.xmlgraphics</groupId>
<artifactId>batik-transcoder</artifactId>
<version>1.6.1</version>
<version>${batik.version}</version>
<scope>provided</scope>
<!--
@@ -75,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) {
@@ -52,6 +52,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates an {@code SVGImageReaderSpi}.
*/
@SuppressWarnings("WeakerAccess")
public SVGImageReaderSpi() {
super(new SVGProviderInfo());
}
@@ -60,6 +61,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
return pSource instanceof ImageInputStream && SVG_READER_AVAILABLE && canDecode((ImageInputStream) pSource);
}
@SuppressWarnings("StatementWithEmptyBody")
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
// NOTE: This test is quite quick as it does not involve any parsing,
// however it may not recognize all kinds of SVG documents.
@@ -94,15 +96,15 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
if (buffer[0] == '?') {
// This is the XML declaration or a processing instruction
while (!(pInput.read() == '?' && pInput.read() == '>')) {
// Skip until end of XML declaration or 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.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
// Skip until end of 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'
@@ -137,8 +139,8 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
return false;
}
while (pInput.read() != '<') {
// Skip over, until next begin tag
while ((pInput.readByte() & 0xFF) != '<') {
// Skip over, until next begin tag or EOF
}
}
}
@@ -147,6 +149,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
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));
}
}
}
}
@@ -120,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();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -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);
}
}
@@ -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();
}
}
@@ -76,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

+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -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");
@@ -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);
}
}
@@ -47,17 +47,30 @@ public final class YCbCrConverter {
buildYCCtoRGBtable();
}
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
double y = (yCbCr[offset] & 0xff);
double cb = (yCbCr[offset + 1] & 0xff) - 128;
double cr = (yCbCr[offset + 2] & 0xff) - 128;
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 - 2 * lumaRed) + y);
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
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);
@@ -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,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++) {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
@@ -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();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
@@ -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();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -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
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -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
}
}
@@ -62,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;
@@ -96,7 +96,7 @@ 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) {
@@ -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());
}
}
}
@@ -109,33 +109,7 @@ public class JPEGImageReader extends ImageReaderBase {
static final int ALL_APP_MARKERS = -1;
/** Segment identifiers for the JPEG segments we care about reading. */
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
private static Map<Integer, List<String>> createSegmentIds() {
Map<Integer, List<String>> map = new LinkedHashMap<>();
// Need all APP markers to be able to re-generate proper metadata later
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
map.put(appMarker, JPEGSegmentUtil.ALL_IDS);
}
// SOFn markers
map.put(JPEG.SOF0, null);
map.put(JPEG.SOF1, null);
map.put(JPEG.SOF2, null);
map.put(JPEG.SOF3, null);
map.put(JPEG.SOF5, null);
map.put(JPEG.SOF6, null);
map.put(JPEG.SOF7, null);
map.put(JPEG.SOF9, null);
map.put(JPEG.SOF10, null);
map.put(JPEG.SOF11, null);
map.put(JPEG.SOF13, null);
map.put(JPEG.SOF14, null);
map.put(JPEG.SOF15, null);
return Collections.unmodifiableMap(map);
}
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
/** Our JPEG reading delegate */
private final ImageReader delegate;
@@ -150,7 +124,7 @@ public class JPEGImageReader extends ImageReaderBase {
private JPEGImage10MetadataCleaner metadataCleaner;
/** Cached list of JPEG segments we filter from the underlying stream */
private List<JPEGSegment> segments;
private List<Segment> segments;
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
super(provider);
@@ -199,6 +173,12 @@ public class JPEGImageReader extends ImageReaderBase {
@Override
public int getNumImages(boolean allowSearch) throws IOException {
if (allowSearch) {
if (isLossless()) {
return 1;
}
}
try {
return delegate.getNumImages(allowSearch);
}
@@ -208,13 +188,46 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
private boolean isLossless() throws IOException {
assertInput();
try {
Frame sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return true;
}
}
catch (IIOException ignore) {
// May happen if no SOF is found, in case we'll just fall through
if (DEBUG) {
ignore.printStackTrace();
}
}
return false;
}
@Override
public int getWidth(int imageIndex) throws IOException {
checkBounds(imageIndex);
Frame sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return sof.samplesPerLine;
}
return delegate.getWidth(imageIndex);
}
@Override
public int getHeight(int imageIndex) throws IOException {
checkBounds(imageIndex);
Frame sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return sof.lines;
}
return delegate.getHeight(imageIndex);
}
@@ -275,7 +288,7 @@ public class JPEGImageReader extends ImageReaderBase {
return rawType;
}
}
catch (NullPointerException ignore) {
catch (IIOException | NullPointerException ignore) {
// Fall through
}
@@ -318,17 +331,17 @@ public class JPEGImageReader extends ImageReaderBase {
assertInput();
checkBounds(imageIndex);
SOFSegment sof = getSOF();
Frame sof = getSOF();
ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCTSegment adobeDCT = getAdobeDCT();
AdobeDCT adobeDCT = getAdobeDCT();
boolean bogusAdobeDCT = false;
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() != 3 ||
adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() != 4)) {
processWarningOccurred(String.format(
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " +
"Ignoring Adobe App14 marker.",
adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB",
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
sof.marker & 0xf, sof.componentsInFrame()
));
@@ -338,9 +351,16 @@ public class JPEGImageReader extends ImageReaderBase {
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
if (sof.marker == JPEG.SOF3) {
// TODO: What about stream position?
// TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
// Read image as lossless
return new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
}
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
if (delegate.canReadRaster() && (
else if (delegate.canReadRaster() && (
bogusAdobeDCT ||
sourceCSType == JPEGColorSpace.CMYK ||
sourceCSType == JPEGColorSpace.YCCK ||
@@ -352,7 +372,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
// TODO: Possible to optimize slightly, to avoid readAsRaster for non-CMYK and other good types?
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, ensureDisplayProfile(profile));
return readImageAsRasterAndReplaceColorProfile(imageIndex, param, sof, sourceCSType, profile);
}
if (DEBUG) {
@@ -362,7 +382,7 @@ public class JPEGImageReader extends ImageReaderBase {
return delegate.read(imageIndex, param);
}
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, SOFSegment startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
int origWidth = getWidth(imageIndex);
int origHeight = getHeight(imageIndex);
@@ -418,7 +438,10 @@ public class JPEGImageReader extends ImageReaderBase {
"Colors may look incorrect."
);
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
if (cmykCS != image.getColorModel().getColorSpace()) {
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
}
}
else {
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
@@ -494,7 +517,7 @@ public class JPEGImageReader extends ImageReaderBase {
return image;
}
static JPEGColorSpace getSourceCSType(JFIFSegment jfif, AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException {
static JPEGColorSpace getSourceCSType(JFIF jfif, AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
/*
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
@@ -536,27 +559,27 @@ public class JPEGImageReader extends ImageReaderBase {
*/
if (adobeDCT != null) {
switch (adobeDCT.getTransform()) {
case AdobeDCTSegment.YCC:
if (startOfFrame.components.length != 3) {
switch (adobeDCT.transform) {
case AdobeDCT.YCC:
if (startOfFrame.componentsInFrame() != 3) {
// This probably means the Adobe marker is bogus
break;
}
return JPEGColorSpace.YCbCr;
case AdobeDCTSegment.YCCK:
if (startOfFrame.components.length != 4) {
case AdobeDCT.YCCK:
if (startOfFrame.componentsInFrame() != 4) {
// This probably means the Adobe marker is bogus
break;
}
return JPEGColorSpace.YCCK;
case AdobeDCTSegment.Unknown:
if (startOfFrame.components.length == 1) {
case AdobeDCT.Unknown:
if (startOfFrame.componentsInFrame() == 1) {
return JPEGColorSpace.Gray;
}
else if (startOfFrame.components.length == 3) {
else if (startOfFrame.componentsInFrame() == 3) {
return JPEGColorSpace.RGB;
}
else if (startOfFrame.components.length == 4) {
else if (startOfFrame.componentsInFrame() == 4) {
return JPEGColorSpace.CMYK;
}
// Else fall through
@@ -565,7 +588,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
// TODO: We should probably allow component ids out of order (ie. BGR or KMCY)...
switch (startOfFrame.components.length) {
switch (startOfFrame.componentsInFrame()) {
case 1:
return JPEGColorSpace.Gray;
case 2:
@@ -583,7 +606,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
else {
// If subsampled, YCbCr else RGB
for (SOFComponent component : startOfFrame.components) {
for (Frame.Component component : startOfFrame.components) {
if (component.hSub != 1 || component.vSub != 1) {
return JPEGColorSpace.YCbCr;
}
@@ -611,7 +634,7 @@ public class JPEGImageReader extends ImageReaderBase {
else {
// TODO: JPEGMetadata (standard format) will report YCbCrA for 4 channel subsampled... :-/
// If subsampled, YCCK else CMYK
for (SOFComponent component : startOfFrame.components) {
for (Frame.Component component : startOfFrame.components) {
if (component.hSub != 1 || component.vSub != 1) {
return JPEGColorSpace.YCCK;
}
@@ -658,7 +681,27 @@ public class JPEGImageReader extends ImageReaderBase {
if (segments == null) {
long start = DEBUG ? System.currentTimeMillis() : 0;
readSegments();
// TODO: Consider just reading the segments directly, for better performance...
List<JPEGSegment> jpegSegments = readSegments();
List<Segment> segments = new ArrayList<>(jpegSegments.size());
for (JPEGSegment segment : jpegSegments) {
try (DataInputStream data = new DataInputStream(segment.segmentData())) {
segments.add(Segment.read(segment.marker(), segment.identifier(), segment.segmentLength(), data));
}
catch (IOException e) {
// TODO: Handle bad segments better, for now, just ignore any bad APP markers
if (segment.marker() >= JPEG.APP0 && JPEG.APP15 >= segment.marker()) {
processWarningOccurred("Bogus APP" + (segment.marker() & 0x0f) + "/" + segment.identifier() + " segment, ignoring");
continue;
}
throw e;
}
}
this.segments = segments;
if (DEBUG) {
System.out.println("Read metadata in " + (System.currentTimeMillis() - start) + " ms");
@@ -666,13 +709,13 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
private void readSegments() throws IOException {
private List<JPEGSegment> readSegments() throws IOException {
imageInput.mark();
try {
imageInput.seek(0); // TODO: Seek to wanted image, skip images on the way
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
}
catch (IIOException | IllegalArgumentException ignore) {
if (DEBUG) {
@@ -684,125 +727,62 @@ public class JPEGImageReader extends ImageReaderBase {
}
// In case of an exception, avoid NPE when referencing segments later
if (segments == null) {
segments = Collections.emptyList();
}
return Collections.emptyList();
}
List<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
List<Application> getAppSegments(final int marker, final String identifier) throws IOException {
initHeader();
List<JPEGSegment> appSegments = Collections.emptyList();
List<Application> appSegments = Collections.emptyList();
for (JPEGSegment segment : segments) {
if ((marker == ALL_APP_MARKERS && segment.marker() >= JPEG.APP0 && segment.marker() <= JPEG.APP15 || segment.marker() == marker)
&& (identifier == null || identifier.equals(segment.identifier()))) {
for (Segment segment : segments) {
if (segment instanceof Application
&& (marker == ALL_APP_MARKERS || marker == segment.marker)
&& (identifier == null || identifier.equals(((Application) segment).identifier))) {
if (appSegments == Collections.EMPTY_LIST) {
appSegments = new ArrayList<>(segments.size());
}
appSegments.add(segment);
appSegments.add((Application) segment);
}
}
return appSegments;
}
SOFSegment getSOF() throws IOException {
Frame getSOF() throws IOException {
initHeader();
for (JPEGSegment segment : segments) {
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 ||
JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 ||
JPEG.SOF9 >= segment.marker() && segment.marker() <= JPEG.SOF11 ||
JPEG.SOF13 >= segment.marker() && segment.marker() <= JPEG.SOF15) {
DataInputStream data = new DataInputStream(segment.data());
try {
int samplePrecision = data.readUnsignedByte();
int lines = data.readUnsignedShort();
int samplesPerLine = data.readUnsignedShort();
int componentsInFrame = data.readUnsignedByte();
SOFComponent[] components = new SOFComponent[componentsInFrame];
for (int i = 0; i < componentsInFrame; i++) {
int id = data.readUnsignedByte();
int sub = data.readUnsignedByte();
int qtSel = data.readUnsignedByte();
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
}
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
}
finally {
data.close();
}
for (Segment segment : segments) {
if (segment instanceof Frame) {
return (Frame) segment;
}
}
throw new IIOException("No SOF segment in stream");
}
AdobeDCTSegment getAdobeDCT() throws IOException {
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
List<JPEGSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
if (!adobe.isEmpty()) {
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
DataInputStream stream = new DataInputStream(adobe.get(0).data());
return new AdobeDCTSegment(
stream.readUnsignedByte(),
stream.readUnsignedShort(),
stream.readUnsignedShort(),
stream.readUnsignedByte()
);
}
return null;
AdobeDCT getAdobeDCT() throws IOException {
List<Application> adobe = getAppSegments(JPEG.APP14, "Adobe");
return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0);
}
JFIFSegment getJFIF() throws IOException{
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
JFIF getJFIF() throws IOException{
List<Application> jfif = getAppSegments(JPEG.APP0, "JFIF");
return jfif.isEmpty() ? null : (JFIF) jfif.get(0);
if (!jfif.isEmpty()) {
JPEGSegment segment = jfif.get(0);
if (segment.length() >= 9) {
return JFIFSegment.read(segment.data());
}
else {
processWarningOccurred("Bogus JFIF segment, ignoring");
}
}
return null;
}
JFXXSegment getJFXX() throws IOException {
List<JPEGSegment> jfxx = getAppSegments(JPEG.APP0, "JFXX");
if (!jfxx.isEmpty()) {
JPEGSegment segment = jfxx.get(0);
if (segment.length() >= 1) {
return JFXXSegment.read(segment.data(), segment.length());
}
else {
processWarningOccurred("Bogus JFXX segment, ignoring");
}
}
return null;
JFXX getJFXX() throws IOException {
List<Application> jfxx = getAppSegments(JPEG.APP0, "JFXX");
return jfxx.isEmpty() ? null : (JFXX) jfxx.get(0);
}
private CompoundDirectory getExif() throws IOException {
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
List<Application> exifSegments = getAppSegments(JPEG.APP1, "Exif");
if (!exifSegments.isEmpty()) {
JPEGSegment exif = exifSegments.get(0);
Application exif = exifSegments.get(0);
InputStream data = exif.data();
if (data.read() == -1) { // Read pad
@@ -838,11 +818,13 @@ public class JPEGImageReader extends ImageReaderBase {
// TODO: Allow metadata to contain the wrongly indexed profiles, if readable
// NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
List<Application> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
// TODO: Possibly move this logic to the ICCProfile class...
if (segments.size() == 1) {
// Faster code for the common case
JPEGSegment segment = segments.get(0);
Application segment = segments.get(0);
DataInputStream stream = new DataInputStream(segment.data());
int chunkNumber = stream.readUnsignedByte();
int chunkCount = stream.readUnsignedByte();
@@ -909,7 +891,8 @@ public class JPEGImageReader extends ImageReaderBase {
try {
ICC_Profile profile = ICC_Profile.getInstance(stream);
return allowBadProfile ? profile : ColorSpaces.validateProfile(profile);
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
}
catch (RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
@@ -925,7 +908,15 @@ public class JPEGImageReader extends ImageReaderBase {
}
@Override
public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException {
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
checkBounds(imageIndex);
if (isLossless()) {
// TODO: What about stream position?
// TODO: Param handling: Reading as raster should support source region, subsampling etc.
return new JPEGLosslessDecoderWrapper(this).readRaster(segments, imageInput);
}
return delegate.readRaster(imageIndex, param);
}
@@ -959,18 +950,18 @@ public class JPEGImageReader extends ImageReaderBase {
ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate();
// Read JFIF thumbnails if present
JFIFSegment jfif = getJFIF();
JFIF jfif = getJFIF();
if (jfif != null && jfif.thumbnail != null) {
thumbnails.add(new JFIFThumbnailReader(thumbnailProgressDelegator, imageIndex, thumbnails.size(), jfif));
}
// Read JFXX thumbnails if present
JFXXSegment jfxx = getJFXX();
JFXX jfxx = getJFXX();
if (jfxx != null && jfxx.thumbnail != null) {
switch (jfxx.extensionCode) {
case JFXXSegment.JPEG:
case JFXXSegment.INDEXED:
case JFXXSegment.RGB:
case JFXX.JPEG:
case JFXX.INDEXED:
case JFXX.RGB:
thumbnails.add(new JFXXThumbnailReader(thumbnailProgressDelegator, getThumbnailReader(), imageIndex, thumbnails.size(), jfxx));
break;
default:
@@ -979,9 +970,9 @@ public class JPEGImageReader extends ImageReaderBase {
}
// Read Exif thumbnails if present
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
List<Application> exifSegments = getAppSegments(JPEG.APP1, "Exif");
if (!exifSegments.isEmpty()) {
JPEGSegment exif = exifSegments.get(0);
Application exif = exifSegments.get(0);
InputStream data = exif.data();
if (data.read() == -1) {
@@ -1066,24 +1057,29 @@ public class JPEGImageReader extends ImageReaderBase {
IIOMetadata imageMetadata;
try {
imageMetadata = delegate.getImageMetadata(imageIndex);
if (isLossless()) {
return new JPEGImage10Metadata(segments);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
}
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) {
metadataCleaner = new JPEGImage10MetadataCleaner(this);
else {
try {
imageMetadata = delegate.getImageMetadata(imageIndex);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
}
return metadataCleaner.cleanMetadata(imageMetadata);
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) {
metadataCleaner = new JPEGImage10MetadataCleaner(this);
}
return metadataCleaner.cleanMetadata(imageMetadata);
}
}
return imageMetadata;
@@ -1367,7 +1363,8 @@ public class JPEGImageReader extends ImageReaderBase {
image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);
}
else {
image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
// image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
image = null;
}
param.setDestination(image);
@@ -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;
}
}
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2016, Harald Kuhr
* Copyright (c) 2016, Herman Kroll
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferUShort;
import java.awt.image.Raster;
import java.io.IOException;
import java.util.List;
/**
* This class provides the conversion of input data
* containing a JPEG Lossless to an BufferedImage.
* <p>
* Take care, that only the following lossless formats are supported:
* 1.2.840.10008.1.2.4.57 JPEG Lossless, Nonhierarchical (Processes 14)
* 1.2.840.10008.1.2.4.70 JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
* <p>
* Currently the following conversions are supported
* - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @author Hermann Kroll
*/
final class JPEGLosslessDecoderWrapper {
private final JPEGImageReader listenerDelegate;
JPEGLosslessDecoderWrapper(final JPEGImageReader listenerDelegate) {
this.listenerDelegate = listenerDelegate;
}
/**
* Decodes a JPEG Lossless stream to a {@code BufferedImage}.
* Currently the following conversions are supported:
* - 24Bit, RGB -> BufferedImage.TYPE_3BYTE_BGR
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @param segments segments
* @param input input stream which contains a jpeg lossless data
* @return if successfully a BufferedImage is returned
* @throws IOException is thrown if the decoder failed or a conversion is not supported
*/
BufferedImage readImage(final List<Segment> segments, final ImageInputStream input) throws IOException {
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input), listenerDelegate);
// TODO: Allow 10/12/14 bit (using a ComponentColorModel with correct bits, as in TIFF)
// TODO: Rewrite this to pass a pre-allocated buffer of correct type (byte/short)/correct bands
// TODO: Progress callbacks
// TODO: Warning callbacks
// Callback can then do subsampling etc.
int[][] decoded = decoder.decode();
int width = decoder.getDimX();
int height = decoder.getDimY();
// Single component, assumed to be Gray
if (decoder.getNumComponents() == 1) {
switch (decoder.getPrecision()) {
case 8:
return to8Bit1ComponentGrayScale(decoded, width, height);
case 16:
return to16Bit1ComponentGrayScale(decoded, width, height);
}
}
// 3 components, assumed to be RGB
else if (decoder.getNumComponents() == 3) {
switch (decoder.getPrecision()) {
case 8:
return to24Bit3ComponentRGB(decoded, width, height);
}
}
throw new IIOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) not supported");
}
private ImageInputStream createBufferedInput(final ImageInputStream input) throws IOException {
return input instanceof BufferedImageInputStream ? input : new BufferedImageInputStream(input);
}
Raster readRaster(final List<Segment> segments, final ImageInputStream input) throws IOException {
// TODO: Can perhaps be implemented faster
return readImage(segments, input).getRaster();
}
/**
* Converts the decoded buffer into a BufferedImage.
* precision: 16 bit, componentCount = 1
*
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_USHORT_GRAY
*/
private BufferedImage to16Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
imageBuffer[i] = (short) decoded[0][i];
}
return image;
}
/**
* Converts the decoded buffer into a BufferedImage.
* precision: 8 bit, componentCount = 1
*
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_BYTE_GRAY
*/
private BufferedImage to8Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
imageBuffer[i] = (byte) decoded[0][i];
}
return image;
}
/**
* Converts the decoded buffer into a BufferedImage.
* precision: 8 bit, componentCount = 3
*
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_3BYTE_RGB
*/
private BufferedImage to24Bit3ComponentRGB(int[][] decoded, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length / 3; i++) {
// Convert to RGB (BGR)
imageBuffer[i * 3 + 2] = (byte) decoded[0][i];
imageBuffer[i * 3 + 1] = (byte) decoded[1][i];
imageBuffer[i * 3] = (byte) decoded[2][i];
}
return image;
}
}
@@ -38,7 +38,7 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
* @version $Id: JPEGProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
*/
final class JPEGProviderInfo extends ReaderWriterProviderInfo {
protected JPEGProviderInfo() {
JPEGProviderInfo() {
super(
JPEGProviderInfo.class,
new String[] {"JPEG", "jpeg", "JPG", "jpg"},
@@ -240,16 +240,21 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
private void streamInit() throws IOException {
stream.seek(0);
int soi = stream.readUnsignedShort();
if (soi != JPEG.SOI) {
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
}
else {
try {
int soi = stream.readUnsignedShort();
if (soi != JPEG.SOI) {
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
}
segment = new Segment(soi, 0, 0, 2);
segments.add(segment);
currentSegment = segments.size() - 1; // 0
}
catch (EOFException eof) {
throw new IIOException(String.format("Not a JPEG stream (short stream. expected SOI: 0x%04x)", JPEG.SOI), eof);
}
}
static boolean isAppSegmentMarker(final int marker) {
@@ -356,7 +361,6 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
return stream.read(b, off, len);
}
@Override
public String toString() {
return String.format("0x%04x[%d-%d]", marker, realStart, realEnd());
@@ -404,7 +408,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
byte[] replacementData = new byte[length];
int numQTs = length / 128;
int newSegmentLength = 2 + 1 + 64 * numQTs;
int newSegmentLength = 2 + (1 + 64) * numQTs; // Len + (qtInfo + qtSize) * numQTs
replacementData[0] = (byte) ((JPEG.DQT >> 8) & 0xff);
replacementData[1] = (byte) (JPEG.DQT & 0xff);
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2016, Harald Kuhr
* Copyright (C) 2015, Michael Martinez
* Copyright (C) 2004, Helmut Dersch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.IOException;
final class QuantizationTable extends Segment {
private final int precision[] = new int[4]; // Quantization precision 8 or 16
private final int[] tq = new int[4]; // 1: this table is presented
final int quantTables[][] = new int[4][64]; // Tables
QuantizationTable() {
super(JPEG.DQT);
tq[0] = 0;
tq[1] = 0;
tq[2] = 0;
tq[3] = 0;
}
// TODO: Get rid of table param, make it a member?
void enhanceTables(final int[] table) throws IOException {
for (int t = 0; t < 4; t++) {
if (tq[t] != 0) {
enhanceQuantizationTable(quantTables[t], table);
}
}
}
private void enhanceQuantizationTable(final int qtab[], final int[] table) {
for (int i = 0; i < 8; i++) {
qtab[table[ i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90;
qtab[table[(2 * 8) + i]] *= 118;
qtab[table[(6 * 8) + i]] *= 49;
qtab[table[(5 * 8) + i]] *= 71;
qtab[table[( 8) + i]] *= 126;
qtab[table[(7 * 8) + i]] *= 25;
qtab[table[(3 * 8) + i]] *= 106;
}
for (int i = 0; i < 8; i++) {
qtab[table[( 8 * i)]] *= 90;
qtab[table[4 + (8 * i)]] *= 90;
qtab[table[2 + (8 * i)]] *= 118;
qtab[table[6 + (8 * i)]] *= 49;
qtab[table[5 + (8 * i)]] *= 71;
qtab[table[1 + (8 * i)]] *= 126;
qtab[table[7 + (8 * i)]] *= 25;
qtab[table[3 + (8 * i)]] *= 106;
}
for (int i = 0; i < 64; i++) {
qtab[i] >>= 6;
}
}
@Override
public String toString() {
// TODO: Tables...
return "DQT[]";
}
public static QuantizationTable read(final DataInput data, final int length) throws IOException {
int count = 2;
QuantizationTable table = new QuantizationTable();
while (count < length) {
final int temp = data.readUnsignedByte();
count++;
final int t = temp & 0x0F;
if (t > 3) {
throw new IIOException("Unexpected JPEG Quantization Table Id (> 3): " + t);
}
table.precision[t] = temp >> 4;
if (table.precision[t] == 0) {
table.precision[t] = 8;
}
else if (table.precision[t] == 1) {
table.precision[t] = 16;
}
else {
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
}
table.tq[t] = 1;
if (table.precision[t] == 8) {
for (int i = 0; i < 64; i++) {
if (count > length) {
throw new IIOException("JPEG Quantization Table format error");
}
table.quantTables[t][i] = data.readUnsignedByte();
count++;
}
}
else {
for (int i = 0; i < 64; i++) {
if (count > length) {
throw new IIOException("JPEG Quantization Table format error");
}
table.quantTables[t][i] = data.readUnsignedShort();
count += 2;
}
}
}
if (count != length) {
throw new IIOException("JPEG Quantization Table error, bad segment length: " + length);
}
return table;
}
}
@@ -0,0 +1,64 @@
/*
* 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 javax.imageio.IIOException;
import java.io.DataInput;
import java.io.IOException;
/**
* RestartInterval.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: RestartInterval.java,v 1.0 24/08/16 harald.kuhr Exp$
*/
class RestartInterval extends Segment {
final int interval;
private RestartInterval(int interval) {
super(JPEG.DRI);
this.interval = interval;
}
@Override
public String toString() {
return "DRI[" + interval + "]";
}
public static RestartInterval read(final DataInput data, final int length) throws IOException {
if (length != 4) {
throw new IIOException("Unexpected length of DRI segment: " + length);
}
return new RestartInterval(data.readUnsignedShort());
}
}
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.util.Arrays;
final class Scan extends Segment {
final int spectralSelStart; // Start of spectral or predictor selection
final int spectralSelEnd; // End of spectral selection
final int approxHigh;
final int approxLow;
final Component[] components;
Scan(final Component[] components, final int spectralStart, final int spectralSelEnd, final int approxHigh, final int approxLow) {
super(JPEG.SOS);
this.components = components;
this.spectralSelStart = spectralStart;
this.spectralSelEnd = spectralSelEnd;
this.approxHigh = approxHigh;
this.approxLow = approxLow;
}
@Override
public String toString() {
return String.format(
"SOS[spectralSelStart: %d, spectralSelEnd: %d, approxHigh: %d, approxLow: %d, components: %s]",
spectralSelStart, spectralSelEnd, approxHigh, approxLow, Arrays.toString(components)
);
}
public static Scan read(final ImageInputStream data) throws IOException {
int length = data.readUnsignedShort();
return read(new SubImageInputStream(data, length), length);
}
public static Scan read(final DataInput data, final int length) throws IOException {
int numComp = data.readUnsignedByte();
int expected = 6 + numComp * 2;
if (expected != length) {
throw new IIOException(String.format("Unexpected SOS length: %d != %d", length, expected));
}
Component[] components = new Component[numComp];
for (int i = 0; i < numComp; i++) {
int scanCompSel = data.readUnsignedByte();
final int temp = data.readUnsignedByte();
components[i] = new Component(scanCompSel, temp & 0x0F, temp >> 4);
}
int selection = data.readUnsignedByte();
int spectralEnd = data.readUnsignedByte();
int temp = data.readUnsignedByte();
return new Scan(components, selection, spectralEnd, temp >> 4, temp & 0x0F);
}
public final static class Component {
final int scanCompSel; // Scan component selector
final int acTabSel; // AC table selector
final int dcTabSel; // DC table selector
Component(final int scanCompSel, final int acTabSel, final int dcTabSel) {
this.scanCompSel = scanCompSel;
this.acTabSel = acTabSel;
this.dcTabSel = dcTabSel;
}
@Override
public String toString() {
return String.format("scanCompSel: %d, acTabSel: %d, dcTabSel: %d", scanCompSel, acTabSel, dcTabSel);
}
}
}
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2016, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.lang.Validate;
import java.io.DataInput;
import java.io.IOException;
/**
* Segment.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: Segment.java,v 1.0 22/08/16 harald.kuhr Exp$
*/
abstract class Segment {
final int marker;
protected Segment(final int marker) {
this.marker = Validate.isTrue(marker >> 8 == 0xFF, marker, "Unknown JPEG marker: 0x%04x");
}
static Segment read(int marker, String identifier, int length, DataInput data) throws IOException {
switch (marker) {
case JPEG.DHT:
return HuffmanTable.read(data, length);
case JPEG.DQT:
return QuantizationTable.read(data, length);
case JPEG.SOF0:
case JPEG.SOF1:
case JPEG.SOF2:
case JPEG.SOF3:
case JPEG.SOF5:
case JPEG.SOF6:
case JPEG.SOF7:
case JPEG.SOF9:
case JPEG.SOF10:
case JPEG.SOF11:
case JPEG.SOF13:
case JPEG.SOF14:
case JPEG.SOF15:
return Frame.read(marker, data, length);
case JPEG.SOS:
return Scan.read(data, length);
case JPEG.COM:
return Comment.read(data, length);
// TODO: JPEG.DAC
case JPEG.DRI:
return RestartInterval.read(data, length);
case JPEG.APP0:
case JPEG.APP1:
case JPEG.APP2:
case JPEG.APP3:
case JPEG.APP4:
case JPEG.APP5:
case JPEG.APP6:
case JPEG.APP7:
case JPEG.APP8:
case JPEG.APP9:
case JPEG.APP10:
case JPEG.APP11:
case JPEG.APP12:
case JPEG.APP13:
case JPEG.APP14:
case JPEG.APP15:
return Application.read(marker, identifier, data, length);
default:
return Unknown.read(marker, length, data);
}
}
}
@@ -42,7 +42,6 @@ import java.io.IOException;
* @author last modified by $Author: haraldk$
* @version $Id: ThumbnailReader.java,v 1.0 18.04.12 12:22 haraldk Exp$
*/
// TODO: Get rid of the com.sun import!!
abstract class ThumbnailReader {
private final ThumbnailReadProgressListener progressListener;
@@ -68,19 +67,9 @@ abstract class ThumbnailReader {
}
static protected BufferedImage readJPEGThumbnail(final ImageReader reader, final ImageInputStream stream) throws IOException {
// try {
// try {
reader.setInput(stream);
reader.setInput(stream);
return reader.read(0);
// }
// finally {
// input.close();
// }
// }
// finally {
// reader.dispose();
// }
return reader.read(0);
}
static protected BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) {
@@ -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,32 +28,34 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import java.io.Serializable;
import java.io.DataInput;
import java.io.IOException;
/**
* SOFComponent
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: SOFComponent.java,v 1.0 22.04.13 16:40 haraldk Exp$
*/
final class SOFComponent {
final int id;
final int hSub;
final int vSub;
final int qtSel;
* Represents an unknown segment in the JPEG stream.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: Unknown.java,v 1.0 22/08/16 harald.kuhr Exp$
*/
final class Unknown extends Segment {
final byte[] data;
SOFComponent(int id, int hSub, int vSub, int qtSel) {
this.id = id;
this.hSub = hSub;
this.vSub = vSub;
this.qtSel = qtSel;
private Unknown(final int marker, final byte[] data) {
super(marker);
this.data = data;
}
@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);
return String.format("Unknown[%04x, length: %d]", marker, data.length);
}
public static Segment read(int marker, int length, DataInput data) throws IOException {
byte[] bytes = new byte[length - 2];
data.readFully(bytes);
return new Unknown(marker, bytes);
}
}
@@ -36,6 +36,7 @@ import org.mockito.InOrder;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
@@ -60,7 +61,8 @@ public class JFIFThumbnailReaderTest extends AbstractThumbnailReaderTest {
assertNotNull(segments);
assertFalse(segments.isEmpty());
return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIFSegment.read(segments.get(0).data()));
JPEGSegment segment = segments.get(0);
return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIF.read(new DataInputStream(segment.segmentData()), segment.segmentLength()));
}
@Test
@@ -37,6 +37,7 @@ import org.mockito.InOrder;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.List;
@@ -64,7 +65,7 @@ public class JFXXThumbnailReaderTest extends AbstractThumbnailReaderTest {
assertFalse(segments.isEmpty());
JPEGSegment jfxx = segments.get(0);
return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXXSegment.read(jfxx.data(), jfxx.length()));
return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXX.read(new DataInputStream(jfxx.segmentData()), jfxx.length()));
}
@Test
@@ -0,0 +1,96 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* JPEGImage10MetadataCleanerTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGImage10MetadataCleanerTest.java,v 1.0 08/08/16 harald.kuhr Exp$
*/
public class JPEGImage10MetadataCleanerTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
protected static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
protected static ImageReaderSpi lookupDelegateProvider() {
return JPEGImageReaderSpi.lookupDelegateProvider(IIORegistry.getDefaultInstance());
}
// Unit/regression test for #276
@Test
public void cleanMetadataMoreThan4DHTSegments() throws Exception {
List<String> testData = Arrays.asList("/jpeg/5dhtsegments.jpg", "/jpeg/6dhtsegments.jpg");
for (String data : testData) {
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClass().getResource(data));
ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(data))) {
ImageReader origReader = SPI.delegateProvider.createReaderInstance();
origReader.setInput(origInput);
ImageReader reader = SPI.createReaderInstance();
reader.setInput(input);
IIOMetadata original = origReader.getImageMetadata(0);
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0));
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList origDHT = origTree.getElementsByTagName("dht");
assertEquals(1, origDHT.getLength());
NodeList cleanDHT = cleanTree.getElementsByTagName("dht");
assertEquals(2, cleanDHT.getLength());
NodeList cleanDHTable = cleanTree.getElementsByTagName("dhtable");
NodeList origDHTable = origTree.getElementsByTagName("dhtable");
assertEquals(origDHTable.getLength(), cleanDHTable.getLength());
// Note: This also tests that the order of the htables are the same,
// but that will only hold if they are already sorted by class.
// Luckily, they are in these cases...
for (int i = 0; i < origDHTable.getLength(); i++) {
Element cleanDHTableElem = (Element) cleanDHTable.item(i);
Element origDHTableElem = (Element) origDHTable.item(i);
assertNotNull(cleanDHTableElem);
assertNotNull(cleanDHTableElem.getAttribute("class"));
assertEquals(origDHTableElem.getAttribute("class"), cleanDHTableElem.getAttribute("class"));
assertNotNull(cleanDHTableElem.getAttribute("htableId"));
assertEquals(origDHTableElem.getAttribute("htableId"), cleanDHTableElem.getAttribute("htableId"));
}
reader.dispose();
origReader.dispose();
}
}
}
}
@@ -94,7 +94,14 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)),
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131))
new TestData(getClassLoaderResource("/jpeg/app-marker-missing-null-term.jpg"), new Dimension(200, 150)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
new TestData(getClassLoaderResource("/jpeg-lossless/16_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 16 bit
new TestData(getClassLoaderResource("/jpeg-lossless/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/f-18.jpg"), new Dimension(320, 240)), // Lossless RGB, 3 DHTs
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512)) // Lossless gray, 16 bit
);
// More test data in specific tests below
@@ -895,6 +902,76 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
reader.dispose();
}
@Test
public void testReadCMYKAsCMYKSameRGBasRGB() throws IOException {
// Make sure CMYK images can be read and still contain their original (embedded) color profile
JPEGImageReader reader = createReader();
// NOTE: Data without ICC profile won't work in this test, as we might end up
// using the non-ICC color conversion case and fail miserably on the CI server.
List<TestData> cmykData = Arrays.asList(
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(100, 100)),
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-custom-icc-bright.jpg"), new Dimension(100, 100))
);
for (TestData data : cmykData) {
reader.setInput(data.getInputStream());
Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
assertTrue(data + " has no image types", types.hasNext());
ImageTypeSpecifier cmykType = null;
ImageTypeSpecifier rgbType = null;
while (types.hasNext()) {
ImageTypeSpecifier type = types.next();
int csType = type.getColorModel().getColorSpace().getType();
if (rgbType == null && csType == ColorSpace.TYPE_RGB) {
rgbType = type;
}
else if (cmykType == null && csType == ColorSpace.TYPE_CMYK) {
cmykType = type;
}
if (rgbType != null && cmykType != null) {
break;
}
}
assertNotNull("No RGB types for " + data, rgbType);
assertNotNull("No CMYK types for " + data, cmykType);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(reader.getWidth(0), 8)); // We don't really need to read it all
param.setDestinationType(cmykType);
BufferedImage imageCMYK = reader.read(0, param);
param.setDestinationType(rgbType);
BufferedImage imageRGB = reader.read(0, param);
assertNotNull(imageCMYK);
assertEquals(ColorSpace.TYPE_CMYK, imageCMYK.getColorModel().getColorSpace().getType());
assertNotNull(imageRGB);
assertEquals(ColorSpace.TYPE_RGB, imageRGB.getColorModel().getColorSpace().getType());
for (int y = 0; y < imageCMYK.getHeight(); y++) {
for (int x = 0; x < imageCMYK.getWidth(); x++) {
int cmykAsRGB = imageCMYK.getRGB(x, y);
int rgb = imageRGB.getRGB(x, y);
if (rgb != cmykAsRGB) {
assertRGBEquals(String.format("Diff at [%d, %d]", x, y), rgb, cmykAsRGB, 2);
}
}
}
}
reader.dispose();
}
@Test
public void testReadNoJFIFYCbCr() throws IOException {
// Basically the same issue as http://stackoverflow.com/questions/9340569/jpeg-image-with-wrong-colors
@@ -929,9 +1006,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
* Slightly fuzzy RGB equals method. Tolerance +/-5 steps.
*/
private void assertRGBEquals(int expectedRGB, int actualRGB) {
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, 5);
assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, 5);
assertEquals((expectedRGB ) & 0xff, (actualRGB ) & 0xff, 5);
assertRGBEquals("RGB values differ", expectedRGB, actualRGB, 5);
}
// Regression: Test subsampling offset within of bounds
@@ -963,8 +1038,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
@@ -980,8 +1055,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
@@ -997,8 +1072,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
@@ -1027,8 +1102,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
@@ -1044,8 +1119,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
assertNotNull(image);
// Make sure correct color is actually read, not just left empty
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfefefd, image.getRGB(0, image.getHeight() - 1));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 2));
assertRGBEquals(0xfffefefd, image.getRGB(0, image.getHeight() - 1));
}
@Test
@@ -1107,6 +1182,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
}
catch (IIOException e) {
e.printStackTrace();
fail(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()));
}
}
@@ -0,0 +1,19 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/**
* JPEGProviderInfoTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
*/
public class JPEGProviderInfoTest extends ReaderWriterProviderInfoTest {
@Override
protected ReaderWriterProviderInfo createProviderInfo() {
return new JPEGProviderInfo();
}
}
@@ -74,6 +74,24 @@ public class JPEGSegmentImageInputStreamTest {
stream.read();
}
@Test(expected = IIOException.class)
public void testStreamNonJPEGArray() throws IOException {
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[] {42, 42, 0, 0, 77, 99})));
stream.readFully(new byte[1]);
}
@Test(expected = IIOException.class)
public void testStreamEmpty() throws IOException {
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
stream.read();
}
@Test(expected = IIOException.class)
public void testStreamEmptyArray() throws IOException {
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
stream.readFully(new byte[1]);
}
@Test
public void testStreamRealData() throws IOException {
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg")));
Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.2.2-SNAPSHOT</version>
<version>3.3.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -102,7 +102,7 @@ final class EXIFEntry extends AbstractEntry {
case TIFF.TAG_ORIENTATION:
return "Orientation";
case TIFF.TAG_SAMPLES_PER_PIXEL:
return "SamplesPerPixels";
return "SamplesPerPixel";
case TIFF.TAG_ROWS_PER_STRIP:
return "RowsPerStrip";
case TIFF.TAG_STRIP_BYTE_COUNTS:
@@ -71,7 +71,7 @@ public final class EXIFReader extends MetadataReader {
else if (bom[0] == 'M' && bom[1] == 'M') {
input.setByteOrder(ByteOrder.BIG_ENDIAN);
}
else {
else {
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
}
@@ -79,7 +79,7 @@ public final class EXIFReader extends MetadataReader {
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
int magic = input.readUnsignedShort();
if (magic != TIFF.TIFF_MAGIC) {
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
}
long directoryOffset = input.readUnsignedInt();
@@ -105,21 +105,27 @@ public final class EXIFReader extends MetadataReader {
}
for (int i = 0; i < entryCount; i++) {
EXIFEntry entry = readEntry(pInput);
try {
EXIFEntry entry = readEntry(pInput);
if (entry == null) {
// System.err.println("Expected: " + entryCount + " values, found only " + i);
// TODO: Log warning?
nextOffset = 0;
if (entry != null) {
entries.add(entry);
}
}
catch (IIOException e) {
break;
}
entries.add(entry);
}
if (readLinked) {
if (nextOffset == -1) {
nextOffset = pInput.readUnsignedInt();
try {
nextOffset = pInput.readUnsignedInt();
}
catch (EOFException e) {
// catch EOF here as missing EOF marker
nextOffset = 0;
}
}
// Read linked IFDs
@@ -138,7 +144,7 @@ public final class EXIFReader extends MetadataReader {
);
ifds.add(0, new IFD(entries));
return new EXIFDirectory(ifds);
}
@@ -170,7 +176,7 @@ public final class EXIFReader extends MetadataReader {
// Replace the entry with parsed data
entries.set(i, new EXIFEntry(tagId, subIFDs.get(0), entry.getType()));
}
else {
else {
// Replace the entry with parsed data
entries.set(i, new EXIFEntry(tagId, subIFDs.toArray(new IFD[subIFDs.size()]), entry.getType()));
}
@@ -207,7 +213,9 @@ public final class EXIFReader extends MetadataReader {
offsets = (long[]) value;
}
else {
throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
throw new IIOException(String.format("Unknown pointer type: %s", (value != null
? value.getClass()
: null)));
}
return offsets;
@@ -218,11 +226,6 @@ public final class EXIFReader extends MetadataReader {
int tagId = pInput.readUnsignedShort();
short type = pInput.readShort();
// This isn't really an entry, and the directory entry count was wrong OR bad data...
if (tagId == 0 && type == 0) {
return null;
}
int count = pInput.readInt(); // Number of values
// It's probably a spec violation to have count 0, but we'll be lenient about it
@@ -231,32 +234,33 @@ public final class EXIFReader extends MetadataReader {
}
if (type <= 0 || type > 13) {
pInput.skipBytes(4); // read Value
// Invalid tag, this is just for debugging
long offset = pInput.getStreamPosition() - 8l;
long offset = pInput.getStreamPosition() - 12l;
if (DEBUG) {
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
System.err.println("type: " + type + " (INVALID)");
System.err.println("count: " + count);
}
pInput.mark();
pInput.seek(offset);
pInput.mark();
pInput.seek(offset);
try {
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
int len = pInput.read(bytes);
try {
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
int len = pInput.read(bytes);
if (DEBUG) {
System.err.print(HexDump.dump(offset, bytes, 0, len));
System.err.println(len < count ? "[...]" : "");
if (DEBUG) {
System.err.print(HexDump.dump(offset, bytes, 0, len));
System.err.println(len < count ? "[...]" : "");
}
}
finally {
pInput.reset();
}
}
finally {
pInput.reset();
}
return null;
}
@@ -504,7 +508,8 @@ public final class EXIFReader extends MetadataReader {
//////////////////////
// TODO: Stream based hex dump util?
public static class HexDump {
private HexDump() {}
private HexDump() {
}
private static final int WIDTH = 32;
@@ -518,7 +523,7 @@ public final class EXIFReader extends MetadataReader {
int i;
for (i = 0; i < len; i++) {
if (i % WIDTH == 0) {
if (i > 0 ) {
if (i > 0) {
builder.append("\n");
}
builder.append(String.format("%08x: ", i + off + offset));
@@ -158,6 +158,10 @@ public final class EXIFWriter extends MetadataWriter {
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
}
public long computeIFDOffsetSize(final Collection<Entry> directory) {
return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
}
private long computeDataSize(final Directory directory) {
long dataSize = 0;
@@ -49,6 +49,22 @@ public interface JPEG {
/** Define Huffman Tables segment marker (DHT). */
int DHT = 0xFFC4;
/** Comment (COM) */
int COM = 0xFFFE;
/** Define Number of Lines (DNL). */
int DNL = 0xFFDC;
/** Define Restart Interval (DRI). */
int DRI = 0xFFDD;
/** Define Hierarchical Progression (DHP). */
int DHP = 0xFFDE;
/** Expand reference components (EXP). */
int EXP = 0xFFDF;
/** Temporary use in arithmetic coding (TEM). */
int TEM = 0xFF01;
/** Define Define Arithmetic Coding conditioning (DAC). */
int DAC = 0xFFCC;
// App segment markers (APPn).
int APP0 = 0xFFE0;
int APP1 = 0xFFE1;
@@ -43,7 +43,7 @@ import java.util.Arrays;
public final class JPEGSegment implements Serializable {
final int marker;
final byte[] data;
final int length;
private final int length;
private transient String id;
@@ -53,11 +53,15 @@ public final class JPEGSegment implements Serializable {
this.length = length;
}
int segmentLength() {
public int segmentLength() {
// This is the length field as read from the stream
return length;
}
public InputStream segmentData() {
return data != null ? new ByteArrayInputStream(data) : null;
}
public int marker() {
return marker;
}
@@ -106,7 +106,7 @@ public final class JPEGSegmentUtil {
if (isRequested(segment, segmentIdentifiers)) {
if (segments == Collections.EMPTY_LIST) {
segments = new ArrayList<JPEGSegment>();
segments = new ArrayList<>();
}
segments.add(segment);

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