Compare commits

...

446 Commits

Author SHA1 Message Date
Harald Kuhr c278f5f1f2 [maven-release-plugin] prepare for next development iteration 2015-01-03 16:22:05 +01:00
Harald Kuhr 4c276df63d [maven-release-plugin] prepare release twelvemonkeys-3.0.2 2015-01-03 16:21:59 +01:00
Harald Kuhr 98616e9c9e TMI-89: Test case 2015-01-03 16:14:25 +01:00
Harald Kuhr d8dedc86fc TMI-89: Fix exception in LZWDecoder for TIFF with LZW strings longer than strip/tile width 2015-01-02 17:47:22 +01:00
Harald Kuhr fe9f345d7b TMI-84: LZWDecoder fixes for > 12 bit exception when using full 12 bit (4096 entries) table 2014-12-04 19:39:58 +01:00
Harald Kuhr 31c3fdc0fd [maven-release-plugin] prepare for next development iteration 2014-11-27 10:02:06 +01:00
Harald Kuhr 217d14095b [maven-release-plugin] prepare release 3.0.1 2014-11-27 10:01:09 +01:00
Harald Kuhr 1ee21061bd Preparing POMs for bugfix release 2014-11-26 21:11:39 +01:00
Harald Kuhr 00b15746e4 TM-80: DecoderStream.skip now correctly skips the bytes it reports as skipped. 2014-11-04 16:24:57 +01:00
Harald Kuhr a42ccd031b TMI-74: Make JPEG segment parsing more lenient (missing file). 2014-11-03 21:17:40 +01:00
Harald Kuhr b4fde6ff17 TMI-77: Fixed bug in predictor for tiled TIFFs. 2014-11-03 21:14:25 +01:00
Harald Kuhr b275d7f777 TMI-74: Make JPEG segment parsing more lenient 2014-11-03 21:14:13 +01:00
Harald Kuhr c544db9882 TMI-73: Now handles TIFF files using only the lower 8 bits of each 16 bit entry in the ColorMap. 2014-11-03 21:13:43 +01:00
Harald Kuhr 9da706dfbb TMI-72: TIFFImageReader now allocates a buffer for LZW decoding same as image width. 2014-10-31 14:16:37 +01:00
Harald Kuhr 977ecb0482 TMI-71: ...and the test case verifying it. 2014-10-28 12:34:24 +01:00
Harald Kuhr 8143c957bf TMI-71: Now ignores ICC color profile if type/component count does not match image data. 2014-10-28 10:11:31 +01:00
Harald Kuhr a5d703b29b TMI-69: TIFFImageReader may throw IndexOutOfBoundsException if width not a multiple of tileWidth. 2014-10-16 14:47:23 +02:00
Harald Kuhr b7265a5117 Fixed some typos. 2014-10-10 10:07:18 +02:00
Harald Kuhr a1769cd40b TMI-67: Handle broken old-style-jpeg from Snowbound software. 2014-10-09 18:44:58 +02:00
Harald Kuhr d054ee9bb1 [maven-release-plugin] prepare release twelvemonkeys-3.0 2014-10-01 11:21:34 +02:00
Harald Kuhr 845944b25f [maven-release-plugin] prepare for next development iteration 2014-10-01 10:32:30 +02:00
Harald Kuhr d12f9bdbbd [maven-release-plugin] prepare release twelvemonkeys-3.0-rc7 2014-10-01 10:32:23 +02:00
Harald Kuhr 3e9820bb22 Prepare for new development 2014-10-01 10:25:34 +02:00
Harald Kuhr 2098e6a898 [maven-release-plugin] prepare release twelvemonkeys-3.0-rc6 2014-10-01 10:19:06 +02:00
Harald Kuhr feb20eefdd [maven-release-plugin] prepare for next development iteration 2014-09-30 12:48:20 +02:00
Harald Kuhr 6977e52059 [maven-release-plugin] prepare release twelvemonkeys-3.0-rc6 2014-09-30 12:48:12 +02:00
Harald Kuhr 8299182f52 TMI-36: Creation of *source.jar in phase 'package' 2014-09-30 11:24:29 +02:00
Harald Kuhr 1ff764997b TMI-TIFF: 16 bit YCbCr support + minor improvements 2014-09-29 14:50:28 +02:00
Harald Kuhr f914d15677 Rewrote test to use JUnit 4. 2014-09-29 11:24:12 +02:00
Harald Kuhr f32110c5b8 Code clean-up, no functional changes. 2014-09-29 11:22:25 +02:00
Harald Kuhr e79f115ab4 TMI-TIFF: Predictor tag 2014-09-29 11:18:10 +02:00
Harald Kuhr e956fedfcf TMI-TIFF: Minor code clean-up. 2014-09-26 16:44:16 +02:00
Harald Kuhr 61cbeb0a09 TMI-META: Now reads ICC profile from PSD Image Resource Blocks in JPEG. 2014-09-26 16:43:14 +02:00
Harald Kuhr 7634ca1261 Minor code clean-up. 2014-09-26 16:41:49 +02:00
Harald Kuhr a67c0fb456 TMI-TIFF: Test case for CCITT modified huffman (type 2), source region support for JPEG and tiled images + minor bug fixes. 2014-09-26 16:01:04 +02:00
Harald Kuhr 8c93be05a5 Better testing of source region reading. A few readers are failing for now, added @Ignore. 2014-09-26 15:59:19 +02:00
Harald Kuhr afff6f78a8 Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys 2014-09-10 21:38:45 +02:00
Harald Kuhr 3e0440b9f4 TMI-62: Faster reading of short and integer rasters. 2014-09-10 21:34:07 +02:00
Harald Kuhr fb9d9de6b0 Updated to reflect latest changes. 2014-09-09 16:49:29 +02:00
Harald Kuhr 2fb9a54618 TMI-27, TMI-28: Source region and subsampling support. 2014-09-09 16:44:20 +02:00
Harald Kuhr 06674d1273 TMI-PSD: Added support for PSB (aka "Large Document Format")
Added support for 32 bit channels.
Added test cases + fixed a few bugs
General code clean-up
2014-09-09 16:36:18 +02:00
Harald Kuhr 7e88a6f7e3 TMI-57: Updated test case. 2014-08-26 21:20:37 +02:00
Harald Kuhr dd54793d3d TMI-57: Clean up after merge. 2014-08-26 21:08:43 +02:00
Harald Kuhr 96eb0d0b7f Merge pull request #58 from rvsoni/master
#57 Added 'image/vnd.adobe.photoshop' mimetype
2014-08-26 21:01:23 +02:00
ravi.soni 9e9f47a2fb #57 Added 'image/vnd.adobe.photoshop' mimetype 2014-08-25 13:07:53 +05:30
Harald Kuhr 9a6f4bba33 Merge pull request #56 from pvorb/patch-1
Fix version numbers of Maven artifacts and JARs
2014-08-14 15:34:50 +02:00
Paul Vorbach d310b7c8e7 Fix version numbers of Maven artifacts and JARs
Fixes issue #55
2014-08-14 15:13:54 +02:00
Harald Kuhr 64668807e0 Code clean-up after merge + Rewrote test to use JUnit 4. 2014-03-20 18:01:20 +01:00
Harald Kuhr 7430d0053a Merge pull request #45 from ankon/imageio-jpeg-test-null
Avoid NPE when the expected tree is indeed null
2014-03-20 17:57:37 +01:00
Harald Kuhr a34a0e3f44 Merge pull request #44 from ankon/common-image-null-reverse
Fix conditions in #showEm()
2014-03-20 17:56:32 +01:00
Harald Kuhr c048928011 Merge pull request #46 from ankon/imageio-jpeg-test-warning
Remove unneeded @SuppressWarnings("unchecked")
2014-03-20 17:53:37 +01:00
Andreas Kohn f4ba4e081e Remove unneeded @SuppressWarnings("unchecked") 2014-03-17 12:20:04 +01:00
Andreas Kohn 9bb7b62987 Avoid NPE when the expected tree is indeed null 2014-03-17 12:19:41 +01:00
Andreas Kohn 552ab24658 Fix conditions in #showEm()
* return when the pOriginal is null, not when it is non-null
  (Eclipse noticed a definitive NPE)
* return when running in headless mode (unit tests via maven)
2014-03-17 12:11:49 +01:00
Harald Kuhr 52aa7e974b Updated examples to point to latest RC. 2014-03-04 11:08:55 +01:00
Harald Kuhr cffc3af45c Added links to prebuilt binaries 2014-03-04 11:06:35 +01:00
Harald Kuhr 5952634671 Added info on supported JDKs for building 2014-03-04 10:53:09 +01:00
Harald Kuhr c3c23d0523 TMI-43: Made tests a little more robust to void false positives. 2014-03-03 20:29:58 +01:00
Harald Kuhr aacad8a575 Merge pull request #42 from ankon/pom-implementation-url-configuration
Use the project.scm.url as Implementation-URL in the jar manifests
2014-03-03 20:03:47 +01:00
Andreas Kohn 83a6d604a6 Use the project.scm.url as Implementation-URL in the jar manifests 2014-03-03 12:02:39 +01:00
Harald Kuhr e3bab84e82 TMI-39: Cleanup after merge. 2014-03-02 23:10:20 +01:00
Harald Kuhr d607450ae4 Merge pull request #39 from escenic/upstream
JPEGImageReader throws "IllegalStateException: sourceRegion.height <= subsamplingYOffset!"
2014-03-02 22:15:06 +01:00
Harald Kuhr 037a47ca2a TMI-38: Fixed a bug in the getSourceCS method, that incorrectly identified non-subsampled JFIF files, as RGB instead of YCbCr. 2014-03-02 22:08:26 +01:00
Harald Kuhr 5c735674f0 TODO: Fix/file OpenJDK bug. 2014-03-02 21:48:37 +01:00
Rune Bremnes 822bea80b6 Fix reading jpeg images where last scanline is higher than the y
source subsampling offset.
2014-02-24 14:32:05 +01:00
Rune Bremnes e924fcefc0 Added failing testcase for JPEGImageReader. 2014-02-24 14:31:57 +01:00
Trygve Laugstøl 0ab9294004 [maven-release-plugin] prepare for next development iteration 2014-02-15 13:14:41 +01:00
Trygve Laugstøl ed5a5e0dca [maven-release-plugin] prepare release twelvemonkeys-3.0-rc5 2014-02-15 13:13:39 +01:00
Trygve Laugstøl ad68bb88a4 o Preparing for release. 2014-02-15 13:09:33 +01:00
Harald Kuhr ff786c2315 WMFImageReaderSpi issue 2014-02-12 20:32:28 +01:00
Harald Kuhr a26f8e5851 TMI-32: Cleanup after merge. Removed filter + code style changes. 2014-02-06 23:15:40 +01:00
Harald Kuhr b49fd7b653 TMI-32: Cleanup after merge. Removed filter + code style changes. 2014-02-06 23:14:08 +01:00
Harald Kuhr 9fa1d97389 TMI-32: Cleanup after merge. Removed filter + code style changes. 2014-02-06 23:09:01 +01:00
Harald Kuhr 7c012323e5 Merge pull request #32 from rtimpe/master
Support registration of JPEG plugins when the classloader can't find sun plugins
2014-02-06 23:02:43 +01:00
Harald Kuhr 83403c67f5 Merge branch 'master' of github.com:haraldk/TwelveMonkeys 2014-02-06 22:49:19 +01:00
Harald Kuhr db259bff10 TMI-34: Handling of problematic Corbis RGB ICC profiles. 2014-02-06 22:45:35 +01:00
Rob Timpe 1e42cf1499 Return immediately when the right sun jpeg plugin is found. 2014-01-28 13:32:46 -08:00
Robert Timpe bb4e77406a Support registration of JPEG plugins when the classloader can't find sun plugins.
In some situations, the classloader may not be able to find the sun jpeg plugins
even if they are registered.  In this case, we can still find the sun plugins by
iterating over all the registered plugins.
2014-01-24 20:29:34 +00:00
Harald Kuhr b9f04059bf Update README.md 2014-01-03 10:05:07 +01:00
Harald Kuhr 14e12eb2c1 TMS-29: Code clean up after merge. 2014-01-02 13:25:37 +01:00
Harald Kuhr 0ded2f211a TMS-29: Test case exposing .ConcurrentModificationException. 2014-01-02 13:21:37 +01:00
Harald Kuhr ed11259e58 Merge branch 'master' of github.com:haraldk/TwelveMonkeys 2014-01-02 13:18:58 +01:00
Harald Kuhr 5779a40a32 TMS-29: Test case exposing .ConcurrentModificationException. 2014-01-02 13:16:26 +01:00
Harald Kuhr 90c7ac7d98 Merge pull request #30 from ankon/issue29-cme-servlet-contextDestroyed
Call #deregisterServiceProvider() outside of the iteration over the existing service providers
2014-01-02 03:57:07 -08:00
Andreas Kohn ecc08daa2d Call #deregisterServiceProvider() outside of the iteration over the existing service providers 2013-12-30 17:57:38 +01:00
Harald Kuhr fc99abb4b4 Code style changes and code clean up. 2013-12-25 13:44:41 +01:00
Harald Kuhr 63d9029a3e Clean-up after pull. 2013-12-23 11:49:45 +01:00
Harald Kuhr af245a80d9 Merge pull request #19 from guinotphil/spifix
Fix issue with JMagick Spi Providers
2013-12-23 02:45:43 -08:00
Harald Kuhr 9cf47aca98 TMI-TIFF: Safer getImageTypes + minor code cleanup. No functional changes. 2013-12-23 11:14:59 +01:00
Harald Kuhr a1f9e979b9 Servlet changes for 3.0. 2013-12-23 11:11:48 +01:00
Harald Kuhr aafdb31a8c Updated readme and todos. 2013-12-23 10:50:50 +01:00
Harald Kuhr ce87171026 TMI-JPEG-22: Fixed issue with trash 0x00 padding in JPEG. 2013-12-16 18:08:39 +01:00
Harald Kuhr 55a373b0ff Code-style. 2013-11-28 14:53:31 +01:00
Harald Kuhr 791e1b2d56 TMI-CORE: Code clean-up. 2013-11-25 17:18:18 +01:00
Harald Kuhr cc5f763503 TMI-CORE: Removed comments for incorrectly out-commented line.. 2013-11-25 12:59:25 +01:00
Harald Kuhr d261105c6b TMI-TIFF: Better tests & validation for the YCbCr upsampler. 2013-11-20 21:39:10 +01:00
Harald Kuhr 9a02e90ab9 TMI-TIFF: Implemented pairwise ordering (before Apple-provided com.sun.imageio.plugins.tiff.TIFFImageReaderSpi).
Removed deprecation warning.
2013-11-20 20:26:06 +01:00
Harald Kuhr d6f5a1281c TMI-TIFF: Added name for StripByteCounts TIFF constant. 2013-11-20 20:21:20 +01:00
Harald Kuhr 534f964868 Minor API changes. 2013-11-20 20:18:10 +01:00
Harald Kuhr cff4d836d1 TMI-PSD: Added todo. 2013-11-20 20:14:52 +01:00
Harald Kuhr 2d42b58814 TMI-JPEG: Test case for the XDensity out of range issue. Reader no longer attempts to read Exif thumbnails of length 0. 2013-11-20 20:11:39 +01:00
Harald Kuhr aa0a0f96e9 Code clean-up. 2013-11-20 20:06:16 +01:00
Harald Kuhr c7ecd7afc8 Rewritten to use ByteBuffer. 2013-11-20 20:05:39 +01:00
Harald Kuhr 39d3fc426e Clean-up after merge from guinotphil. 2013-11-15 17:41:34 +01:00
Harald Kuhr f08fbd0e21 Merge pull request #20 from guinotphil/jmagickconversion
Adding support for CMYK images.
2013-11-15 08:14:10 -08:00
guinotphil b5f2f9dbb8 Add a first implementation of toBuffered for ColorSeparation to handle CMYK pictures.
The code has been tested with a CMYK picture, but I have not been able to test alpha picture.
The code will probably also need to be improved to handle any kind of ColorSeparation picture.
Though, it's a nice base for further developments.
2013-11-15 16:02:35 +01:00
guinotphil dd2327e70e * Correct test of 4th byte of header
* Handle Photoshop 3's JPEG format
2013-11-15 15:38:50 +01:00
guinotphil 371aa4298b Force the use of the package's class loader since JMagick class may not be available with current Thread's class loader (eg. application servers such as JBoss AS 7...) 2013-11-15 15:36:57 +01:00
guinotphil 1af9a0c48c Manage error occuring with non-seekable stream such as URLConnection. 2013-11-15 15:35:17 +01:00
Harald Kuhr b34770658a Updated readme. 2013-11-15 13:44:56 +01:00
Harald Kuhr a11f007005 Updated readme. 2013-11-12 12:45:18 +01:00
Harald Kuhr ac9fa338b9 Added link to FAQ. 2013-11-12 12:36:38 +01:00
Harald Kuhr 348ced52be Added advanced examples. 2013-11-12 12:35:16 +01:00
Harald Kuhr 9849cdb001 Updated readme. 2013-11-12 09:23:02 +01:00
Harald Kuhr 38fa2189bc TMI-IIO: Removed some deprecation warnings. 2013-11-05 20:24:12 +01:00
Harald Kuhr db0f8901dc Updated readme. 2013-11-05 16:03:25 +01:00
Harald Kuhr 18448b6a43 Updated readme. 2013-11-05 16:01:56 +01:00
Harald Kuhr c491c8a518 TMI-JPEG: Fixed issue with wrong class name for writer and reader spi. 2013-11-05 15:40:14 +01:00
Harald Kuhr f5a4fe03f4 TMI-15: Fixed some issues introduced in later JREs (at least 7u45). 2013-11-05 09:43:46 +01:00
Harald Kuhr d04c29ae12 Added documentation of IIOProviderContextListener. 2013-11-01 11:03:31 +01:00
Harald Kuhr 51f0b20bb0 TMI-IFF: Updated documentation/description. 2013-10-31 13:48:47 +01:00
Harald Kuhr a5e6346647 Documentation clean-up. 2013-10-31 12:32:35 +01:00
Harald Kuhr 15cb7dd71b Updated readme. 2013-10-31 10:01:19 +01:00
Harald Kuhr 3be5c1713b Updated readme. 2013-10-31 09:49:58 +01:00
Harald Kuhr 8aff3faa09 Updated readme. 2013-10-31 09:41:35 +01:00
Harald Kuhr 973fe9fa37 Updated readme. 2013-10-30 18:02:41 +01:00
Harald Kuhr 7527f2cdc6 Updated readme. 2013-10-30 18:00:25 +01:00
Harald Kuhr a36eb0cd5d Updated readme. 2013-10-30 17:58:27 +01:00
Harald Kuhr 8f33f906fb Updated readme. 2013-10-30 17:55:04 +01:00
Harald Kuhr 22d7ce80b1 Updated readme. 2013-10-30 17:50:08 +01:00
Harald Kuhr 3bd8900cbe Updated readme. 2013-10-30 17:47:10 +01:00
Harald Kuhr cc44e73d7d Updated readme. 2013-10-30 17:44:31 +01:00
Harald Kuhr c3ee44992a Updated readme. 2013-10-30 17:39:25 +01:00
Harald Kuhr e2d56659ca Updated readme. 2013-10-30 17:36:40 +01:00
Harald Kuhr 5189c7c1e7 Updated readme with correct bulleting. 2013-10-30 17:35:34 +01:00
Harald Kuhr 9cab7903ed Updated readme. 2013-10-30 17:34:43 +01:00
Harald Kuhr dc31322518 Updated readme. 2013-10-30 17:32:59 +01:00
Harald Kuhr cb82b39e6a Delete old README. 2013-10-30 17:16:20 +01:00
Harald Kuhr 5064d08dd6 updated readme. 2013-10-30 17:16:00 +01:00
Harald Kuhr 7e14f0b37c updated readme. 2013-10-30 17:12:31 +01:00
Harald Kuhr ef13030cc7 Updated readme. 2013-10-30 17:11:07 +01:00
Harald Kuhr f83ca01e8f Finally a more useful readme. 2013-10-30 17:03:10 +01:00
Harald Kuhr 5508137c5c TMI-JPEG-4: Code clean up 2013-10-30 13:49:23 +01:00
Harald Kuhr ae58b859e4 TMI-JPEG-4: Fixed issue related to X/Y density out of range. 2013-10-25 17:09:20 +02:00
Harald Kuhr bf1aae6652 Removed Maven warnings due to missing encoding/missing depency/plugin versions. Build should now be stable. 2013-10-25 13:05:36 +02:00
Harald Kuhr 86921ad389 ImageReader subsampling test 2013-10-24 21:19:52 +02:00
Harald Kuhr d7958fc8a7 TMI-JPEG-4: Clean up 2013-10-23 16:37:11 +02:00
Harald Kuhr ca48837e11 TMI-JPEG-4: Moved metadata cleaning to separate class.
Better class name welcome... ;-)
2013-10-23 14:56:01 +02:00
Harald Kuhr b14363da3b TMI-JPEG-4: Now does a pretty decent job at glossing over metadata issues. 2013-10-21 19:31:04 +02:00
Harald Kuhr c8061eb0c4 TMI-JPEG: Regression fix for NPE in metadata if delegate returns null metadata. 2013-09-28 12:32:01 +02:00
Harald Kuhr 1acc04eeaf TMC-IOENC-11: Fixed problem introduced when migrating byte[] -> ByteBuffer 2013-09-27 14:21:31 +02:00
Harald Kuhr cd197afc04 TMI-META: Minor improvements in XMP parsing, PSD made public and faster dumping from JPEGSegmentUtil. 2013-09-19 09:52:47 +02:00
Harald Kuhr 086357694a TMI-JPEG-10: Fixed an issue with JPEGs without JFIF segment being treated as RGB, even when YCbCr. 2013-09-19 09:25:59 +02:00
Harald Kuhr 602e5ec34b TMI-TIFF: Rewritten to use ByteBuffer. 2013-09-18 10:29:56 +02:00
Harald Kuhr aebfad914f TMC-IOENC: Encoder implementation clean-up. 2013-09-13 17:04:10 +02:00
Harald Kuhr d1f00ce817 TMC-IOENC: Decoder implementation clean-up. 2013-09-13 16:25:36 +02:00
Harald Kuhr cd6e9ebbf5 TMS: Updated POM with new build-plugin version. 2013-09-08 14:43:51 +02:00
Harald Kuhr 0ff99afe6d TMI-JPEG: Now does a better effort to gloss over metadata issues in underlying stream. 2013-09-08 14:43:05 +02:00
Harald Kuhr 47425e2ca0 Rewritten to use SunWritableRaster if available, with graceful fallback.
Better tiling in test app.
2013-09-08 14:41:06 +02:00
Harald Kuhr 2116feb49f Experimental implementation of Base64 encoded DataURL string. Probably not a good idea.. ;-) 2013-09-08 14:38:49 +02:00
Harald Kuhr cdc832623a TMI-METADATA: Minor clean-up, preparing for read/write of metadata.
Now uses proper constants for TIFF types.
2013-09-08 14:33:40 +02:00
Harald Kuhr 5531c863cf TMI-JPEG: Fixed typos in exception messages. 2013-09-08 14:27:08 +02:00
Harald Kuhr dc63fac8ef Updated JZ2012 demo/example code 2013-09-08 14:25:13 +02:00
Harald Kuhr 10b95b225f TMI-CORE: Added comments/fixed typos no functional changes. 2013-09-08 14:23:13 +02:00
Harald Kuhr 23ae6a966a TMC-IO: Fixed typos, no functional changes 2013-09-08 14:21:51 +02:00
Harald Kuhr 55bd82491f Moved old obsolete stuff to sandbox. 2013-09-08 14:20:13 +02:00
Harald Kuhr 13c8cd7f93 POM changes in sandbox. 2013-09-08 14:18:38 +02:00
Harald Kuhr 2f69847b23 Moved old obsolete stuff to sandbox. 2013-09-08 14:16:31 +02:00
Harald Kuhr c5f1d8101b Moved old obsolete stuff to sandbox. 2013-09-08 14:08:38 +02:00
Harald Kuhr 4c18c2a685 TMC-IMAGE: Added TODO, should probably retire class and move to sandbox 2013-09-08 13:53:01 +02:00
Harald Kuhr 55b161b115 TMC-IMAGE: Removed support for prehistoric JREs. 2013-09-08 13:41:16 +02:00
Harald Kuhr f2ff00580a TMC-IOENC: Refactored Decoder to use ByteBuffer instead of byte[] for better readability/simpler code. 2013-09-08 13:39:13 +02:00
Harald Kuhr 9a27f62dec Created replacement for StringBufferInputStream that properly encodes chars into bytes. 2013-09-04 13:23:17 +02:00
Harald Kuhr 4de927b657 TMI-META: Removed misleading TODO/comment 2013-07-15 09:39:13 +02:00
Harald Kuhr de81723912 TMI-JPEG: Added license. 2013-06-27 10:20:32 +02:00
Harald Kuhr 9b81db5e32 Merge pull request #8 from justwrote/master
Mockito should be a test dependency
2013-06-21 11:13:23 -07:00
justwrote 0860db2166 mockito should be a test dependency 2013-06-21 12:07:49 +02:00
Harald Kuhr 37b223c29b TMI-TIFF: Added license. 2013-06-19 16:58:03 +02:00
Harald Kuhr 2433075578 TMI-JPEG: Removed experimental metadata code to avoid NPE. 2013-06-13 09:30:18 +02:00
Harald Kuhr 6ce9543c00 TMI-TIFF: Test case for CCITT decoding. 2013-06-12 21:57:09 +02:00
Harald Kuhr ff3fbc8bd2 TMI-TIFF: Getting close to full baseline support!
- Added Modified Huffman decoding (needs a proper test image)
 - Improved predictor support (16/32 bpp)
 - Fixed handling of bogus RowsPerStrip
2013-06-12 21:54:28 +02:00
Harald Kuhr cdce0aebff TMC-LANG: Code clean-up, added tests and fixed issues. 2013-06-06 19:51:07 +02:00
Harald Kuhr f5b5e818c5 TMC-LANG: Made test case OS agnostic (now builds on Windows too). Thanks Omri Spector! 2013-06-06 16:57:35 +02:00
Harald Kuhr 33ffc14e3f TMS-XXX: Code style. 2013-06-05 10:56:12 +02:00
Harald Kuhr a2effd7ba0 TMI-META: Added license. 2013-06-05 10:55:18 +02:00
Harald Kuhr 544d60dabb TMI-JPEG: Fixed ICC profile issue. Now applies profiles when it should.
Profiles with bad indexes are now ignored on read.
Added support for JPEG-LS SOF55 segment (no further JPEG-LS support)
Added class documentation.
2013-06-05 10:54:51 +02:00
Harald Kuhr f8c40a3748 TMI-JPEG: Better CMYK to RGB algorithm 2013-06-05 10:49:37 +02:00
Harald Kuhr 28e2f3c21b TMC-CORE: Fixed typos 2013-06-05 10:47:57 +02:00
Harald Kuhr 2cf1c6e43b TMC-IMAGE: Fixed some typos. 2013-06-05 10:46:41 +02:00
Harald Kuhr e72988aa7b TMC-IMAGE: Code clean-up. Removed obsolete code. No functional changes. 2013-06-05 10:45:31 +02:00
Harald Kuhr 0e628f6e4c TMI-CORE: Added empty ICC profile locations for Linux.
+ Better exception handling for missing profile locations.
2013-06-05 09:58:32 +02:00
Harald Kuhr 1d5cc6d266 TMI-JPEG: Refactorings for better separation. 2013-04-22 21:01:30 +02:00
Harald Kuhr 28d8796e54 TMI-JPEG: Simplified logic. 2013-04-22 21:00:33 +02:00
Harald Kuhr 0ffd7cacc4 TMI-ICNS: Added debug for sips command. 2013-04-22 11:08:29 +02:00
Harald Kuhr b966254322 TMI-JPEG: More lenient segment parsing, now allows 0xFF padding between segments + fixed an NPE in JPEGImageReader if the parsing fails. 2013-04-19 16:17:01 +02:00
Harald Kuhr 61e01e3316 TMI-TIFF: Code clean-up. 2013-03-26 09:44:32 +01:00
Harald Kuhr 09444ab083 TMI-TIFF: Horizontal differencing predictor implementation as stream, for easier reading. 2013-03-26 09:42:49 +01:00
Harald Kuhr b97d95cca7 More Maven-friendly test suite. 2013-03-04 15:16:59 +01:00
Harald Kuhr 1ffe694538 TMS-XXXX: New map adapters for servlet context and request attributes + minor API tweaks. 2013-03-04 14:48:50 +01:00
Harald Kuhr dd0f382d3c TMC-XXXX: Code cleaun-up and fixed spelling errors. 2013-02-28 13:08:19 +01:00
Harald Kuhr 0319a6f84c TMI-CORE: Minor UI bugfix 2013-02-27 12:07:34 +01:00
Harald Kuhr df9f5734bd TMI-CORE: Improved zoom quality when zooming out. 2013-02-20 10:45:20 +01:00
Harald Kuhr 59e5c3b3fd TMI-TIFF: Fixed doc, removed todo. 2013-02-20 10:44:42 +01:00
Harald Kuhr 2764460db5 TMI-TIFF: Now supports YCbCr subsampled images with image/tile/strip width/height not a multiple of the x/y subsampling. More lenience for weird subsampling.
+ Some minor house-keeping with no functional change.
2013-02-19 22:02:15 +01:00
Harald Kuhr c9809d0fa1 TMI-CORE: Added zoom in/out/actual to image display, moved background to submenu. 2013-02-19 13:02:45 +01:00
Harald Kuhr bb7e1a4258 TMC-XXXX: Added constructor exposing fast rendering flag. 2013-02-19 12:23:49 +01:00
Harald Kuhr cc604e650b TMI-TIFF: Added more constants + debugging code for field names + suppressed unwanted warnings. 2013-02-18 14:51:45 +01:00
Harald Kuhr a0d4973d7f TMC-XXXX: Updated author + version number. 2013-02-15 13:08:14 +01:00
Harald Kuhr d8867736b7 TMI-TIFF: Fixed several bugs in the LittleEndianDataInputStream needed for proper TIFF output (should affect other things as well...) 2013-02-15 12:55:37 +01:00
Harald Kuhr ed6223fcab TMI-CORE: Fixed a reappearing bug in the JDK7 code, should now work properly with "broken" ICC color profiles . 2013-02-15 12:52:56 +01:00
Harald Kuhr f8369fb5b6 TMI-TIFF: Rolled back some breaking changes. 2013-02-15 12:34:36 +01:00
Harald Kuhr 94db6b4a6f TMI-TIFF: Simplified progress update. 2013-02-14 14:40:03 +01:00
Harald Kuhr 10f501e919 TMI-XXXX: Fixed a typo in the JPEG docs. 2013-02-14 14:14:43 +01:00
Harald Kuhr 9c8ad3cb74 TMI-TIFF: Added warning for unknown YCbCrPositioning values. 2013-02-14 12:53:22 +01:00
Harald Kuhr 42831ea65b TMI-TIFF: Now passes YCbCrPositioning to upsampler. Replaced magic value with constant. 2013-02-14 12:48:07 +01:00
Harald Kuhr 1548523336 TMI-TIFF: Code clean-up. 2013-02-14 12:37:05 +01:00
Harald Kuhr b3672be1d4 TMI-TIFF: Replaced JPEG test case with more light-weight file. 2013-02-14 12:31:00 +01:00
Harald Kuhr 3b15653a10 TMI-TIFF: Added support for premultiplied alpha (ExtraSamples == 1) + code clean-up. 2013-02-14 11:25:33 +01:00
Harald Kuhr 46b53a824c TMI-TIFF: Added type spec for CMYK+A separated images 2013-02-07 12:35:04 +01:00
Harald Kuhr 02063c809e TMI-TIFF: Added JPEG-compressed data to tests. 2013-02-06 17:02:51 +01:00
Harald Kuhr 8b9d5c7abc TMI-TIFF: Renamed "Old-style" JPEG constants, to discourage use.
Removed hardcoding of JFIF-stream for "Old-style" JPEG reading + cleaned up code.
Fixed a bug in getRawImageType for planar RGBA
2013-02-06 15:20:27 +01:00
Harald Kuhr c394f8a4bc TMI-TIFF: Fixed bug in YCbCr reading. Implemented "old-style" JPEG reading for two test images. More work needed. 2013-02-06 11:20:42 +01:00
Harald Kuhr fcd15a9e36 TMI-META: Now correctly reads/parses SOS marker segment. Slightly stricter checking of markers. 2013-02-06 10:44:49 +01:00
Harald Kuhr 41a08761ba TMI-TIFF: Removed leftover debug output. 2013-01-31 16:36:47 +01:00
Harald Kuhr b834a32b01 TMI-TIFF: Minor bug introduced by testing.. 2013-01-31 16:35:37 +01:00
Harald Kuhr 00f47e81a4 TMI-XXX: New code style + minor housekeeping changes. 2013-01-31 15:41:59 +01:00
Harald Kuhr f666610184 TMI-TIFF: New tag + fixed spelling for tag. 2013-01-31 15:40:08 +01:00
Harald Kuhr 47fbf473db TMI-TIFF: Implemented YCbCr reading. 2013-01-31 15:38:45 +01:00
Harald Kuhr 8c4f9d3ed6 TMI-XXX: More leniency 2013-01-31 14:49:27 +01:00
Harald Kuhr e68b3aa9e3 TMI-TIFF: Now uses String class instead of byte[], to avoid excessive array concatenation and copying. 2013-01-29 22:26:11 +01:00
Harald Kuhr dd849aeea6 TMI-TIFF: Now uses subclasses instead of if-branching for LZW compatibility decoding. 2013-01-29 21:24:51 +01:00
Harald Kuhr 59b91918e0 TMI-TIFF: Fixed minor bug in type spec for ARGB images + implemented support for "old-style" (reversed) LZW compression from libtiff. 2013-01-29 21:01:46 +01:00
Harald Kuhr 7846f497af TMS: Moving old junk to sandbox. 2012-09-25 14:50:47 +02:00
Harald Kuhr 6c082353d6 TMI-22: Cleaned up reading of ICC profile, trying to be more lenient about chunk count/chunk index. 2012-06-22 09:57:02 +02:00
Harald Kuhr 92690e1644 TMI-TIFF: More format support, more lenient TIFF (EXIF) parsing. 2012-06-21 17:05:33 +02:00
Harald Kuhr 9ef8ac9930 Removed obsolete methods. 2012-06-21 16:59:04 +02:00
Harald Kuhr 7260c5baea Removed obsolete methods. 2012-06-21 16:58:33 +02:00
Harald Kuhr 381e229575 Added a comment. 2012-06-21 16:57:57 +02:00
Harald Kuhr 80d2f4ad89 Added test case.
Clean up + new methods.
2012-06-21 16:57:10 +02:00
Harald Kuhr b0c2b4886f Added test case. No changes. 2012-06-21 16:56:25 +02:00
Harald Kuhr 14869fb591 Clean up: No functional changes. 2012-06-21 16:54:11 +02:00
Harald Kuhr f7b7b91fba Updated sandbox pom. 2012-06-21 16:53:06 +02:00
Harald Kuhr 5c9a3e8e58 Added test case. Fixed some minor issues. 2012-06-21 16:52:30 +02:00
Harald Kuhr 2cbdd7fd82 Clean up: Moving obsolete stuff to sandbox. 2012-06-21 16:37:27 +02:00
Harald Kuhr 5bac1e3a2b Removed some synchronized keywords weirdness. 2012-06-21 16:36:25 +02:00
Harald Kuhr 0d83ab5483 Minor clean-up & doc changes. 2012-06-21 16:34:11 +02:00
Harald Kuhr 0aad4cb77a Clean up: Moved test code from main method to test case. 2012-06-21 16:29:04 +02:00
Harald Kuhr 73a880a358 TMI-23: Better handling of SOS segment (variable length). Now treats the rest of the stream after SOS as single segment. Not really ideal, but gives better performance than scanning for EOI... 2012-06-21 16:08:03 +02:00
Harald Kuhr c2245a503d TMI-22: Changed IIOException to warning for images with single chunk ICC_PROFILE with bad index/count. 2012-06-21 16:03:59 +02:00
Harald Kuhr 75c09d3aef Now sets Vary header. Minor code clean up & fixes. 2012-06-21 10:55:14 +02:00
Harald Kuhr 4db12d313b Added init param annotation. 2012-06-21 10:50:45 +02:00
Harald Kuhr 3095422a44 Fixed code style. 2012-06-21 10:49:39 +02:00
Harald Kuhr d84acbf4b3 A servlet for serving static files 2012-06-21 10:45:06 +02:00
Harald Kuhr c7f6dedaa7 ContextListener for web app-local IIO providers. 2012-06-21 10:44:53 +02:00
Harald Kuhr 37e9adcfec Fixed a bug in the MappedFileBuffer + added test case for exposing the bug. 2012-06-01 14:42:48 +02:00
Harald Kuhr 7f2ad765cf TMI-XXX: Made the PSD XMP parser slightly more lenient. 2012-05-31 16:24:17 +02:00
Harald Kuhr 289be6ca12 TMI-TIFF: Added validation + tests for reading JPEG DQT needed for TIFF Compression JPEG. 2012-05-23 15:20:10 +02:00
Harald Kuhr b8ff4af178 TMI-TIFF: Implemented workaround for a bug in ImageReader.getDestination() + added test case for ImageReaderBase. 2012-05-23 15:18:10 +02:00
Harald Kuhr dd7be5ef11 TMI-TIFF: Added missing test resources. 2012-05-22 08:56:39 +02:00
Harald Kuhr 98361194ea TMI-TIFF: Initial commit. Major work in progress. :-) 2012-05-22 00:00:11 +02:00
Harald Kuhr 9492ed67f1 Minor enhancements to the Decoder API + tests. 2012-05-21 23:56:47 +02:00
Harald Kuhr a4dfb7a009 TMI-21: Implemented getRawImageType and getImageTypes for CMYK/YCCK.
TMI-16: Refactorings, cleaner color space determination + tests for thumbnail readers.
2012-05-07 20:26:26 +02:00
Harald Kuhr aaef2e4fad TMI-16: Clean-up of thumbnail reading. Removed obsolete code. 2012-05-02 11:59:14 +02:00
Harald Kuhr 241c1882f4 TMI-16: Refactorings. Moved segment classes to upper level. Extracted thumbnail reading to separate classes. 2012-04-24 20:11:04 +02:00
Harald Kuhr ae87726974 Added test case. 2012-04-19 18:00:50 +02:00
Harald Kuhr b9a1c5c2f4 TMI-20: Made the EXIFReader more lenient about bad directory entry count. 2012-04-19 17:53:28 +02:00
Harald Kuhr 7bcfd228b9 Documentation + fix for minor issue when quality could not be determined. 2012-04-19 17:51:19 +02:00
Harald Kuhr 465eb2ecb3 Removed a couple of todos (now done). 2012-04-17 12:28:27 +02:00
Harald Kuhr 0bdb68ea6f TMI-19: Fix for broken JFIF raw RGB thumbnails 2012-04-16 22:55:32 +02:00
Harald Kuhr c16ffaca13 TMI-18: Fix for images/thumbnails get inverted colors. 2012-04-16 22:53:17 +02:00
Harald Kuhr 24db7e847c Added support for offset/length in ByteArrayImageInputStream. 2012-04-16 10:19:53 +02:00
Harald Kuhr 927723a472 Added support for offset/length in ByteArrayImageInputStream. 2012-04-16 10:19:15 +02:00
Harald Kuhr 2f07329296 TMI-18: Better filtering of APP segments, now only takes 'Exif' APP1 segments into account.
+ Updated failing test.
2012-04-15 22:10:20 +02:00
Harald Kuhr 08b5891298 TMI-18: Preliminary fix for images get inverted colors. 2012-04-13 10:56:26 +02:00
Harald Kuhr f940fed152 Added test case for 1-bit/monochrome. 2012-04-04 16:02:17 +02:00
Harald Kuhr da9b94bdf3 Added test case for 64 color EHB (finally).
Rewrote EHB handling in CMAPChunk to fix bug.
Added test cases for 16 color indexed and 32 color indexed.
Removed obsolete test cases.
2012-04-03 16:58:04 +02:00
Harald Kuhr bf4ad6265a Implemented huffman decompression and "big line" changes for PCHG chunk. 2012-04-03 16:55:46 +02:00
Harald Kuhr e95cf300ba Removed unnecessary (duplicate) if-statement. 2012-04-02 14:26:52 +02:00
Harald Kuhr 13a4646ae4 Fixed a bug in the IFFImageWriter, caused by buffered data not being written to the stream. Adapter streams are now properly flushed/closed.
Test clean-up.
2012-04-02 14:25:28 +02:00
Harald Kuhr 36a05272a5 Added reader test case for IFF FORM type PBM.
Minor optimization in reading PBM data.
Clean-up.
2012-04-02 14:23:37 +02:00
Harald Kuhr a99c337348 Known issue IFFImageWriter. 2012-03-30 17:09:47 +02:00
Harald Kuhr 93e57306d5 Support for decoding JPEG quality setting based on DQT tables. 2012-03-30 16:59:49 +02:00
Harald Kuhr 0307237852 Better writer tests.
Fixed a bug in PICTWriter.
Minor changes in ImageReader/WriterBase classes.
2012-03-30 16:58:09 +02:00
Harald Kuhr 7431065519 Fixed duped test data. 2012-03-30 16:53:26 +02:00
Harald Kuhr de34ac7ede Now with source region/subsampling support also for multipalette. 2012-03-30 16:53:04 +02:00
Harald Kuhr 4463a00667 Added multi-palette support to IFFImageReader + minor clean-up. 2012-03-30 16:36:32 +02:00
Harald Kuhr 926359d9d2 New test cases.
Better exception handling of IMAGEERROR.
2012-03-21 10:13:03 +01:00
Harald Kuhr e712df3862 More test cases. 2012-03-20 17:49:44 +01:00
Harald Kuhr 6430841dcc More fine-grained tests for UUIDFactory. 2012-03-20 12:13:17 +01:00
Harald Kuhr f7bc246bad Better method naming.
Code cleanup.
Doc cleanup.
2012-03-13 09:17:20 +01:00
Harald Kuhr 07a5c62a28 Better UUID documentation + more tests. 2012-03-12 10:53:13 +01:00
Harald Kuhr 9cb21dbfc9 Added UUID factory for creating various Version 1 and Version 5 UUIDs. 2012-03-01 15:22:07 +01:00
Harald Kuhr 19ed19633c Fixed quote to more relevant part. :-) 2012-02-14 17:08:31 +01:00
Harald Kuhr 897da0ebca Fixed a threading issue. Thanks to Lachlan O'Dea <lodea@me.com> for pointing it out! 2012-02-13 15:02:08 +01:00
Harald Kuhr ff3d578806 Whoops. Adding missing reset method. 2012-02-09 15:29:13 +01:00
Harald Kuhr 7904fefcd4 Typo. 2012-02-09 15:27:42 +01:00
Harald Kuhr e3dcca854b Minor fix to the SOFn recognition + better warnings. 2012-02-07 13:05:49 +01:00
Harald Kuhr 11f9b2bdf8 Added JPEG image writer + spi (for completeness, delegates all the real work) 2012-02-07 13:05:05 +01:00
Harald Kuhr 39dafd48ca POM-fix. 2012-02-03 16:30:08 +01:00
Harald Kuhr 3efae7cfba Fixed a typo + removed a todo. 2012-02-02 17:00:08 +01:00
Harald Kuhr c3524adbbc Made EXIFReader more lenient while parsing.
- Now supports empty strings encoded with value count 0.
 - Added Rational.NaN constant to handle bad EXIF data.
Fixed a bug in the JPEGImageReader's raw EXIF thumbnail decoding.
Added test cases.
2012-02-02 16:55:01 +01:00
Harald Kuhr f2e3f7ed03 Implemented all-new JPEGSegmentIIS that filters out bad JPEG segments before passing on to the native reader.
Implemented JFIF, JFXX and EXIF thumbnail reading.
Added loads of test cases for special cases found in the wild.
2012-02-01 16:01:34 +01:00
Harald Kuhr 1830808d56 Minor fixes and clean-up. 2012-02-01 15:58:54 +01:00
Harald Kuhr cda19ece0d More tests. 2012-02-01 15:57:06 +01:00
Harald Kuhr ed441a7d6a Code clean-up. 2012-02-01 15:55:22 +01:00
Harald Kuhr 6c6c08a8f5 Added (long overdue) test cases for ServiceRegistry. 2012-02-01 15:54:37 +01:00
Harald Kuhr b92caf121d Added test cases + fixed some hard to find bugs. 2012-01-25 16:11:58 +01:00
Harald Kuhr d36d828110 Fixed typos. 2012-01-25 16:06:26 +01:00
Harald Kuhr c19338b5b9 Test cases for writers + renamed reader test cases to follow naming convention. 2012-01-20 13:52:34 +01:00
Harald Kuhr 3f381a9c4c Moving obsolete stuff into sandbox. 2012-01-13 17:19:39 +01:00
Harald Kuhr 84a2e8b10c Massive metadata cleanup, new test cases and bugfix. 2012-01-13 17:16:59 +01:00
Harald Kuhr 529377aa01 Fixed typo in doc. 2011-12-21 11:01:35 +01:00
Harald Kuhr 280407d9c0 Rewritten threading code to use latches, to avoid shutting down the executor service between steps. 2011-12-20 15:42:16 +01:00
Harald Kuhr 6ba32b657a Code clean-up, no functional changes. 2011-12-20 15:38:06 +01:00
Harald Kuhr 7435c12a80 Implemented (trivial) tests for dispose. 2011-12-20 15:35:38 +01:00
Harald Kuhr 7867aeae76 - Fixed issue with subsampling/source regions.
- Experimental support for custom ICC profiles with class output.
- Base work for extracting Exif thumbnail.
2011-12-20 15:33:38 +01:00
Harald Kuhr 5d6097baef Added test cases + workaround for some known issues with GIF encoding. 2011-12-20 12:45:41 +01:00
Harald Kuhr db526e07ec Major test-case cleanup.
- Removed JMock dependency, tests rewritten to use Mockito for stub/mock
- All test should now be using JUnit annotation-style tests
- All modules should now depend on same JUnit version
- Rewrote a few tests to better utilize JUnit annotations
- Fixed a few broken tests
- Code style changes
2011-12-19 14:34:49 +01:00
Harald Kuhr 8f452930ac Deleting obsolete class. 2011-12-19 14:32:59 +01:00
Harald Kuhr 49f5ab8e64 Code clean-up. 2011-12-19 14:30:40 +01:00
Harald Kuhr 0c4fc454b9 Major test-case cleanup.
- Removed JMock dependency, tests rewritten to use Mockito for stub/mock
- All test should now be using JUnit annotation-style tests
- All modules should now depend on same JUnit version
- Rewrote a few tests to better utilize JUnit annotations
- Fixed a few broken tests
- Code style changes
2011-12-19 14:28:34 +01:00
Harald Kuhr 52a97cfb2f Adding package-info files, that IDEA *silently deleted* instead of moving...?! :-P 2011-12-14 10:16:42 +01:00
Harald Kuhr 0b23d9f6c2 Moving old servlet stuff to sandbox. 2011-12-14 10:12:34 +01:00
Harald Kuhr 73fc08f8c1 Fixed image progress to work as specified. 2011-12-12 10:44:53 +01:00
Harald Kuhr 5d3fb34e49 Various fixes for metadata parsing.
- Added more TIFF/EXIF tags
- Clean-up of JPEG segment reading
- Better toString in general and XMP specific
2011-12-12 10:42:40 +01:00
Harald Kuhr 2a282cf8e4 Added test case for IIOOutputStreamAdapter + fixed bug in flush method.
Strengthened tests for IIOInputStreamAdapter
Minor clean up of the code.
2011-11-30 12:46:58 +01:00
Harald Kuhr d1e72d1ece Added methods for getting normalized list of formats supported by ImageIO + minor clean-up. 2011-11-28 15:07:43 +01:00
Harald Kuhr f130e654ef Encoding issue. 2011-11-28 12:10:53 +01:00
Harald Kuhr c006f22ac2 Rewrote handling of JPEG 2000 icons. Now returns blank image with correct dimensions + issues warning if can't be read, instead of exception.
Added quick fix conversion/reading for OS X using sips command line.
Updated test cases.
2011-11-28 12:02:00 +01:00
Harald Kuhr 905a3da97b Clean-up of reader. Better instantiation of provider in tests. No functional changes. 2011-11-25 12:47:47 +01:00
Harald Kuhr 158504de5d Added missing POM. 2011-11-24 16:25:33 +01:00
Harald Kuhr dc6b8d3035 Code clean-up. No functional changes. 2011-11-23 13:58:26 +01:00
Harald Kuhr 9742af9a5d Added test case for icon containing TOC_ + IC10 resources + fixed PNG reading and skipping of unknown resources.
Added test case for icon with no 8 bit mask + fixed fallback to 1 bit mask.
Added test case for icon with no mask + fixed transparency issue.
2011-11-14 15:25:21 +01:00
Harald Kuhr 3a9ad582f2 Replaced duped code. 2011-11-08 12:26:56 +01:00
Harald Kuhr 5782c8c824 Cleaned up + added som references to doc. 2011-11-08 12:24:40 +01:00
Harald Kuhr b5fd17ba24 Test clean-up. Strengthened some tests. Better error messages (stack traces). 2011-11-08 10:16:59 +01:00
Harald Kuhr 093fe2924b Added ICNS plugin to Maven project. 2011-11-08 10:15:55 +01:00
Harald Kuhr e867c2125c Now correctly merges masks and color data.
Added test case with icnV resource.
2011-11-07 18:13:24 +01:00
Harald Kuhr 17f3b97699 Fixed test case + code clean-up. 2011-11-01 14:09:34 +01:00
Harald Kuhr 8dcfb46bdb Test case clean-up + minor fixes. 2011-11-01 13:31:29 +01:00
Harald Kuhr 38b197f6c1 Fixed DateConverter to not have locale-aware formats (thanks, Fredrik Gustafsson).
Rewrote test cases for better readability/new code style.
2011-11-01 13:11:11 +01:00
Harald Kuhr 8edc448bf9 Layer support for PSD format. Still work in progress, but the basics is done. 2011-10-30 21:54:47 +01:00
Harald Kuhr 5857e27cf2 Charset issues. 2011-10-30 21:49:25 +01:00
Harald Kuhr 7ddc2c991e Better support for multiple images/windows in test code.
Code style update.
2011-10-30 20:37:48 +01:00
Harald Kuhr fe25b48804 Code style update, no functional changes. 2011-10-30 20:34:14 +01:00
Harald Kuhr 18abfcdbc2 Changed default indent from double space to tab. Minor clean-up. 2011-10-30 18:11:52 +01:00
Harald Kuhr 7546a9d2ab Clean up. Moved implementation to abstract class, and made exposed readers final. 2011-10-28 17:23:13 +02:00
Harald Kuhr 092474830d Start of a ICNS reader. 2011-10-28 17:21:25 +02:00
Harald Kuhr 7f0395e76b Fixed some serious bugs in the OLE2 compound documents and the internal streaming code.
Relaxed one test to comply with spec.
2011-10-20 18:12:20 +02:00
Harald Kuhr 0e11d6e2ae Fixed bugs in Seekable implementations. 2011-10-20 18:10:29 +02:00
Harald Kuhr cee29fb6a1 Major test overhaul part II, now uses JUnit 4 annotation style tests. 2011-10-18 20:28:06 +02:00
Harald Kuhr 9cafe4d9a9 Major test overhaul, now uses JUnit 4 annotation style tests. 2011-10-18 20:16:32 +02:00
Harald Kuhr 4b77d1c22a Clean-up, no functional change. 2011-10-13 09:55:02 +02:00
Harald Kuhr 1ba271af9d Minor fix for better error message. 2011-10-13 09:53:20 +02:00
Harald Kuhr 3e5da06e80 Clean-up 2011-09-02 16:18:23 +02:00
Harald Kuhr 7e8662772c Fixed an embarrassing NPE... 2011-08-29 17:07:45 +02:00
Harald Kuhr fbb51f0387 Added (Open)JDK 7 support (workaround for a bug, really) 2011-04-27 14:15:25 +02:00
Harald Kuhr a4d4111195 Added identifier (id() method) and made toString method return something more readable. 2011-04-27 14:13:48 +02:00
Harald Kuhr cb149a7c79 System.getProperty("os.name") reports "Darwin" on OpenJDK 7 for OS X.
It also reports "universal" for os.arch and "10.7.0" for os.version, which is probably hardcoded and incorrect. :-/
2011-04-19 16:47:17 +02:00
Harald Kuhr cb4a35016b Removed TODO. 2011-04-15 17:07:03 +02:00
Harald Kuhr c6d6a86343 Expanded test cases, fixed a potential NPE. 2011-04-15 17:05:47 +02:00
Harald Kuhr 521c4e4bbc Added test case for ImageFilter + implemented changes suggested by Rune Bremnes. 2011-04-15 10:35:21 +02:00
Harald Kuhr b5b1b4f422 New code style, no functional changes. 2011-04-07 16:07:17 +02:00
Harald Kuhr 3f98534011 Fixed doc links. 2011-04-07 13:48:10 +02:00
Harald Kuhr 63b5ae9994 Removed debug output accidentally committed.
Fixed a test.
2011-03-10 16:58:26 +01:00
Harald Kuhr b45f2ac09f Removed non-UTF characters from source files. 2011-03-10 16:57:31 +01:00
Harald Kuhr 65ee6771ca Added test cases for JPEGSegment reading. 2011-03-02 17:28:18 +01:00
Harald Kuhr af7d5fa94a Various code clean-up. No functional changes. 2011-03-02 17:26:17 +01:00
Harald Kuhr e75741ccd3 Replaced UTF characters with unicode escapes in source files. 2011-03-02 17:25:18 +01:00
Harald Kuhr 2abee4653b Added TODO. 2011-03-02 17:24:25 +01:00
Harald Kuhr 5545a08854 Doc fix + minor clean-up 2011-02-25 16:26:33 +01:00
Harald Kuhr 2d04b8d484 Added fast conversion from CMYK to RGB for non-ICC cases. 2011-02-23 19:16:47 +01:00
Harald Kuhr 38ab0d936a Added deprecations and todo to replace old legacy code with regexp. 2011-02-23 19:15:57 +01:00
Harald Kuhr 720752149e Removed non-UTF characters from source files. 2011-02-23 19:14:51 +01:00
Harald Kuhr d1943f9f49 Made JMgaick dependency provided. 2011-02-23 19:12:45 +01:00
Harald Kuhr 403dff946b Fixed a typo. 2011-02-22 15:58:01 +01:00
Harald Kuhr a014698a45 Removed final TODO. 2011-02-22 15:27:00 +01:00
Harald Kuhr 6cc97e3721 Added caching of AdobeRGB1998 and Generic CMYK profiles for use in getInstance methods.
Added debug info.
Refactored profile reading slightly.
2011-02-22 15:25:19 +01:00
Harald Kuhr ba4ff3dc45 New code style. No functional changes. 2011-02-21 17:34:15 +01:00
Harald Kuhr c60f80aacb Implemented properties-based ICC profile lookup. 2011-02-21 17:30:45 +01:00
Harald Kuhr dba1fa20da Removed non-UTF characters from source files. 2011-02-19 15:45:58 +01:00
Harald Kuhr 26128bf7ea Removed sandbox from default build. 2011-02-18 09:43:48 +01:00
Harald Kuhr c438036ee2 Added headless to surefire execution. 2011-02-18 09:35:20 +01:00
Harald Kuhr f1a4a79003 New code style. No functional changes. 2011-02-17 21:42:36 +01:00
Harald Kuhr dda46b0ab9 Added experimental JavaFX to POM. 2011-02-17 21:32:41 +01:00
Harald Kuhr 32a8fe497a Added a TODO for missing functionality. 2011-02-17 21:31:50 +01:00
Harald Kuhr 5accfc9cf0 Fixed a potential NPE. 2011-02-17 21:31:14 +01:00
Harald Kuhr 87118aecee New code style. No functional changes. 2011-02-17 17:58:55 +01:00
Harald Kuhr 770f948e1a New code style. No functional changes. 2011-02-17 17:54:50 +01:00
Harald Kuhr 5bd896f80f New code style. No functional changes. 2011-02-17 16:53:08 +01:00
Harald Kuhr 1433a24052 New Servlet configurator. 2011-02-17 12:49:07 +01:00
Harald Kuhr 066b902a5e New Servlet configurator. 2011-02-17 12:46:16 +01:00
Harald Kuhr 20b87d155d Mainly new code standard.
A few changes that should have been committed earlier.. :-/
2011-02-17 12:40:49 +01:00
Harald Kuhr 41b8080683 Updated done. 2011-02-17 12:37:37 +01:00
Harald Kuhr 43cc440e67 New code style. No functional changes. 2011-02-17 12:36:40 +01:00
Harald Kuhr 191643a36c Clean-up and minor changes in core classes.
Adapted new code style.
No or few functional changes.
2011-02-16 22:29:23 +01:00
Harald Kuhr df0d3f90e8 Work in progress: Brand new JPEGImageReader capable of reading CMYK JPEG and images with "broken" color profiles. 2011-02-16 22:24:59 +01:00
Harald Kuhr 47ab16457a Renamed CS constant to be more consistent. 2011-02-16 22:15:18 +01:00
Harald Kuhr 5c6c9e3e26 Minor clean up and doc changes. 2011-02-16 22:13:12 +01:00
Harald Kuhr d772674223 Minor clean up and doc changes. 2011-02-16 22:11:03 +01:00
Harald Kuhr 4d9661f950 Added JPEG utils. 2011-02-14 16:59:38 +01:00
Harald Kuhr 8d5b775851 Added classes for color management. 2011-02-08 15:08:22 +01:00
Harald Kuhr f610c82cb7 Updated some copyright statements. 2010-10-13 17:04:11 +02:00
Harald Kuhr f419cfecdd Cleaned up the NIO buffered image classes. 2010-10-12 10:40:55 +02:00
Harald Kuhr 9d6f263b86 Cleaned up the NIO buffered image classes. 2010-06-13 20:29:25 +02:00
Harald Kuhr 7d4d007975 Rewritten the MappedBufferImage class, should now perform a lot better. 2010-06-13 00:50:28 +02:00
Harald Kuhr c982203361 Added sandbox-swing to sandbox. 2010-06-07 10:41:50 +02:00
Harald Kuhr 724d893ea6 Minor clean-up and fixing some typos. 2010-06-07 09:56:45 +02:00
Harald Kuhr b6ee5ce450 Clean-up of sandbox, rearranging everything.
Added a couple of files that was never commited.
2010-06-07 09:55:35 +02:00
Harald Kuhr 1f60b62626 Some serious project clean-up. 2010-06-07 09:53:02 +02:00
Harald Kuhr 823854d40e Fixed known problem with listeners being removed inside the imageProgress callback.
Clean-up, removed out-commented code, fixed typos etc.
2010-05-07 12:42:56 +02:00
Harald Kuhr e468484d68 Clean-up, fxed typos etc. 2010-04-30 14:33:23 +02:00
Harald Kuhr 037c0d078a Sandbox clean-up 2010-04-20 19:55:44 +02:00
Harald Kuhr b643324ddb Merge commit 'hamnis/master' 2010-02-07 21:47:45 +01:00
Erlend Hamnaberg f6bb0aeb1b set twelvemonkeys as prefix to artifact 2010-02-07 21:46:51 +01:00
Harald Kuhr f3218d5819 Minor POM chamges/clean-up. 2010-02-07 21:40:53 +01:00
Harald Kuhr df27411c82 Merge commit 'hamnis/master'
Conflicts:
	imageio/imageio-batik/pom.xml
2010-02-07 21:00:52 +01:00
Harald Kuhr 67e623f666 Merge commit 'hamnis/master'
Conflicts:
	imageio/imageio-batik/pom.xml
	imageio/imageio-core/pom.xml
	imageio/imageio-ico/pom.xml
	imageio/imageio-iff/pom.xml
	imageio/imageio-metadata/pom.xml
	imageio/imageio-pict/pom.xml
	imageio/imageio-psd/pom.xml
	imageio/imageio-reference/pom.xml
	imageio/imageio-thumbsdb/pom.xml
	twelvemonkeys-core/pom.xml
	twelvemonkeys-imageio/pom.xml
	twelvemonkeys-servlet/pom.xml
2010-02-07 20:43:44 +01:00
Erlend Hamnaberg 3050899903 Renamed servlet to own groupId. Fixed naming of ImageIO 2010-02-07 19:17:13 +01:00
Erlend Hamnaberg abdecf1d5e Reenabled now fixed test case 2010-02-07 19:07:05 +01:00
Erlend Hamnaberg 673527abe5 Merge branch 'master' of git://github.com/haraldk/TwelveMonkeys 2010-02-07 19:03:55 +01:00
Erlend Hamnaberg 830035bcec 2.3-SNAPSHOT to 3.0-SNAPSHOT 2010-01-29 20:06:18 +01:00
Erlend Hamnaberg 41853cec7e disabled some failing test cases and moved test case to correct spot 2010-01-29 20:03:37 +01:00
Erlend Hamnaberg 5b7fcd5c95 Fixed some pom issues 2010-01-29 19:47:05 +01:00
Erlend Hamnaberg ee99550a65 Moving the rest 2010-01-29 19:40:12 +01:00
Erlend Hamnaberg 40e6486154 Moved files after merge 2010-01-29 19:36:02 +01:00
Erlend Hamnaberg 38d6c58eba Merge branch 'master' into 3.0-structure
Conflicts:
	imageio/imageio-psd/pom.xml
	twelvemonkeys-imageio/pom.xml
	twelvemonkeys-servlet/pom.xml
2010-01-29 19:27:10 +01:00
Erlend Hamnaberg c310083d2a Merge branch 'master' of git://github.com/haraldk/TwelveMonkeys 2010-01-24 23:13:33 +01:00
Erlend Hamnaberg ec4334cbb5 Merge commit 'upstream/master' 2009-11-11 21:47:48 +01:00
Erlend Hamnaberg 27553dc47a Revert "Work in progress for PSD metadata support:"
This reverts commit b5f6c96583.
2009-11-11 21:47:31 +01:00
Erlend Hamnaberg e8a4cc048c Revert "Work in progress for PSD metadata support:"
This reverts commit 0d41db32cf.
2009-11-11 21:45:53 +01:00
Erlend Hamnaberg f8b716687c cleanup 2009-11-11 20:59:24 +01:00
Erlend Hamnaberg 0786949c1c It all works 2009-11-08 19:52:30 +01:00
Erlend Hamnaberg b8faa6e36f Sandbox 2009-11-08 19:19:46 +01:00
Erlend Hamnaberg 7167a7a4ad Core now moved to common. 2009-11-08 19:01:36 +01:00
Erlend Hamnaberg e0a6c0a2bd Missing file 2009-11-08 18:47:32 +01:00
Erlend Hamnaberg 6eaac4ec8b Fixed compile 2009-11-08 18:40:48 +01:00
Erlend Hamnaberg b3aa378f16 moving files around 2009-11-08 18:40:32 +01:00
Erlend Hamnaberg ad913b5093 fix coming problem 2009-11-08 18:40:17 +01:00
Erlend Hamnaberg 9b615de8ed Moving files around 2009-11-08 18:39:58 +01:00
Erlend Hamnaberg ba5f0a2f5f fix dependencies 2009-11-08 18:39:48 +01:00
Erlend Hamnaberg 2016be8f9a removed redundant files 2009-11-08 18:39:33 +01:00
Erlend Hamnaberg 45a42ea8a3 New structure 2009-11-08 18:39:14 +01:00
Harald Kuhr 0d41db32cf Work in progress for PSD metadata support:
- Implemented more of standard support
 - Changes to native format spec
 - Implemented more of native format
 - Minor changes in various resources due to meta data implementation
2009-11-07 09:41:40 +08:00
Harald Kuhr b5f6c96583 Work in progress for PSD metadata support:
- Added PSDMetadata and PSDMetadataFormat
 - Implemented most of standard format
 - Start of native format definintion
 - Updated SPI and Reader to return new format
2009-11-07 09:41:40 +08:00
Harald Kuhr 34d874d69d Removed an old file that shouldn't have been committed... 2009-11-07 09:41:39 +08:00
1022 changed files with 56438 additions and 19394 deletions
-1
View File
@@ -1 +0,0 @@
We did it
+527
View File
@@ -0,0 +1,527 @@
## Background
TwelveMonkeys ImageIO is a collection of plug-ins for Java's ImageIO.
These plugins extends the number of image file formats supported in Java, using the javax.imageio.* package.
The main purpose of this project is to provide support for formats not covered by the JDK itself.
Support for formats is important, both to be able to read data found
"in the wild", as well as to maintain access to data in legacy formats.
Because there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
----
## Features
Mainstream format support
#### JPEG
* Read support for the following JPEG flavors:
* YCbCr JPEGs without JFIF segment (converted to RGB, using embedded ICC profile)
* CMYK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile )
* Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
* JPEGs containing ICC profiles with interpretation other than 'Perceptual'
* JPEGs containing ICC profiles with class other than 'Display'
* JPEGs containing ICC profiles that are incompatible with stream data
* JPEGs with corrupted ICC profiles
* JPEGs with corrupted `ICC_PROFILE` segments
* JPEGs using non-standard color spaces, unsupported by Java 2D
* Issues warnings instead of throwing exceptions in cases of corrupted or non-conformant data where ever the image data can still be read in a reasonable way
* Thumbnail support:
* JFIF thumbnails (even if stream contains inconsistent metadata)
* JFXX thumbnails (JPEG, Indexed and RGB)
* EXIF thumbnails (JPEG, RGB and YCbCr)
* Metadata support:
* JPEG metadata in both standard and native formats (even if stream contains inconsistent metadata)
* `javax_imageio_jpeg_image_1.0` format (currently as native format, may change in the future)
* Illegal combinations of JFIF, Exif and Adobe markers, using "unknown" segments in the
"MarkerSequence" tag for the unsupported segments (for `javax_imageio_jpeg_image_1.0` format)
* Extended write support in progress:
* CMYK JPEGs
* YCCK JPEGs
#### JPEG-2000
* Possibly coming in the future, pending some license issues.
If you are one of the authors, or know one of the authors and/or the current license holders of either the original jj2000 package or the JAI ImageIO project, please contact me
(I've tried to get in touch in various ways, without success so far).
#### Adobe Photoshop Document (PSD)
* Read support for the following file types:
* Monochrome, 1 channel, 1 bit
* Indexed, 1 channel, 8 bit
* Gray, 1 channel, 8, 16 and 32 bit
* Duotone, 1 channel, 8, 16 and 32 bit
* RGB, 3-4 channels, 8, 16 and 32 bit
* CMYK, 4-5 channels, 8, 16 and 32 bit
* Read support for the following compression types:
* Uncompressed
* RLE (PackBits)
* Layer support
* Image layers only, in all of the above types
* Thumbnail support
* JPEG
* RAW (RGB)
* Support for "Large Document Format" (PSB)
#### Aldus/Adobe Tagged Image File Format (TIFF)
* Read support for the following "Baseline" TIFF file types:
* Class B (Bi-level), all relevant compression types, 1 bit per sample
* Class G (Gray), all relevant compression types, 2, 4, 8, 16 or 32 bits per sample, unsigned integer
* Class P (Palette/indexed color), all relevant compression types, 1, 2, 4, 8 or 16 bits per sample, unsigned integer
* Class R (RGB), all relevant compression types, 8 or 16 bits per sample, unsigned integer
* Read support for the following TIFF extensions:
* Tiling
* LZW Compression (type 5)
* "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined
* JPEG Compression (type 7)
* ZLib (aka Adobe-style Deflate) Compression (type 8)
* Deflate Compression (type 32946)
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression
* Alpha channel (ExtraSamples type 1/Associated Alpha)
* CMYK data (PhotometricInterpretation type 5/Separated)
* YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
* Planar data (PlanarConfiguration type 2/Planar)
* ICC profiles (ICCProfile)
* BitsPerSample values up to 16 for most PhotometricInterpretations
* Multiple images (pages) in one file
* Write support in progress
* Will support writing most "Baseline" TIFF file types
#### Apple Mac Paint Picture Format (PICT)
* Legacy format, especially useful for reading OS X clipboard data.
* Read support for the following file types:
* QuickDraw (format support is not complete, but supports most OS X clipboard data as well as RGB pixel data)
* QuickDraw bitmap
* QuickDraw pixmap
* QuickTime stills
* Write support for RGB pixel data:
* QuickDraw pixmap
#### Commodore Amiga/Electronic Arts Interchange File Format (IFF)
* Legacy format, allows reading popular image from the Commodore Amiga computer.
* Read support for the following file types:
* ILBM Indexed color, 1-8 interleaved bit planes, including 6 bit EHB
* ILBM Gray, 8 bit interleaved bit planes
* ILBM RGB, 24 and 32 bit interleaved bit planes
* ILBM HAM6 and HAM8
* PBM Indexed color, 1-8 bit,
* PBM Gray, 8 bit
* PBM RGB, 24 and 32 bit
* PBM HAM6 and HAM8
* Write support
* ILBM Indexed color, 1-8 bits per sample, 8 bit gray, 24 and 32 bit true color.
* Support for the following compression types (read/write):
* Uncompressed
* RLE (PackBits)
Icon/other formats
#### Apple Icon Image (ICNS)
* Read support for the following icon types:
* All known "native" icon types
* Large PNG encoded icons
* Large JPEG 2000 encoded icons (requires JPEG 2000 ImageIO plugin or fallback to `sips` command line tool)
#### MS Windows Icon and Cursor Formats (ICO & CUR)
* Read support for the following file types:
* ICO Indexed color, 1, 4 and 8 bit
* ICO RGB, 16, 24 and 32 bit
* CUR Indexed color, 1, 4 and 8 bit
* CUR RGB, 16, 24 and 32 bit
#### MS Windows Thumbs DB (Thumbs.db)
* Read support
Other formats, using 3rd party libraries
#### Scalable Vector Graphics (SVG)
* Read-only support using Batik
#### MS Windows MetaFile (WMF)
* Limited read-only support using Batik
## Basic usage
Most of the time, all you need to do is simply include the plugins in your project and write:
BufferedImage image = ImageIO.read(file);
This will load the first image of the file, entirely into memory.
The basic and simplest form of writing is:
if (!ImageIO.write(image, format, file)) {
// Handle image not written case
}
This will write the entire image into a single file, using the default settings for the given format.
The plugins are discovered automatically at run time. See the [FAQ](#faq) for more info on how this mechanism works.
## Advanced usage
If you need more control of read parameters and the reading process, the common idiom for reading is something like:
// Create input stream
ImageInputStream input = ImageIO.createImageInputStream(file);
try {
// Get the reader
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) {
throw new IllegalArgumentException("No reader for: " + file);
}
ImageReader reader = readers.next();
try {
reader.setInput(input);
// Optionally, listen for read warnings, progress, etc.
reader.addIIOReadWarningListener(...);
reader.addIIOReadProgressListener(...);
ImageReadParam param = reader.getDefaultReadParam();
// Optionally, control read settings like sub sampling, source region or destination etc.
param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Finally read the image, using settings from param
BufferedImage image = reader.read(0, param);
// Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// ...
}
finally {
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
}
finally {
// Close stream in finally block to avoid resource leaks
input.close();
}
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
entire image into memory first.
It's also possible to read multiple images from the same file in a loop, using `reader.getNumImages()`.
If you need more control of write parameters and the writing process, the common idiom for writing is something like:
// Get the writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
if (!writers.hasNext()) {
throw new IllegalArgumentException("No writer for: " + format);
}
ImageWriter writer = writers.next();
try {
// Create output stream
ImageOutputStream output = ImageIO.createImageOutputStream(file);
try {
writer.setOutput(output);
// Optionally, listen to progress, warnings, etc.
ImageWriteParam param = writer.getDefaultWriteParam();
// Optionally, control format specific settings of param (requires casting), or
// control generic write settings like sub sampling, source region, output type etc.
// Optionally, provide thumbnails and image/stream metadata
writer.write(..., new IIOImage(..., image, ...), param);
}
finally {
// Close stream in finally block to avoid resource leaks
output.close();
}
}
finally {
// Dispose writer in finally block to avoid memory leaks
writer.dispose();
}
For more advanced usage, and information on how to use the ImageIO API, I suggest you read the
[Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
from Oracle.
#### Deploying the plugins in a web app
Because the `ImageIO` plugin registry (the `IIORegistry`) is "VM global", it doesn't by default work well with
servlet contexts. This is especially evident if you load plugins from the `WEB-INF/lib` or `classes` folder.
Unless you add `ImageIO.scanForPlugins()` somewhere in your code, the plugins might never be available at all.
I addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context).
If you restart your application, old classes will by default remain in memory forever (because the next time
`scanForPlugins` is called, it's another `ClassLoader` that scans/loads classes, and thus they will be new instances
in the registry). If a read is attempted using one of the remaining ("old") readers, weird exceptions
(like `NullPointerException`s when accessing `static final` initialized fields) may occur.
To work around both the discovery problem and the resource leak,
it is recommended to use the `IIOProviderContextListener` that implements
dynamic loading and unloading of ImageIO plugins for web applications.
<web-app ...>
...
<listener>
<display-name>ImageIO service provider loader/unloader</display-name>
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
</listener>
...
</web-app>
#### Using the ResampleOp
The library comes with a resampling (image resizing) operation, that contains many different algorithms
to provide excellent results at reasonable speed.
import com.twelvemonkeys.image.ResampleOp;
...
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
BufferedImage output = resampler.filter(input, null);
#### Using the DiffusionDither
The library comes with a dithering operation, that can be used to convert `BufferedImage`s to `IndexColorModel` using
Floyd-Steinberg error-diffusion dither.
import com.twelvemonkeys.image.DiffusionDither;
...
BufferedImage input = ...; // Image to dither
BufferedImageOp ditherer = new DiffusionDither();
BufferedImage output = ditherer.filter(input, null);
## Building
Download the project (using [Git](http://git-scm.com/downloads)):
$ git clone git@github.com:haraldk/TwelveMonkeys.git
This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys`
folder, and issue the command below to build.
Build the project (using [Maven](http://maven.apache.org/download.cgi)):
$ mvn package
Currently, the only supported JDK for making a build is Oracle JDK 7.x.
It's possible to build using OpenJDK, but some tests will fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether. To build using JDK 8, you need to pass `-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider` to revert to the color manangement system used in Java 7.
Because the unit tests needs quite a bit of memory to run, you might have to set the environment variable `MAVEN_OPTS`
to give the Java process that runs Maven more memory. I suggest something like `-Xmx512m -XX:MaxPermSize=256m`.
Optionally, you can install the project in your local Maven repository using:
$ mvn install
## Installing
To install the plug-ins,
either use Maven and add the necessary dependencies to your project,
or manually add the needed JARs along with required dependencies in class-path.
The ImageIO registry and service lookup mechanism will make sure the plugins are available for use.
To verify that the JPEG plugin is installed and used at run-time, you could use the following code:
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
while (readers.hasNext()) {
System.out.println("reader: " + readers.next());
}
The first line should print:
reader: com.twelvemonkeys.imageio.jpeg.JPEGImageReader@somehash
#### Maven dependency example
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
...
<dependencies>
...
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.0-rc5</version> <!-- Alternatively, build your own 3.0-something version -->
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.0-rc5</version> <!-- Alternatively, build your own 3.0-something version -->
</dependency>
</dependencies>
#### Manual dependency example
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.0-rc5.jar
twelvemonkeys-common-io-3.0-rc5.jar
twelvemonkeys-common-image-3.0-rc5.jar
twelvemonkeys-imageio-core-3.0-rc5.jar
twelvemonkeys-imageio-metadata-3.0-rc5.jar
twelvemonkeys-imageio-jpeg-3.0-rc5.jar
twelvemonkeys-imageio-tiff-3.0-rc5.jar
### Links to prebuilt binaries
Common dependencies
* [common-lang-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0-rc5/common-lang-3.0-rc5.jar)
* [common-io-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0-rc5/common-io-3.0-rc5.jar)
* [common-image-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0-rc5/common-image-3.0-rc5.jar)
ImageIO dependencies
* [imageio-core-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0-rc5/imageio-core-3.0-rc5.jar)
* [imageio-metadata-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0-rc5/imageio-metadata-3.0-rc5.jar)
ImageIO plugins
* [imageio-jpeg-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0-rc5/imageio-jpeg-3.0-rc5.jar)
* [imageio-tiff-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0-rc5/imageio-tiff-3.0-rc5.jar)
* [imageio-psd-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0-rc5/imageio-psd-3.0-rc5.jar)
* [imageio-pict-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0-rc5/imageio-pict-3.0-rc5.jar)
* [imageio-iff-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0-rc5/imageio-iff-3.0-rc5.jar)
* [imageio-icns-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0-rc5/imageio-icns-3.0-rc5.jar)
* [imageio-ico-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0-rc5/imageio-ico-3.0-rc5.jar)
* [imageio-thumbsdb-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0-rc5/imageio-thumbsdb-3.0-rc5.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0-rc5/imageio-batik-3.0-rc5.jar)
* [imageio-jmagick-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0-rc5/imageio-jmagick-3.0-rc5.jar)
Servlet support
* [servlet-3.0-rc5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0-rc5/servlet-3.0-rc5.jar)
## License
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
Copyright (c) 2008-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:
o Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
o 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.
o 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.
## FAQ
q: How do I use it?
a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need.
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
q: What changes do I have to make to my code in order to use the plug-ins?
a: The short answer is: None. For basic usage, like ImageIO.read(...) or ImageIO.getImageReaders(...), there is no need
to change your code. Most of the functionality is available through standard ImageIO APIs, and great care has been taken
not to introduce extra API where none is necessary.
Should you want to use very specific/advanced features of some of the formats, you might have to use specific APIs, like
setting base URL for an SVG image that consists of multiple files,
or controlling the output compression of a TIFF file.
q: How does it work?
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO.
ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
TODO: Describe SPI mechanism.
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath.
The fine print: The TwelveMonkeys service providers for TIFF and JPEG overrides the onRegistration method, and
utilizes the pairwise partial ordering mechanism of the IIOServiceRegistry to make sure it is installed before
the Sun/Oracle provided JPEGImageReader and the Apple provided TIFFImageReader on OS X, respectively.
Using the pairwise ordering will not remove any functionality form these implementations, but in most cases you'll end
up using the TwelveMonkeys plug-ins instead.
q: What about JAI? Several of the formats are already supported by JAI.
a: While JAI (and jai-imageio in particular) have support for some of the formats, JAI has some major issues.
The most obvious being:
- It's not actively developed. No issues has been fixed for years.
- To get full format support, you need native libs.
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
Some environments may also prevent deployment of native libs, which brings us back to square one.
q: What about JMagick or IM4Java? Can't you just use what´s already available?
a: While great libraries with a wide range of formats support, the ImageMagick-based libraries has some disadvantages
compared to ImageIO.
- No real stream support, these libraries only work with files.
- No easy access to pixel data through standard Java2D/BufferedImage API.
- Not a pure Java solution, requires system specific native libs.
-----
We did it
-20
View File
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>2.0</version>
<packaging>pom</packaging>
<name>Twelvemonkeys all (aggregator)</name>
<modules>
<module>twelvemonkeys-core</module>
<module>twelvemonkeys-servlet</module>
<module>twelvemonkeys-swing</module>
<module>twelvemonkeys-imageio</module>
<module>twelvemonkeys-sandbox</module>
</modules>
</project>
+35
View File
@@ -0,0 +1,35 @@
<?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.common</groupId>
<artifactId>common</artifactId>
<version>3.0.3-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Image</name>
<description>
The TwelveMonkeys Common Image support
</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-io</artifactId>
</dependency>
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId>
<version>6.2.4</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -41,21 +41,24 @@ import java.util.ArrayList;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/AbstractImageSource.java#1 $
*/
public abstract class AbstractImageSource implements ImageProducer {
private List<ImageConsumer> mConsumers = new ArrayList<ImageConsumer>();
protected int mWidth;
protected int mHeight;
protected int mXOff;
protected int mYOff;
private List<ImageConsumer> consumers = new ArrayList<ImageConsumer>();
protected int width;
protected int height;
protected int xOff;
protected int yOff;
// ImageProducer interface
public void addConsumer(ImageConsumer pConsumer) {
if (mConsumers.contains(pConsumer)) {
public void addConsumer(final ImageConsumer pConsumer) {
if (consumers.contains(pConsumer)) {
return;
}
mConsumers.add(pConsumer);
consumers.add(pConsumer);
try {
initConsumer(pConsumer);
sendPixels(pConsumer);
if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
@@ -68,34 +71,35 @@ public abstract class AbstractImageSource implements ImageProducer {
}
catch (Exception e) {
e.printStackTrace();
if (isConsumer(pConsumer)) {
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
}
}
}
public void removeConsumer(ImageConsumer pConsumer) {
mConsumers.remove(pConsumer);
public void removeConsumer(final ImageConsumer pConsumer) {
consumers.remove(pConsumer);
}
/**
* This implementation silently ignores this instruction. If pixeldata is
* This implementation silently ignores this instruction. If pixel data is
* not in TDLR order by default, subclasses must override this method.
*
* @param pConsumer the consumer that requested the resend
*
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
*/
public void requestTopDownLeftRightResend(ImageConsumer pConsumer) {
public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) {
// ignore
}
public void startProduction(ImageConsumer pConsumer) {
public void startProduction(final ImageConsumer pConsumer) {
addConsumer(pConsumer);
}
public boolean isConsumer(ImageConsumer pConsumer) {
return mConsumers.contains(pConsumer);
public boolean isConsumer(final ImageConsumer pConsumer) {
return consumers.contains(pConsumer);
}
protected abstract void initConsumer(ImageConsumer pConsumer);
@@ -47,33 +47,34 @@ import java.io.IOException;
*/
public class AreaAverageOp implements BufferedImageOp, RasterOp {
final private int mWidth;
final private int mHeight;
final private int width;
final private int height;
private Rectangle mSourceRegion;
private Rectangle sourceRegion;
public AreaAverageOp(final int pWidth, final int pHeight) {
mWidth = pWidth;
mHeight = pHeight;
width = pWidth;
height = pHeight;
}
public Rectangle getSourceRegion() {
if (mSourceRegion == null) {
if (sourceRegion == null) {
return null;
}
return new Rectangle(mSourceRegion);
return new Rectangle(sourceRegion);
}
public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) {
mSourceRegion = null;
sourceRegion = null;
}
else {
if (mSourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion);
if (sourceRegion == null) {
sourceRegion = new Rectangle(pSourceRegion);
}
else {
mSourceRegion.setBounds(pSourceRegion);
sourceRegion.setBounds(pSourceRegion);
}
}
}
@@ -93,7 +94,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
long start = System.currentTimeMillis();
// Straight-forward version
//Image scaled = src.getScaledInstance(mWidth, mHeight, Image.SCALE_AREA_AVERAGING);
//Image scaled = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
//ImageUtil.drawOnto(result, scaled);
//result = new BufferedImageFactory(scaled).getBufferedImage();
@@ -104,7 +105,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
AffineTransform xform = null;
int w = src.getWidth();
int h = src.getHeight();
while (w / 2 > mWidth && h / 2 > mHeight) {
while (w / 2 > width && h / 2 > height) {
w /= 2;
h /= 2;
@@ -129,7 +130,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
src = temp.getSubimage(0, 0, w, h);
}
resample(src, result, AffineTransform.getScaleInstance(mWidth / (double) w, mHeight / (double) h));
resample(src, result, AffineTransform.getScaleInstance(width / (double) w, height / (double) h));
*/
// The real version
@@ -160,11 +161,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
if (mSourceRegion != null) {
int cx = mSourceRegion.x;
int cy = mSourceRegion.y;
int cw = mSourceRegion.width;
int ch = mSourceRegion.height;
if (sourceRegion != null) {
int cx = sourceRegion.x;
int cy = sourceRegion.y;
int cw = sourceRegion.width;
int ch = sourceRegion.height;
boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
@@ -179,11 +180,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
// TODO: This don't work too well..
// The thing is that the step length and the scan length will vary, for
// non-even (1/2, 1/4, 1/8 etc) resampling
int widthSteps = (width + mWidth - 1) / mWidth;
int heightSteps = (height + mHeight - 1) / mHeight;
int widthSteps = (width + this.width - 1) / this.width;
int heightSteps = (height + this.height - 1) / this.height;
final boolean oddX = width % mWidth != 0;
final boolean oddY = height % mHeight != 0;
final boolean oddX = width % this.width != 0;
final boolean oddY = height % this.height != 0;
final int dataElements = src.getNumDataElements();
final int bands = src.getNumBands();
@@ -210,16 +211,16 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
}
}
for (int y = 0; y < mHeight; y++) {
if (!oddY || y < mHeight) {
for (int y = 0; y < this.height; y++) {
if (!oddY || y < this.height) {
scanH = heightSteps;
}
else {
scanH = height - (y * heightSteps);
}
for (int x = 0; x < mWidth; x++) {
if (!oddX || x < mWidth) {
for (int x = 0; x < this.width; x++) {
if (!oddX || x < this.width) {
scanW = widthSteps;
}
else {
@@ -243,8 +244,8 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
//
//System.err.println("width: " + width);
//System.err.println("height: " + height);
//System.err.println("mWidth: " + mWidth);
//System.err.println("mHeight: " + mHeight);
//System.err.println("width: " + width);
//System.err.println("height: " + height);
//
//e.printStackTrace();
continue;
@@ -382,20 +383,20 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
ColorModel cm = destCM != null ? destCM : src.getColorModel();
return new BufferedImage(cm,
ImageUtil.createCompatibleWritableRaster(src, cm, mWidth, mHeight),
ImageUtil.createCompatibleWritableRaster(src, cm, width, height),
cm.isAlphaPremultiplied(), null);
}
public WritableRaster createCompatibleDestRaster(Raster src) {
return src.createCompatibleWritableRaster(mWidth, mHeight);
return src.createCompatibleWritableRaster(width, height);
}
public Rectangle2D getBounds2D(Raster src) {
return new Rectangle(mWidth, mHeight);
return new Rectangle(width, height);
}
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(mWidth, mHeight);
return new Rectangle(width, height);
}
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
@@ -60,14 +60,16 @@ import java.awt.image.RGBImageFilter;
public class BrightnessContrastFilter extends RGBImageFilter {
// TODO: Replace with RescaleOp?
// This filter can filter IndexColorModel, as it is does not depend on
// the pixels' location
{
canFilterIndexColorModel = true;
}
// Use a precalculated lookup table for performace
private int[] mLUT = null;
// Use a pre-calculated lookup table for performance
private final int[] LUT;
/**
* Creates a BrightnessContrastFilter with default values
@@ -105,7 +107,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
* {@code -1.0,..,0.0,..,1.0}.
*/
public BrightnessContrastFilter(float pBrightness, float pContrast) {
mLUT = createLUT(pBrightness, pContrast);
LUT = createLUT(pBrightness, pContrast);
}
private static int[] createLUT(float pBrightness, float pContrast) {
@@ -149,7 +151,6 @@ public class BrightnessContrastFilter extends RGBImageFilter {
*
* @return the filtered pixel value in the default color space
*/
public int filterRGB(int pX, int pY, int pARGB) {
// Get color components
int r = pARGB >> 16 & 0xFF;
@@ -157,9 +158,9 @@ public class BrightnessContrastFilter extends RGBImageFilter {
int b = pARGB & 0xFF;
// Scale to new contrast
r = mLUT[r];
g = mLUT[g];
b = mLUT[b];
r = LUT[r];
g = LUT[g];
b = LUT[b];
// Return ARGB pixel, leave transparency as is
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
@@ -28,6 +28,8 @@
package com.twelvemonkeys.image;
import com.twelvemonkeys.lang.Validate;
import javax.swing.Icon;
import java.awt.image.BufferedImage;
import java.awt.*;
@@ -41,51 +43,48 @@ import java.awt.geom.AffineTransform;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $
*/
public class BufferedImageIcon implements Icon {
private final BufferedImage mImage;
private int mWidth;
private int mHeight;
private final boolean mFast;
private final BufferedImage image;
private int width;
private int height;
private final boolean fast;
public BufferedImageIcon(BufferedImage pImage) {
this(pImage, pImage.getWidth(), pImage.getHeight());
this(pImage, pImage != null ? pImage.getWidth() : 0, pImage != null ? pImage.getHeight() : 0);
}
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
if (pWidth <= 0 || pHeight <= 0) {
throw new IllegalArgumentException("Icon size must be positive");
}
this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight);
}
mImage = pImage;
mWidth = pWidth;
mHeight = pHeight;
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) {
image = Validate.notNull(pImage, "image");
width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d");
height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d");
mFast = pImage.getWidth() == mWidth && pImage.getHeight() == mHeight;
fast = useFastRendering;
}
public int getIconHeight() {
return mHeight;
return height;
}
public int getIconWidth() {
return mWidth;
return width;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
if (mFast || !(g instanceof Graphics2D)) {
if (fast || !(g instanceof Graphics2D)) {
//System.out.println("Scaling fast");
g.drawImage(mImage, x, y, mWidth, mHeight, null);
g.drawImage(image, x, y, width, height, null);
}
else {
//System.out.println("Scaling using interpolation");
Graphics2D g2 = (Graphics2D) g;
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
xform.scale(mWidth / (double) mImage.getWidth(), mHeight / (double) mImage.getHeight());
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(mImage, xform, null);
g2.drawImage(image, xform, null);
}
}
}
@@ -73,14 +73,15 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
*/
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
private final Kernel mKernel;
private final int mEdgeCondition;
private final Kernel kernel;
private final int edgeCondition;
private final ConvolveOp mConvolve;
private final ConvolveOp convolve;
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
// Create convolution operation
int edge;
switch (pEdgeCondition) {
case EDGE_REFLECT:
case EDGE_WRAP:
@@ -90,9 +91,10 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
edge = pEdgeCondition;
break;
}
mKernel = pKernel;
mEdgeCondition = pEdgeCondition;
mConvolve = new ConvolveOp(pKernel, edge, pHints);
kernel = pKernel;
edgeCondition = pEdgeCondition;
convolve = new ConvolveOp(pKernel, edge, pHints);
}
public ConvolveWithEdgeOp(final Kernel pKernel) {
@@ -107,8 +109,8 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
throw new IllegalArgumentException("source image cannot be the same as the destination image");
}
int borderX = mKernel.getWidth() / 2;
int borderY = mKernel.getHeight() / 2;
int borderX = kernel.getWidth() / 2;
int borderY = kernel.getHeight() / 2;
BufferedImage original = addBorder(pSource, borderX, borderY);
@@ -126,7 +128,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
}
// Do the filtering (if destination is null, a new image will be created)
destination = mConvolve.filter(original, destination);
destination = convolve.filter(original, destination);
if (pSource != original) {
// Remove the border
@@ -137,7 +139,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
}
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
if ((mEdgeCondition & 2) == 0) {
if ((edgeCondition & 2) == 0) {
return pOriginal;
}
@@ -158,7 +160,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
g.drawImage(pOriginal, pBorderX, pBorderY, null);
// TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel
switch (mEdgeCondition) {
switch (edgeCondition) {
case EDGE_REFLECT:
// Top/left (empty)
g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center
@@ -186,7 +188,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right
break;
default:
throw new IllegalArgumentException("Illegal edge operation " + mEdgeCondition);
throw new IllegalArgumentException("Illegal edge operation " + edgeCondition);
}
}
@@ -206,39 +208,39 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
* @see #EDGE_WRAP
*/
public int getEdgeCondition() {
return mEdgeCondition;
return edgeCondition;
}
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
return mConvolve.filter(pSource, pDestination);
return convolve.filter(pSource, pDestination);
}
public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) {
return mConvolve.createCompatibleDestImage(pSource, pDesinationColorModel);
return convolve.createCompatibleDestImage(pSource, pDesinationColorModel);
}
public WritableRaster createCompatibleDestRaster(final Raster pSource) {
return mConvolve.createCompatibleDestRaster(pSource);
return convolve.createCompatibleDestRaster(pSource);
}
public Rectangle2D getBounds2D(final BufferedImage pSource) {
return mConvolve.getBounds2D(pSource);
return convolve.getBounds2D(pSource);
}
public Rectangle2D getBounds2D(final Raster pSource) {
return mConvolve.getBounds2D(pSource);
return convolve.getBounds2D(pSource);
}
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
return mConvolve.getPoint2D(pSourcePoint, pDestinationPoint);
return convolve.getPoint2D(pSourcePoint, pDestinationPoint);
}
public RenderingHints getRenderingHints() {
return mConvolve.getRenderingHints();
return convolve.getRenderingHints();
}
public Kernel getKernel() {
return mConvolve.getKernel();
return convolve.getKernel();
}
}
@@ -51,7 +51,7 @@ import java.awt.image.WritableRaster;
*/
public class CopyDither implements BufferedImageOp, RasterOp {
protected IndexColorModel mIndexColorModel = null;
protected IndexColorModel indexColorModel = null;
/**
* Creates a {@code CopyDither}, using the given
@@ -61,7 +61,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
*/
public CopyDither(IndexColorModel pICM) {
// Store colormodel
mIndexColorModel = pICM;
indexColorModel = pICM;
}
/**
@@ -83,17 +83,12 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}.
*/
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
ColorModel pDestCM) {
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
mIndexColorModel);
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, indexColorModel);
}
else if (pDestCM instanceof IndexColorModel) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
(IndexColorModel) pDestCM);
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) pDestCM);
}
else {
throw new ImageFilterException("Only IndexColorModel allowed.");
@@ -112,13 +107,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
return createCompatibleDestRaster(pSrc, getICM(pSrc));
}
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
IndexColorModel pIndexColorModel) {
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
}
@@ -207,8 +196,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @return the destination image, or a new image, if {@code pDest} was
* {@code null}.
*/
public final BufferedImage filter(BufferedImage pSource,
BufferedImage pDest) {
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
// Create destination image, if none provided
if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource));
@@ -238,16 +226,17 @@ public class CopyDither implements BufferedImageOp, RasterOp {
}
private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
}
private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
}
private IndexColorModel createIndexColorModel(Raster pSource) {
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_INT_ARGB);
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_INT_ARGB);
image.setData(pSource);
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY);
}
@@ -261,8 +250,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
IndexColorModel pColorModel) {
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
int width = pSource.getWidth();
int height = pSource.getHeight();
@@ -292,6 +280,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
pDest.setDataElements(x, y, pixel);
}
}
return pDest;
}
}
@@ -17,7 +17,7 @@ import java.util.Random;
* This {@code BufferedImageOp/RasterOp} implements basic
* Floyd-Steinberg error-diffusion algorithm for dithering.
* <P/>
* The weights used are 7/16 3/16 5/16 1/16, distributed like this:
* The weights used are 7/16, 3/16, 5/16 and 1/16, distributed like this:
* <!-- - -
* | |x|7|
* - - - -
@@ -36,45 +36,47 @@ import java.util.Random;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/DiffusionDither.java#1 $
*
* @version $Id: DiffusionDither.java#1 $
*/
public class DiffusionDither implements BufferedImageOp, RasterOp {
protected IndexColorModel mIndexColorModel = null;
private boolean mAlternateScans = true;
private static final int FS_SCALE = 1 << 8;
private static final Random RANDOM = new Random();
protected final IndexColorModel indexColorModel;
private boolean alternateScans = true;
/**
* Creates a {@code DiffusionDither}, using the given
* {@code IndexColorModel} for dithering into.
*
* @param pICM an IndexColorModel.
*/
public DiffusionDither(IndexColorModel pICM) {
// Store colormodel
mIndexColorModel = pICM;
public DiffusionDither(final IndexColorModel pICM) {
// Store color model
indexColorModel = pICM;
}
/**
* Creates a {@code DiffusionDither}, with no fixed
* {@code IndexColorModel}. The colormodel will be generated for each
* filtering, unless the dest image allready has an
* {@code IndexColorModel}. The color model will be generated for each
* filtering, unless the destination image already has an
* {@code IndexColorModel}.
*/
public DiffusionDither() {
this(null);
}
/**
* Sets the scan mode. If the parameter is true, error distribution for
* every even line will be left-to-right, while odd lines will be
* right-to-left.
* The default is {@code true}.
*
* @param pUse {@code true} if scan mode should be alternating left/right
*/
public void setAlternateScans(boolean pUse) {
mAlternateScans = pUse;
alternateScans = pUse;
}
/**
@@ -86,8 +88,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
* an instance of {@code IndexColorModel}.
*/
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
ColorModel pDestCM) {
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
if (pDestCM == null) {
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
@@ -107,7 +108,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* Creates a compatible {@code Raster} to dither into.
* Only {@code IndexColorModel} allowed.
*
* @param pSrc
* @param pSrc the source raster
*
* @return a {@code WritableRaster}
*/
@@ -115,14 +116,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
return createCompatibleDestRaster(pSrc, getICM(pSrc));
}
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
IndexColorModel pIndexColorModel) {
/**
* Creates a compatible {@code Raster} to dither into.
*
* @param pSrc the source raster.
* @param pIndexColorModel the index color model used to create a {@code Raster}.
*
* @return a {@code WritableRaster}
*/
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
/*
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
BufferedImage.TYPE_BYTE_INDEXED,
pIndexColorModel).getRaster();
*/
}
@@ -216,13 +219,12 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource the source image
* @param pDest the destiantion image
* @param pDest the destination image
*
* @return the destination image, or a new image, if {@code pDest} was
* {@code null}.
*/
public final BufferedImage filter(BufferedImage pSource,
BufferedImage pDest) {
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
// Create destination image, if none provided
if (pDest == null) {
pDest = createCompatibleDestImage(pSource, getICM(pSource));
@@ -241,8 +243,8 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource
* @param pDest
* @param pSource the source raster, assumed to be in sRGB
* @param pDest the destination raster, may be {@code null}
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
@@ -252,10 +254,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
}
private IndexColorModel getICM(BufferedImage pSource) {
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
}
private IndexColorModel getICM(Raster pSource) {
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
}
private IndexColorModel createIndexColorModel(Raster pSource) {
@@ -265,21 +267,18 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
}
/**
* Performs a single-input/single-output dither operation, applying basic
* Floyd-Steinberg error-diffusion to the image.
*
* @param pSource
* @param pDest
* @param pColorModel
* @param pSource the source raster, assumed to be in sRGB
* @param pDest the destination raster, may be {@code null}
* @param pColorModel the indexed color model to use
*
* @return the destination raster, or a new raster, if {@code pDest} was
* {@code null}.
*/
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
IndexColorModel pColorModel) {
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
int width = pSource.getWidth();
int height = pSource.getHeight();
@@ -293,20 +292,15 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
// When reference for column, add 1 to reference as this buffer is
// offset from actual column position by one to allow FS to not check
// left/right edge conditions
int[][] mCurrErr = new int[width + 2][3];
int[][] mNextErr = new int[width + 2][3];
int[][] currErr = new int[width + 2][3];
int[][] nextErr = new int[width + 2][3];
// Random errors in [-1 .. 1] - for first row
for (int i = 0; i < width + 2; i++) {
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
/*
mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
mCurrErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
*/
mCurrErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
mCurrErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
mCurrErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
}
// Temp buffers
@@ -319,10 +313,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
// Loop through image data
for (int y = 0; y < height; y++) {
// Clear out next error rows for colour errors
for (int i = mNextErr.length; --i >= 0;) {
mNextErr[i][0] = 0;
mNextErr[i][1] = 0;
mNextErr[i][2] = 0;
for (int i = nextErr.length; --i >= 0;) {
nextErr[i][0] = 0;
nextErr[i][1] = 0;
nextErr[i][2] = 0;
}
// Set up start column and limit
@@ -349,7 +343,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
for (int i = 0; i < 3; i++) {
// Make a 28.4 FP number, add Error (with fraction),
// rounding and truncate to int
inRGB[i] = ((inRGB[i] << 4) + mCurrErr[x + 1][i] + 0x08) >> 4;
inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4;
// Clamp
if (inRGB[i] > 255) {
@@ -385,26 +379,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
if (forward) {
// Row 1 (y)
// Update error in this pixel (x + 1)
mCurrErr[x + 2][0] += diff[0] * 7;
mCurrErr[x + 2][1] += diff[1] * 7;
mCurrErr[x + 2][2] += diff[2] * 7;
currErr[x + 2][0] += diff[0] * 7;
currErr[x + 2][1] += diff[1] * 7;
currErr[x + 2][2] += diff[2] * 7;
// Row 2 (y + 1)
// Update error in this pixel (x - 1)
mNextErr[x][0] += diff[0] * 3;
mNextErr[x][1] += diff[1] * 3;
mNextErr[x][2] += diff[2] * 3;
nextErr[x][0] += diff[0] * 3;
nextErr[x][1] += diff[1] * 3;
nextErr[x][2] += diff[2] * 3;
// Update error in this pixel (x)
mNextErr[x + 1][0] += diff[0] * 5;
mNextErr[x + 1][1] += diff[1] * 5;
mNextErr[x + 1][2] += diff[2] * 5;
nextErr[x + 1][0] += diff[0] * 5;
nextErr[x + 1][1] += diff[1] * 5;
nextErr[x + 1][2] += diff[2] * 5;
// Update error in this pixel (x + 1)
// TODO: Consider calculating this using
// error term = error - sum(error terms 1, 2 and 3)
// See Computer Graphics (Foley et al.), p. 573
mNextErr[x + 2][0] += diff[0]; // * 1;
mNextErr[x + 2][1] += diff[1]; // * 1;
mNextErr[x + 2][2] += diff[2]; // * 1;
nextErr[x + 2][0] += diff[0]; // * 1;
nextErr[x + 2][1] += diff[1]; // * 1;
nextErr[x + 2][2] += diff[2]; // * 1;
// Next
x++;
@@ -418,26 +412,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
else {
// Row 1 (y)
// Update error in this pixel (x - 1)
mCurrErr[x][0] += diff[0] * 7;
mCurrErr[x][1] += diff[1] * 7;
mCurrErr[x][2] += diff[2] * 7;
currErr[x][0] += diff[0] * 7;
currErr[x][1] += diff[1] * 7;
currErr[x][2] += diff[2] * 7;
// Row 2 (y + 1)
// Update error in this pixel (x + 1)
mNextErr[x + 2][0] += diff[0] * 3;
mNextErr[x + 2][1] += diff[1] * 3;
mNextErr[x + 2][2] += diff[2] * 3;
nextErr[x + 2][0] += diff[0] * 3;
nextErr[x + 2][1] += diff[1] * 3;
nextErr[x + 2][2] += diff[2] * 3;
// Update error in this pixel (x)
mNextErr[x + 1][0] += diff[0] * 5;
mNextErr[x + 1][1] += diff[1] * 5;
mNextErr[x + 1][2] += diff[2] * 5;
nextErr[x + 1][0] += diff[0] * 5;
nextErr[x + 1][1] += diff[1] * 5;
nextErr[x + 1][2] += diff[2] * 5;
// Update error in this pixel (x - 1)
// TODO: Consider calculating this using
// error term = error - sum(error terms 1, 2 and 3)
// See Computer Graphics (Foley et al.), p. 573
mNextErr[x][0] += diff[0]; // * 1;
mNextErr[x][1] += diff[1]; // * 1;
mNextErr[x][2] += diff[2]; // * 1;
nextErr[x][0] += diff[0]; // * 1;
nextErr[x][1] += diff[1]; // * 1;
nextErr[x][2] += diff[2]; // * 1;
// Previous
x--;
@@ -451,15 +445,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
// Make next error info current for next iteration
int[][] temperr;
temperr = mCurrErr;
mCurrErr = mNextErr;
mNextErr = temperr;
temperr = currErr;
currErr = nextErr;
nextErr = temperr;
// Toggle direction
if (mAlternateScans) {
if (alternateScans) {
forward = !forward;
}
}
return pDest;
}
}
@@ -48,8 +48,8 @@ public class GrayFilter extends RGBImageFilter {
canFilterIndexColorModel = true;
}
private int mLow = 0;
private float mRange = 1.0f;
private int low = 0;
private float range = 1.0f;
/**
* Constructs a GrayFilter using ITU color-conversion.
@@ -82,8 +82,8 @@ public class GrayFilter extends RGBImageFilter {
pHigh = 1f;
}
mLow = (int) (pLow * 255f);
mRange = pHigh - pLow;
low = (int) (pLow * 255f);
range = pHigh - pLow;
}
@@ -118,9 +118,9 @@ public class GrayFilter extends RGBImageFilter {
//int gray = (int) ((float) (r + g + b) / 3.0f);
if (mRange != 1.0f) {
if (range != 1.0f) {
// Apply range
gray = mLow + (int) (gray * mRange);
gray = low + (int) (gray * range);
}
// Return ARGB pixel
@@ -32,42 +32,20 @@ package com.twelvemonkeys.image;
* This class wraps IllegalArgumentException as thrown by the
* BufferedImageOp interface for more fine-grained control.
*
* @author Harald Kuhr
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageFilterException.java#1 $
*/
public class ImageFilterException extends IllegalArgumentException {
private Throwable mCause = null;
public ImageFilterException(String pStr) {
super(pStr);
public ImageFilterException(String message) {
super(message);
}
public ImageFilterException(Throwable pT) {
initCause(pT);
public ImageFilterException(Throwable cause) {
super(cause);
}
public ImageFilterException(String pStr, Throwable pT) {
super(pStr);
initCause(pT);
}
public Throwable initCause(Throwable pThrowable) {
if (mCause != null) {
// May only be called once
throw new IllegalStateException();
}
else if (pThrowable == this) {
throw new IllegalArgumentException();
}
mCause = pThrowable;
// Hmmm...
return this;
}
public Throwable getCause() {
return mCause;
public ImageFilterException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -32,19 +32,17 @@ import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
import java.util.Hashtable;
/**
* This class contains methods for basic image manipulation and conversion.
*
* @todo Split palette generation out, into ColorModel classes.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
* @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
*/
public final class ImageUtil {
// TODO: Split palette generation out, into ColorModel classes (?)
public final static int ROTATE_90_CCW = -90;
public final static int ROTATE_90_CW = 90;
@@ -59,12 +57,14 @@ public final class ImageUtil {
* @see #EDGE_REFLECT
*/
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
/**
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
* @see #EDGE_REFLECT
*/
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
/**
* Adds a border to the image while convolving. The border will reflect the
* edges of the original image. This is usually a good default.
@@ -74,6 +74,7 @@ public final class ImageUtil {
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
*/
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
/**
* Adds a border to the image while convolving. The border will wrap the
* edges of the original image. This is usually the best choice for tiles.
@@ -174,19 +175,12 @@ public final class ImageUtil {
/** Our static image tracker */
private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT);
//private static Object sTrackerMutex = new Object();
/** Image id used by the image tracker */
//private static int sTrackerId = 0;
/** */
protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
/** */
protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
/** */
private static final boolean COLORMODEL_TRANSFERTYPE_SUPPORTED = isColorModelTransferTypeSupported();
/** */
private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration();
@@ -208,28 +202,12 @@ public final class ImageUtil {
private ImageUtil() {
}
/**
* Tests if {@code ColorModel} has a {@code getTransferType} method.
*
* @return {@code true} if {@code ColorModel} has a
* {@code getTransferType} method
*/
private static boolean isColorModelTransferTypeSupported() {
try {
ColorModel.getRGBdefault().getTransferType();
return true;
}
catch (Throwable t) {
return false;
}
}
/**
* Converts the {@code RenderedImage} to a {@code BufferedImage}.
* The new image will have the <em>same</em> {@code ColorModel},
* {@code Raster} and properties as the original image, if possible.
* <p/>
* If the image is allready a {@code BufferedImage}, it is simply returned
* If the image is already a {@code BufferedImage}, it is simply returned
* and no conversion takes place.
*
* @param pOriginal the image to convert.
@@ -237,7 +215,7 @@ public final class ImageUtil {
* @return a {@code BufferedImage}
*/
public static BufferedImage toBuffered(RenderedImage pOriginal) {
// Don't convert if it allready is a BufferedImage
// Don't convert if it already is a BufferedImage
if (pOriginal instanceof BufferedImage) {
return (BufferedImage) pOriginal;
}
@@ -283,7 +261,7 @@ public final class ImageUtil {
* Converts the {@code RenderedImage} to a {@code BufferedImage} of the
* given type.
* <p/>
* If the image is allready a {@code BufferedImage} of the given type, it
* If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place.
*
* @param pOriginal the image to convert.
@@ -297,7 +275,7 @@ public final class ImageUtil {
* @see java.awt.image.BufferedImage#getType()
*/
public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) {
// Don't convert if it allready is BufferedImage and correct type
// Don't convert if it already is BufferedImage and correct type
if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) {
return (BufferedImage) pOriginal;
}
@@ -329,7 +307,7 @@ public final class ImageUtil {
* given type. The new image will have the same {@code ColorModel},
* {@code Raster} and properties as the original image, if possible.
* <p/>
* If the image is allready a {@code BufferedImage} of the given type, it
* If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place.
* <p/>
* This method simply invokes
@@ -354,7 +332,7 @@ public final class ImageUtil {
* The new image will have the same {@code ColorModel}, {@code Raster} and
* properties as the original image, if possible.
* <p/>
* If the image is allready a {@code BufferedImage}, it is simply returned
* If the image is already a {@code BufferedImage}, it is simply returned
* and no conversion takes place.
*
* @param pOriginal the image to convert.
@@ -365,7 +343,7 @@ public final class ImageUtil {
* @throws ImageConversionException if the image cannot be converted
*/
public static BufferedImage toBuffered(Image pOriginal) {
// Don't convert if it allready is BufferedImage
// Don't convert if it already is BufferedImage
if (pOriginal instanceof BufferedImage) {
return (BufferedImage) pOriginal;
}
@@ -381,7 +359,7 @@ public final class ImageUtil {
/**
* Creates a copy of the given image. The image will have the same
* colormodel and raster type, but will not share image (pixel) data.
* color model and raster type, but will not share image (pixel) data.
*
* @param pImage the image to clone.
*
@@ -411,11 +389,11 @@ public final class ImageUtil {
* <p/>
* This method is optimized for the most common cases of {@code ColorModel}
* and pixel data combinations. The raster's backing {@code DataBuffer} is
* created directly from the pixel data, as this is faster and with more
* created directly from the pixel data, as this is faster and more
* resource-friendly than using
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
* <p/>
* For unknown combinations, the method will fallback to using
* For uncommon combinations, the method will fallback to using
* {@code ColorModel.createCompatibleWritableRaster(w, h)} and
* {@code WritableRaster.setDataElements(w, h, pixels)}
* <p/>
@@ -441,8 +419,8 @@ public final class ImageUtil {
*/
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
// NOTE: This is optimized code for most common cases.
// We create a DataBuffer with the array from grabber.getPixels()
// directly, and creating a raster based on the ColorModel.
// We create a DataBuffer from the pixel array directly,
// and creating a raster based on the DataBuffer and ColorModel.
// Creating rasters this way is faster and more resource-friendly, as
// cm.createCompatibleWritableRaster allocates an
// "empty" DataBuffer with a storage array of w*h. This array is
@@ -456,14 +434,12 @@ public final class ImageUtil {
if (pPixels instanceof int[]) {
int[] data = (int[]) pPixels;
buffer = new DataBufferInt(data, data.length);
//bands = data.length / (w * h);
bands = pColorModel.getNumComponents();
}
else if (pPixels instanceof short[]) {
short[] data = (short[]) pPixels;
buffer = new DataBufferUShort(data, data.length);
bands = data.length / (pWidth * pHeight);
//bands = cm.getNumComponents();
}
else if (pPixels instanceof byte[]) {
byte[] data = (byte[]) pPixels;
@@ -476,47 +452,30 @@ public final class ImageUtil {
else {
bands = data.length / (pWidth * pHeight);
}
//bands = pColorModel.getNumComponents();
//System.out.println("Pixels: " + data.length + " (" + buffer.getSize() + ")");
//System.out.println("w*h*bands: " + (pWidth * pHeight * bands));
//System.out.println("Bands: " + bands);
//System.out.println("Numcomponents: " + pColorModel.getNumComponents());
}
else {
//System.out.println("Fallback!");
// Fallback mode, slower & requires more memory, but compatible
bands = -1;
// Create raster from colormodel, w and h
// Create raster from color model, w and h
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
}
//System.out.println("Bands: " + bands);
//System.out.println("Pixels: " + pixels.getClass() + " length: " + buffer.getSize());
//System.out.println("Needed Raster: " + cm.createCompatibleWritableRaster(1, 1));
if (raster == null) {
//int bits = cm.getPixelSize();
//if (bits > 4) {
if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
//System.out.println("Creating packed indexed model");
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
}
else if (pColorModel instanceof PackedColorModel) {
//System.out.println("Creating packed model");
PackedColorModel pcm = (PackedColorModel) pColorModel;
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
}
else {
//System.out.println("Creating interleaved model");
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
int[] bandsOffsets = new int[bands];
for (int i = 0; i < bands;) {
bandsOffsets[i] = bands - (++i);
}
//System.out.println("zzz Data array: " + buffer.getSize());
raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT);
}
@@ -536,32 +495,45 @@ public final class ImageUtil {
*
* @param pOriginal the orignal image
* @param pModel the original color model
* @param mWidth the requested width of the raster
* @param mHeight the requested height of the raster
* @param width the requested width of the raster
* @param height the requested height of the raster
*
* @return a new WritableRaster
*/
static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int mWidth, int mHeight) {
static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) {
if (pModel == null || equals(pOriginal.getColorModel(), pModel)) {
int[] bOffs;
switch (pOriginal.getType()) {
case BufferedImage.TYPE_3BYTE_BGR:
int[] bOffs = {2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
mWidth, mHeight,
mWidth * 3, 3,
width, height,
width * 3, 3,
bOffs, null);
case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
mWidth, mHeight,
mWidth * 4, 4,
width, height,
width * 4, 4,
bOffs, null);
case BufferedImage.TYPE_CUSTOM:
// Peek into the sample model to see if we have a sample model that will be incompatible with the default case
SampleModel sm = pOriginal.getRaster().getSampleModel();
if (sm instanceof ComponentSampleModel) {
bOffs = ((ComponentSampleModel) sm).getBandOffsets();
return Raster.createInterleavedRaster(sm.getDataType(),
width, height,
width * bOffs.length, bOffs.length,
bOffs, null);
}
// Else fall through
default:
return pOriginal.getColorModel().createCompatibleWritableRaster(mWidth, mHeight);
return pOriginal.getColorModel().createCompatibleWritableRaster(width, height);
}
}
return pModel.createCompatibleWritableRaster(mWidth, mHeight);
return pModel.createCompatibleWritableRaster(width, height);
}
/**
@@ -569,7 +541,7 @@ public final class ImageUtil {
* The new image will have the same {@code ColorModel}, {@code Raster} and
* properties as the original image, if possible.
* <p/>
* If the image is allready a {@code BufferedImage} of the given type, it
* If the image is already a {@code BufferedImage} of the given type, it
* is simply returned and no conversion takes place.
*
* @param pOriginal the image to convert.
@@ -597,7 +569,7 @@ public final class ImageUtil {
* the color model
*/
private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) {
// Don't convert if it allready is BufferedImage and correct type
// Don't convert if it already is BufferedImage and correct type
if ((pOriginal instanceof BufferedImage)
&& ((BufferedImage) pOriginal).getType() == pType
&& (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) {
@@ -784,7 +756,7 @@ public final class ImageUtil {
* Creates a scaled instance of the given {@code Image}, and converts it to
* a {@code BufferedImage} if needed.
* If the original image is a {@code BufferedImage} the result will have
* same type and colormodel. Note that this implies overhead, and is
* same type and color model. Note that this implies overhead, and is
* probably not useful for anything but {@code IndexColorModel} images.
*
* @param pImage the {@code Image} to scale
@@ -820,7 +792,7 @@ public final class ImageUtil {
BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints);
// Convert if colormodels or type differ, to behave as documented
// Convert if color models or type differ, to behave as documented
if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) {
//System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
//long start = System.currentTimeMillis();
@@ -835,11 +807,13 @@ public final class ImageUtil {
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
// TODO: DiffusionDither does not support transparency at the moment, this will create bad results
new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
}
else {
drawOnto(temp, scaled);
}
scaled = temp;
//long end = System.currentTimeMillis();
//System.out.println("Time: " + (end - start) + " ms");
@@ -965,9 +939,6 @@ public final class ImageUtil {
}
private static int convertAWTHints(int pHints) {
// TODO: These conversions are broken!
// box == area average
// point == replicate (or..?)
switch (pHints) {
case Image.SCALE_FAST:
case Image.SCALE_REPLICATE:
@@ -1129,26 +1100,26 @@ public final class ImageUtil {
* Sharpens an image using a convolution matrix.
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
* <TABLE border="1" cellspacing="0">
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
* <TR><TD>-{@code pAmmount}</TD>
* <TD>4.0 * {@code pAmmount} + 1.0</TD>
* <TD>-{@code pAmmount}</TD></TR>
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
* <TR><TD>-{@code pAmount}</TD>
* <TD>4.0 * {@code pAmount} + 1.0</TD>
* <TD>-{@code pAmount}</TD></TR>
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
* </TABLE>
*
* @param pOriginal the BufferedImage to sharpen
* @param pAmmount the ammount of sharpening
* @param pAmount the amount of sharpening
*
* @return a BufferedImage, containing the sharpened image.
*/
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmmount) {
if (pAmmount == 0f) {
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) {
if (pAmount == 0f) {
return pOriginal;
}
// Create the convolution matrix
float[] data = new float[] {
0.0f, -pAmmount, 0.0f, -pAmmount, 4f * pAmmount + 1f, -pAmmount, 0.0f, -pAmmount, 0.0f
0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f
};
// Do the filtering
@@ -1174,7 +1145,7 @@ public final class ImageUtil {
* Creates a blurred version of the given image.
*
* @param pOriginal the original image
* @param pRadius the ammount to blur
* @param pRadius the amount to blur
*
* @return a new {@code BufferedImage} with a blurred version of the given image
*/
@@ -1187,18 +1158,18 @@ public final class ImageUtil {
// See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation
// Also see http://www.jhlabs.com/ip/blurring.html
// TODO: Rethink... Fixed ammount and scale matrix instead?
// pAmmount = 1f - pAmmount;
// float pAmmount = 1f - pRadius;
// TODO: Rethink... Fixed amount and scale matrix instead?
// pAmount = 1f - pAmount;
// float pAmount = 1f - pRadius;
//
// // Normalize ammount
// float normAmt = (1f - pAmmount) / 24;
// // Normalize amount
// float normAmt = (1f - pAmount) / 24;
//
// // Create the convolution matrix
// float[] data = new float[] {
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
// normAmt, normAmt * 2, pAmmount, normAmt * 2, normAmt,
// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt,
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
// };
@@ -1380,18 +1351,18 @@ public final class ImageUtil {
* Changes the contrast of the image
*
* @param pOriginal the {@code Image} to change
* @param pAmmount the ammount of contrast in the range [-1.0..1.0].
* @param pAmount the amount of contrast in the range [-1.0..1.0].
*
* @return an {@code Image}, containing the contrasted image.
*/
public static Image contrast(Image pOriginal, float pAmmount) {
public static Image contrast(Image pOriginal, float pAmount) {
// No change, return original
if (pAmmount == 0f) {
if (pAmount == 0f) {
return pOriginal;
}
// Create filter
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmmount);
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount);
// Return contrast adjusted image
return filter(pOriginal, filter);
@@ -1402,18 +1373,18 @@ public final class ImageUtil {
* Changes the brightness of the original image.
*
* @param pOriginal the {@code Image} to change
* @param pAmmount the ammount of brightness in the range [-2.0..2.0].
* @param pAmount the amount of brightness in the range [-2.0..2.0].
*
* @return an {@code Image}
*/
public static Image brightness(Image pOriginal, float pAmmount) {
public static Image brightness(Image pOriginal, float pAmount) {
// No change, return original
if (pAmmount == 0f) {
if (pAmount == 0f) {
return pOriginal;
}
// Create filter
RGBImageFilter filter = new BrightnessContrastFilter(pAmmount, 0f);
RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f);
// Return brightness adjusted image
return filter(pOriginal, filter);
@@ -1454,7 +1425,7 @@ public final class ImageUtil {
}
/**
* Tries to use H/W-accellerated code for an image for display purposes.
* Tries to use H/W-accelerated code for an image for display purposes.
* Note that transparent parts of the image might be replaced by solid
* color. Additional image information not used by the current diplay
* hardware may be discarded, like extra bith depth etc.
@@ -1467,7 +1438,7 @@ public final class ImageUtil {
}
/**
* Tries to use H/W-accellerated code for an image for display purposes.
* Tries to use H/W-accelerated code for an image for display purposes.
* Note that transparent parts of the image might be replaced by solid
* color. Additional image information not used by the current diplay
* hardware may be discarded, like extra bith depth etc.
@@ -1483,7 +1454,7 @@ public final class ImageUtil {
}
/**
* Tries to use H/W-accellerated code for an image for display purposes.
* Tries to use H/W-accelerated code for an image for display purposes.
* Note that transparent parts of the image will be replaced by solid
* color. Additional image information not used by the current diplay
* hardware may be discarded, like extra bith depth etc.
@@ -1773,7 +1744,7 @@ public final class ImageUtil {
* @param pTimeOut the time to wait, in milliseconds.
*
* @return true if the image was loaded successfully, false if an error
* occured, or the wait was interrupted.
* occurred, or the wait was interrupted.
*
* @see #waitForImages(Image[],long)
*/
@@ -1788,7 +1759,7 @@ public final class ImageUtil {
* @param pImages an array of Image objects to wait for.
*
* @return true if the images was loaded successfully, false if an error
* occured, or the wait was interrupted.
* occurred, or the wait was interrupted.
*
* @see #waitForImages(Image[],long)
*/
@@ -1804,7 +1775,7 @@ public final class ImageUtil {
* @param pTimeOut the time to wait, in milliseconds
*
* @return true if the images was loaded successfully, false if an error
* occured, or the wait was interrupted.
* occurred, or the wait was interrupted.
*/
public static boolean waitForImages(Image[] pImages, long pTimeOut) {
// TODO: Need to make sure that we don't wait for the same image many times
@@ -1814,13 +1785,6 @@ public final class ImageUtil {
// Create a local id for use with the mediatracker
int imageId;
// NOTE: The synchronization throws IllegalMonitorStateException if
// using JIT on J2SE 1.2 (tested version Sun JRE 1.2.2_017).
// Works perfectly interpreted... Hmmm...
//synchronized (sTrackerMutex) {
//imageId = ++sTrackerId;
//}
// NOTE: This is very experimental...
imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages);
@@ -1866,7 +1830,7 @@ public final class ImageUtil {
}
/**
* Tests wether the image has any transparent or semi-transparent pixels.
* Tests whether the image has any transparent or semi-transparent pixels.
*
* @param pImage the image
* @param pFast if {@code true}, the method tests maximum 10 x 10 pixels,
@@ -1934,7 +1898,7 @@ public final class ImageUtil {
}
/**
* Blends two ARGB values half and half, to create a tone inbetween.
* Blends two ARGB values half and half, to create a tone in between.
*
* @param pRGB1 color 1
* @param pRGB2 color 2
@@ -1947,7 +1911,7 @@ public final class ImageUtil {
}
/**
* Blends two colors half and half, to create a tone inbetween.
* Blends two colors half and half, to create a tone in between.
*
* @param pColor color 1
* @param pOther color 2
@@ -1965,7 +1929,7 @@ public final class ImageUtil {
}
/**
* Blends two colors, controlled by the blendfactor.
* Blends two colors, controlled by the blending factor.
* A factor of {@code 0.0} will return the first color,
* a factor of {@code 1.0} will return the second.
*
@@ -1987,50 +1951,4 @@ public final class ImageUtil {
private static int clamp(float f) {
return (int) f;
}
/**
* PixelGrabber subclass that stores any potential properties from an image.
*/
/*
private static class MyPixelGrabber extends PixelGrabber {
private Hashtable mProps = null;
public MyPixelGrabber(Image pImage) {
// Simply grab all pixels, do not convert to default RGB space
super(pImage, 0, 0, -1, -1, false);
}
// Default implementation does not store the properties...
public void setProperties(Hashtable pProps) {
super.setProperties(pProps);
mProps = pProps;
}
public Hashtable getProperties() {
return mProps;
}
}
*/
/**
* Gets the transfer type from the given {@code ColorModel}.
* <p/>
* NOTE: This is a workaround for missing functionality in JDK 1.2.
*
* @param pModel the color model
* @return the transfer type
*
* @throws NullPointerException if {@code pModel} is {@code null}.
*
* @see java.awt.image.ColorModel#getTransferType()
*/
public static int getTransferType(ColorModel pModel) {
if (COLORMODEL_TRANSFERTYPE_SUPPORTED) {
return pModel.getTransferType();
}
else {
// Stupid workaround
// TODO: Create something that performs better
return pModel.createCompatibleSampleModel(1, 1).getDataType();
}
}
}
@@ -89,12 +89,14 @@ import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* This class implements an adaptive pallete generator to reduce images
* This class implements an adaptive palette generator to reduce images
* to a variable number of colors.
* It can also render images into fixed color pallettes.
* <p/>
@@ -127,7 +129,7 @@ import java.util.List;
* @author <A href="mailto:deweese@apache.org">Thomas DeWeese</A>
* @author <A href="mailto:jun@oop-reserch.com">Jun Inamori</A>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/IndexImage.java#1 $
* @version $Id: IndexImage.java#1 $
* @see DiffusionDither
*/
class IndexImage {
@@ -237,19 +239,22 @@ class IndexImage {
if (this.val != val) {
return false;
}
count++;
return true;
}
}
/**
* Used to define a cube of the colorspace. The cube can be split
* approximagely in half to generate two cubes.
* Used to define a cube of the color space. The cube can be split
* approximately in half to generate two cubes.
*/
private static class Cube {
int[] min = {0, 0, 0}, max = {255, 255, 255};
int[] min = {0, 0, 0};
int[] max = {255, 255, 255};
boolean done = false;
List[] colors = null;
List<Counter>[] colors = null;
int count = 0;
static final int RED = 0;
static final int GRN = 1;
@@ -261,7 +266,7 @@ class IndexImage {
* @param colors contains the 3D color histogram to be subdivided
* @param count the total number of pixels in the 3D histogram.
*/
public Cube(List[] colors, int count) {
public Cube(List<Counter>[] colors, int count) {
this.colors = colors;
this.count = count;
}
@@ -312,20 +317,27 @@ class IndexImage {
c0 = RED;
c1 = GRN;
}
Cube ret;
ret = splitChannel(splitChannel, c0, c1);
if (ret != null) {
return ret;
}
ret = splitChannel(c0, splitChannel, c1);
if (ret != null) {
return ret;
}
ret = splitChannel(c1, splitChannel, c0);
if (ret != null) {
return ret;
}
done = true;
return null;
@@ -381,16 +393,13 @@ class IndexImage {
for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) {
int idx = idx2 | (k << c1Sh4);
List v = colors[idx];
List<Counter> v = colors[idx];
if (v == null) {
continue;
}
Iterator itr = v.iterator();
Counter c;
while (itr.hasNext()) {
c = (Counter) itr.next();
for (Counter c : v) {
val = c.val;
vals[0] = (val & 0xFF0000) >> 16;
vals[1] = (val & 0xFF00) >> 8;
@@ -425,7 +434,6 @@ class IndexImage {
int c = counts[i];
if (c == 0) {
// No counts below this so move up bottom of cube.
if ((tcount == 0) && (i < max[splitChannel])) {
this.min[splitChannel] = i + 1;
@@ -438,10 +446,8 @@ class IndexImage {
continue;
}
if ((half - tcount) <= ((tcount + c) - half)) {
// Then lastAdd is a better top idx for this then i.
if (lastAdd == -1) {
// No lower place to break.
if (c == this.count) {
@@ -465,13 +471,11 @@ class IndexImage {
else {
if (i == this.max[splitChannel]) {
if (c == this.count) {
// would move min up but that should
// have happened already.
return null;// no split to make.
}
else {
// Would like to break between i and i+1
// but no i+1 so use lastAdd and i;
splitLo = lastAdd;
@@ -503,6 +507,7 @@ class IndexImage {
ret.max[c0] = this.max[c0];
ret.min[c1] = this.min[c1];
ret.max[c1] = this.max[c1];
return ret;
}
@@ -515,6 +520,7 @@ class IndexImage {
if (this.count == 0) {
return 0;
}
float red = 0, grn = 0, blu = 0;
int minR = min[0], minG = min[1], minB = min[2];
int maxR = max[0], maxG = max[1], maxB = max[2];
@@ -531,20 +537,18 @@ class IndexImage {
for (int k = minIdx[2]; k <= maxIdx[2]; k++) {
int idx = idx2 | k;
List v = colors[idx];
List<Counter> v = colors[idx];
if (v == null) {
continue;
}
Iterator itr = v.iterator();
Counter c;
while (itr.hasNext()) {
c = (Counter) itr.next();
for (Counter c : v) {
val = c.val;
ired = (val & 0xFF0000) >> 16;
igrn = (val & 0x00FF00) >> 8;
iblu = (val & 0x0000FF);
if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) {
weight = (c.count / (float) this.count);
red += ((float) ired) * weight;
@@ -579,16 +583,13 @@ class IndexImage {
* This version will be removed in a later version of the API.
*/
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
return getIndexColorModel(pImage, pNumberOfColors, pFast
? COLOR_SELECTION_FAST
: COLOR_SELECTION_QUALITY);
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
}
/**
* Gets an {@code IndexColorModel} from the given image. If the image has an
* {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel}
* is created, using an adaptive pallete.
* is created, using an adaptive palette.
*
* @param pImage the image to get {@code IndexColorModel} from
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
@@ -636,23 +637,18 @@ class IndexImage {
// We now have at least a buffered image, create model from it
if (icm == null) {
icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints);
//System.out.println("IndexColorModel created from colors.");
}
else if (!(icm instanceof InverseColorMapIndexColorModel)) {
// If possible, use faster code
//System.out.println("Wrappimg IndexColorModel in InverseColorMapIndexColorModel");
icm = new InverseColorMapIndexColorModel(icm);
}
//else {
//System.out.println("Allredy InverseColorMapIndexColorModel");
//}
return icm;
}
/**
* Creates an {@code IndexColorModel} from the given image, using an adaptive
* pallete.
* palette.
*
* @param pImage the image to get {@code IndexColorModel} from
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
@@ -674,7 +670,8 @@ class IndexImage {
int height = pImage.getHeight();
// Using 4 bits from R, G & B.
List[] colors = new List[1 << 12];// [4096]
@SuppressWarnings("unchecked")
List<Counter>[] colors = new List[1 << 12];// [4096]
// Speedup, doesn't decrease image quality much
int step = 1;
@@ -739,13 +736,16 @@ class IndexImage {
while (numberOfCubes < pNumberOfColors) {
while (cubes[fCube].isDone()) {
fCube++;
if (fCube == numberOfCubes) {
break;
}
}
if (fCube == numberOfCubes) {
break;
}
Cube cube = cubes[fCube];
Cube newCube = cube.split();
@@ -756,6 +756,7 @@ class IndexImage {
cube = newCube;
newCube = tmp;
}
int j = fCube;
int count = cube.count;
@@ -765,17 +766,19 @@ class IndexImage {
}
cubes[j++] = cubes[i];
}
cubes[j++] = cube;
count = newCube.count;
while (j < numberOfCubes) {
if (cubes[j].count < count) {
break;
}
j++;
}
for (int i = numberOfCubes; i > j; i--) {
cubes[i] = cubes[i - 1];
}
System.arraycopy(cubes, j, cubes, j + 1, numberOfCubes - j);
cubes[j/*++*/] = newCube;
numberOfCubes++;
}
@@ -803,15 +806,13 @@ class IndexImage {
// - transparency added to all totally black colors?
int numOfBits = 8;
// -- haraldK, 20021024, as suggested by Thomas E Deweese
// -- haraldK, 20021024, as suggested by Thomas E. Deweese
// plus adding a transparent pixel
IndexColorModel icm;
if (useTransparency) {
//icm = new IndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
}
else {
//icm = new IndexColorModel(numOfBits, r.length, r, g, b);
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b);
}
return icm;
@@ -820,7 +821,7 @@ class IndexImage {
/**
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
* pallete (8 bit) from the color data in the image, and uses default
* palette (8 bit) from the color data in the image, and uses default
* dither.
* <p/>
* The image returned is a new image, the input image is not modified.
@@ -864,7 +865,7 @@ class IndexImage {
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
* adaptive pallete (8 bit) from the given palette image.
* adaptive palette (8 bit) from the given palette image.
* Dithering, transparency and color selection is controlled with the
* {@code pHints}parameter.
* <p/>
@@ -874,7 +875,7 @@ class IndexImage {
* @param pPalette the Image to read color information from
* @param pMatte the background color, used where the original image was
* transparent
* @param pHints mHints that control output quality and speed.
* @param pHints hints that control output quality and speed.
* @return the indexed BufferedImage. The image will be of type
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
@@ -899,7 +900,7 @@ class IndexImage {
/**
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
* pallete with the given number of colors.
* palette with the given number of colors.
* Dithering, transparency and color selection is controlled with the
* {@code pHints}parameter.
* <p/>
@@ -909,7 +910,7 @@ class IndexImage {
* @param pNumberOfColors the number of colors for the image
* @param pMatte the background color, used where the original image was
* transparent
* @param pHints mHints that control output quality and speed.
* @param pHints hints that control output quality and speed.
* @return the indexed BufferedImage. The image will be of type
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
@@ -925,7 +926,7 @@ class IndexImage {
* @see IndexColorModel
*/
public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) {
// NOTE: We need to apply matte before creating colormodel, otherwise we
// NOTE: We need to apply matte before creating color model, otherwise we
// won't have colors for potential faded transitions
IndexColorModel icm;
@@ -946,7 +947,7 @@ class IndexImage {
/**
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
* {@code IndexColorModel}'s pallete.
* {@code IndexColorModel}'s palette.
* Dithering, transparency and color selection is controlled with the
* {@code pHints} parameter.
* <p/>
@@ -985,15 +986,16 @@ class IndexImage {
final int width = pImage.getWidth();
final int height = pImage.getHeight();
// Support transparancy?
// Support transparency?
boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE);
// Create image with solid background
BufferedImage solid = pImage;
if (pMatte != null) {// transparency doesn't really matter
if (pMatte != null) { // transparency doesn't really matter
solid = createSolid(pImage, pMatte);
}
BufferedImage indexed;
// Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default
@@ -1044,12 +1046,12 @@ class IndexImage {
finally {
g2d.dispose();
}
break;
}
// Transparency support, this approach seems lame, but it's the only
// solution I've found until now (that actually works).
// Got anything to do with isPremultiplied? Hmm...
if (transparency) {
// Re-apply the alpha-channel of the original image
applyAlpha(indexed, pImage);
@@ -1062,7 +1064,7 @@ class IndexImage {
/**
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
* pallete with the given number of colors.
* palette with the given number of colors.
* Dithering, transparency and color selection is controlled with the
* {@code pHints}parameter.
* <p/>
@@ -1070,7 +1072,7 @@ class IndexImage {
*
* @param pImage the BufferedImage to index
* @param pNumberOfColors the number of colors for the image
* @param pHints mHints that control output quality and speed.
* @param pHints hints that control output quality and speed.
* @return the indexed BufferedImage. The image will be of type
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
@@ -1092,7 +1094,7 @@ class IndexImage {
/**
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
* {@code IndexColorModel}'s pallete.
* {@code IndexColorModel}'s palette.
* Dithering, transparency and color selection is controlled with the
* {@code pHints}parameter.
* <p/>
@@ -1123,7 +1125,7 @@ class IndexImage {
* Converts the input image (must be {@code TYPE_INT_RGB} or
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
* adaptive pallete (8 bit) from the given palette image.
* adaptive palette (8 bit) from the given palette image.
* Dithering, transparency and color selection is controlled with the
* {@code pHints}parameter.
* <p/>
@@ -1131,7 +1133,7 @@ class IndexImage {
*
* @param pImage the BufferedImage to index
* @param pPalette the Image to read color information from
* @param pHints mHints that control output quality and speed.
* @param pHints hints that control output quality and speed.
* @return the indexed BufferedImage. The image will be of type
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
@@ -1183,14 +1185,14 @@ class IndexImage {
* @param pAlpha the image containing the alpha
*/
private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) {
// Apply alpha as transparancy, using threshold of 25%
// Apply alpha as transparency, using threshold of 25%
for (int y = 0; y < pAlpha.getHeight(); y++) {
for (int x = 0; x < pAlpha.getWidth(); x++) {
// Get alpha component of pixel, if less than 25% opaque
// (0x40 = 64 => 25% of 256), the pixel will be transparent
if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) {
pImage.setRGB(x, y, 0x00FFFFFF);// 100% transparent
pImage.setRGB(x, y, 0x00FFFFFF); // 100% transparent
}
}
}
@@ -1200,7 +1202,6 @@ class IndexImage {
* This class is also a command-line utility.
*/
public static void main(String pArgs[]) {
// Defaults
int argIdx = 0;
int speedTest = -1;
@@ -1237,14 +1238,13 @@ class IndexImage {
speedTest = 10;
}
}
else
if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
overWrite = true;
argIdx++;
}
else
if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
argIdx++;
try {
numColors = Integer.parseInt(pArgs[argIdx++]);
}
@@ -1253,34 +1253,28 @@ class IndexImage {
break;
}
}
else
if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
argIdx++;
gray = true;
}
else
if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
argIdx++;
numColors = 2;
monochrome = true;
}
else
if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
argIdx++;
dither = pArgs[argIdx++];
}
else
if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
argIdx++;
paletteFileName = pArgs[argIdx++];
}
else
if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
argIdx++;
quality = pArgs[argIdx++];
}
else
if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
argIdx++;
try {
background = StringUtil.toColor(pArgs[argIdx++]);
@@ -1290,18 +1284,15 @@ class IndexImage {
break;
}
}
else
if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
argIdx++;
transparency = true;
}
else
if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
argIdx++;
format = StringUtil.toLowerCase(pArgs[argIdx++]);
}
else
if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
argIdx++;
// Setting errArgs to true, to print usage
@@ -1321,6 +1312,7 @@ class IndexImage {
? ", "
: "\n"));
}
System.err.print("Output format names: ");
String[] writers = ImageIO.getWriterFormatNames();
@@ -1333,7 +1325,7 @@ class IndexImage {
}
// Read in image
java.io.File in = new java.io.File(pArgs[argIdx++]);
File in = new File(pArgs[argIdx++]);
if (!in.exists()) {
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
@@ -1341,10 +1333,10 @@ class IndexImage {
}
// Read palette if needed
java.io.File paletteFile = null;
File paletteFile = null;
if (paletteFileName != null) {
paletteFile = new java.io.File(paletteFileName);
paletteFile = new File(paletteFileName);
if (!paletteFile.exists()) {
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
System.exit(5);
@@ -1352,10 +1344,10 @@ class IndexImage {
}
// Make sure we can write
java.io.File out;
File out;
if (argIdx < pArgs.length) {
out = new java.io.File(pArgs[argIdx/*++*/]);
out = new File(pArgs[argIdx/*++*/]);
// Get format from file extension
if (format == null) {
@@ -1363,7 +1355,6 @@ class IndexImage {
}
}
else {
// Create new file in current dir, same name + format extension
String baseName = FileUtil.getBasename(in);
@@ -1371,8 +1362,9 @@ class IndexImage {
if (format == null) {
format = "png";
}
out = new java.io.File(baseName + '.' + format);
out = new File(baseName + '.' + format);
}
if (!overWrite && out.exists()) {
System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!");
System.exit(5);
@@ -1396,12 +1388,12 @@ class IndexImage {
}
}
}
catch (java.io.IOException ioe) {
catch (IOException ioe) {
ioe.printStackTrace(System.err);
System.exit(5);
}
// Create mHints
// Create hints
int hints = DITHER_DEFAULT;
if ("DIFFUSION".equalsIgnoreCase(dither)) {
@@ -1441,16 +1433,14 @@ class IndexImage {
///////////////////////////////
// Index
long start = 0;
long end;
if (speedTest > 0) {
// SPEED TESTING
System.out.println("Measuring speed!");
start = System.currentTimeMillis();
// END SPEED TESTING
}
BufferedImage indexed;
IndexColorModel colors;
@@ -1459,7 +1449,6 @@ class IndexImage {
colors = MonochromeColorModel.getInstance();
}
else if (gray) {
//indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY);
image = ImageUtil.toBuffered(ImageUtil.grayscale(image));
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
@@ -1470,7 +1459,6 @@ class IndexImage {
}
}
else if (paletteImg != null) {
// Get palette from image
indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB),
colors = getIndexColorModel(paletteImg, numColors, hints), background, hints);
@@ -1479,12 +1467,10 @@ class IndexImage {
image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
}
if (speedTest > 0) {
// SPEED TESTING
end = System.currentTimeMillis();
System.out.println("Color selection + dither: " + (end - start) + " ms");
System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms");
// END SPEED TESTING
}
@@ -1494,11 +1480,11 @@ class IndexImage {
System.err.println("No writer for format: \"" + format + "\"!");
}
}
catch (java.io.IOException ioe) {
catch (IOException ioe) {
ioe.printStackTrace(System.err);
}
if (speedTest > 0) {
if (speedTest > 0) {
// SPEED TESTING
System.out.println("Measuring speed!");
@@ -1513,17 +1499,16 @@ class IndexImage {
for (int i = 0; i < speedTest; i++) {
start = System.currentTimeMillis();
getIndexedImage(image, colors, background, hints);
end = System.currentTimeMillis();
time += (end - start);
time += (System.currentTimeMillis() - start);
System.out.print('.');
if ((i + 1) % 10 == 0) {
System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms");
}
}
System.out.println("\nDither only:");
System.out.println("Total time (" + speedTest + " invocations): " + time + "ms");
System.out.println("Average: " + time / speedTest + "ms");
// END SPEED TESTING
}
}
@@ -72,12 +72,12 @@ class InverseColorMap {
*/
final static int MAXQUANTVAL = 1 << 5;
byte[] mRGBMapByte;
int[] mRGBMapInt;
int mNumColors;
int mMaxColor;
byte[] mInverseRGB; // inverse rgb color map
int mTransparentIndex = -1;
byte[] rgbMapByte;
int[] rgbMapInt;
int numColors;
int maxColor;
byte[] inverseRGB; // inverse rgb color map
int transparentIndex = -1;
/**
* @param pRGBColorMap the rgb color map to create inverse color map for.
@@ -99,11 +99,11 @@ class InverseColorMap {
* @param pTransparent the index of the transparent pixel in the map
*/
InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
mRGBMapByte = pRGBColorMap;
mNumColors = mRGBMapByte.length / 4;
mTransparentIndex = pTransparent;
rgbMapByte = pRGBColorMap;
numColors = rgbMapByte.length / 4;
transparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
}
@@ -112,11 +112,11 @@ class InverseColorMap {
* @param pTransparent the index of the transparent pixel in the map
*/
InverseColorMap(int[] pRGBColorMap, int pTransparent) {
mRGBMapInt = pRGBColorMap;
mNumColors = mRGBMapInt.length;
mTransparentIndex = pTransparent;
rgbMapInt = pRGBColorMap;
numColors = rgbMapInt.length;
transparentIndex = pTransparent;
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
}
@@ -130,8 +130,8 @@ class InverseColorMap {
final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors
final int xsqr2 = xsqr + xsqr;
for (int i = 0; i < mNumColors; ++i) {
if (i == mTransparentIndex) {
for (int i = 0; i < numColors; ++i) {
if (i == transparentIndex) {
// Skip the transparent pixel
continue;
}
@@ -141,15 +141,15 @@ class InverseColorMap {
int blue, b, bdist, binc, bxx;
// HaraldK 20040801: Added support for int[]
if (mRGBMapByte != null) {
red = mRGBMapByte[i * 4] & 0xFF;
green = mRGBMapByte[i * 4 + 1] & 0xFF;
blue = mRGBMapByte[i * 4 + 2] & 0xFF;
if (rgbMapByte != null) {
red = rgbMapByte[i * 4] & 0xFF;
green = rgbMapByte[i * 4 + 1] & 0xFF;
blue = rgbMapByte[i * 4 + 2] & 0xFF;
}
else if (mRGBMapInt != null) {
red = (mRGBMapInt[i] >> 16) & 0xFF;
green = (mRGBMapInt[i] >> 8) & 0xFF;
blue = mRGBMapInt[i] & 0xFF;
else if (rgbMapInt != null) {
red = (rgbMapInt[i] >> 16) & 0xFF;
green = (rgbMapInt[i] >> 8) & 0xFF;
blue = rgbMapInt[i] & 0xFF;
}
else {
throw new IllegalStateException("colormap == null");
@@ -170,7 +170,7 @@ class InverseColorMap {
for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) {
if (i == 0 || pTemp[rgbI] > bdist) {
pTemp[rgbI] = bdist;
mInverseRGB[rgbI] = (byte) i;
inverseRGB[rgbI] = (byte) i;
}
}
}
@@ -187,7 +187,7 @@ class InverseColorMap {
* created inverse color map.
*/
public final int getIndexNearest(int pColor) {
return mInverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
return inverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) +
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
}
@@ -203,7 +203,7 @@ class InverseColorMap {
*/
public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
// NOTE: the third line in expression for blue is shifting DOWN not UP.
return mInverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
return inverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) +
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
}
@@ -30,6 +30,7 @@
package com.twelvemonkeys.image;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.lang.Validate;
import java.awt.*;
import java.awt.image.DataBuffer;
@@ -37,7 +38,7 @@ import java.awt.image.IndexColorModel;
/**
* A faster implementation of {@code IndexColorModel}, that is backed by an
* inverse color-map, for fast lookups.
* inverse color-map, for fast look-ups.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author $Author: haku $
@@ -46,13 +47,13 @@ import java.awt.image.IndexColorModel;
*/
public class InverseColorMapIndexColorModel extends IndexColorModel {
protected int mRGBs[];
protected int mMapSize;
protected int rgbs[];
protected int mapSize;
protected InverseColorMap mInverseMap = null;
protected InverseColorMap inverseMap = null;
private final static int ALPHA_THRESHOLD = 0x80;
private int mWhiteIndex = -1;
private int whiteIndex = -1;
private final static int WHITE = 0x00FFFFFF;
private final static int RGB_MASK = 0x00FFFFFF;
@@ -60,37 +61,36 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
* Creates an {@code InverseColorMapIndexColorModel} from an existing
* {@code IndexColorModel}.
*
* @param pColorModel the colormodel to create from
* @param pColorModel the color model to create from.
* @throws IllegalArgumentException if {@code pColorModel} is {@code null}
*/
public InverseColorMapIndexColorModel(IndexColorModel pColorModel) {
this(pColorModel, getRGBs(pColorModel));
public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) {
this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel));
}
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
// times. What is wrong with protected?!
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(),
pRGBs, 0,
ImageUtil.getTransferType(pColorModel),
pColorModel.getValidPixels());
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels());
mRGBs = pRGBs;
mMapSize = mRGBs.length;
rgbs = pRGBs;
mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs);
mWhiteIndex = getWhiteIndex();
inverseMap = new InverseColorMap(rgbs);
whiteIndex = getWhiteIndex();
}
/**
* Creates a defensive copy of the RGB colormap in the given
* Creates a defensive copy of the RGB color map in the given
* {@code IndexColorModel}.
*
* @param pColorModel the indec colormodel to get RGB values from
* @return the RGB colormap
* @param pColorModel the indexed color model to get RGB values from
* @return the RGB color map
*/
private static int[] getRGBs(IndexColorModel pColorModel) {
int[] rgb = new int[pColorModel.getMapSize()];
pColorModel.getRGBs(rgb);
return rgb;
}
@@ -111,15 +111,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
*
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs,
int pStart, boolean pAlpha, int pTransparentIndex,
int pTransferType) {
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, int pStart, boolean pAlpha, int pTransparentIndex, int pTransferType) {
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
rgbs = getRGBs(this);
mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
mWhiteIndex = getWhiteIndex();
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
whiteIndex = getWhiteIndex();
}
/**
@@ -138,15 +136,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
*
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
byte[] pReds, byte[] pGreens, byte[] pBlues,
int pTransparentIndex) {
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues, int pTransparentIndex) {
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
rgbs = getRGBs(this);
mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
mWhiteIndex = getWhiteIndex();
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
whiteIndex = getWhiteIndex();
}
/**
@@ -164,19 +160,18 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
*
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
*/
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
byte[] pReds, byte[] pGreens, byte[] pBlues) {
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues) {
super(pNumBits, pSize, pReds, pGreens, pBlues);
mRGBs = getRGBs(this);
mMapSize = mRGBs.length;
rgbs = getRGBs(this);
mapSize = rgbs.length;
mInverseMap = new InverseColorMap(mRGBs);
mWhiteIndex = getWhiteIndex();
inverseMap = new InverseColorMap(rgbs);
whiteIndex = getWhiteIndex();
}
private int getWhiteIndex() {
for (int i = 0; i < mRGBs.length; i++) {
int color = mRGBs[i];
for (int i = 0; i < rgbs.length; i++) {
int color = rgbs[i];
if ((color & RGB_MASK) == WHITE) {
return i;
}
@@ -244,7 +239,6 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
*
*/
public Object getDataElements(int rgb, Object pixel) {
int alpha = (rgb>>>24);
int pix;
@@ -253,11 +247,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
}
else {
int color = rgb & RGB_MASK;
if (color == WHITE && mWhiteIndex != -1) {
pix = mWhiteIndex;
if (color == WHITE && whiteIndex != -1) {
pix = whiteIndex;
}
else {
pix = mInverseMap.getIndexNearest(color);
pix = inverseMap.getIndexNearest(color);
}
}
@@ -297,8 +291,7 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
shortObj[0] = (short) pix;
break;
default:
throw new UnsupportedOperationException("This method has not been " +
"implemented for transferType " + transferType);
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
}
return pixel;
}
@@ -54,11 +54,11 @@ final class MagickAccelerator {
private static final int RESAMPLE_OP = 0;
private static Class[] sNativeOp = new Class[1];
private static Class[] nativeOp = new Class[1];
static {
try {
sNativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
}
catch (ClassNotFoundException e) {
System.err.println("Could not find class: " + e);
@@ -94,8 +94,8 @@ final class MagickAccelerator {
}
private static int getNativeOpIndex(Class pOpClass) {
for (int i = 0; i < sNativeOp.length; i++) {
if (pOpClass == sNativeOp[i]) {
for (int i = 0; i < nativeOp.length; i++) {
if (pOpClass == nativeOp[i]) {
return i;
}
}
@@ -112,7 +112,7 @@ final class MagickAccelerator {
switch (getNativeOpIndex(pOperation.getClass())) {
case RESAMPLE_OP:
ResampleOp resample = (ResampleOp) pOperation;
result = resampleMagick(pInput, resample.mWidth, resample.mHeight, resample.mFilterType);
result = resampleMagick(pInput, resample.width, resample.height, resample.filterType);
// NOTE: If output parameter is non-null, we have to return that
// image, instead of result
@@ -32,6 +32,8 @@ import magick.*;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
/**
@@ -160,7 +162,11 @@ public final class MagickUtil {
image = rgbToBuffered(pImage, true);
break;
case ImageType.ColorSeparationType:
image = cmykToBuffered(pImage, false);
break;
case ImageType.ColorSeparationMatteType:
image = cmykToBuffered(pImage, true);
break;
case ImageType.OptimizeType:
default:
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
@@ -546,4 +552,60 @@ public final class MagickUtil {
return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null);
}
/**
* Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
// Retreive the ICC profile
ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo());
ColorSpace cs = new ICC_ColorSpace(profile);
int bands = cs.getNumComponents() + (pAlpha ? 1 : 0);
int[] bits = new int[bands];
for (int i = 0; i < bands; i++) {
bits[i] = 8;
}
ColorModel cm = pAlpha ?
new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) :
new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
byte[] pixels = new byte[length * bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// TODO: handle more generic cases if profile is not CMYK
// TODO: Test "ACMYK"
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
// TODO: build array from bands variable, here it just works for CMYK
// The values has not been tested with an alpha picture actually...
int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3};
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(cm, raster, pAlpha, null);
}
}
@@ -33,7 +33,7 @@ import java.awt.image.*;
/**
* Monochrome B/W color model.
*
* @author Harald Kuhr
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class MonochromeColorModel extends IndexColorModel {
@@ -48,37 +48,37 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
// TODO: support more raster types/color models
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
final private int mPixelSizeX;
final private int mPixelSizeY;
final private int pixelSizeX;
final private int pixelSizeY;
private Rectangle mSourceRegion;
private Rectangle sourceRegion;
public PixelizeOp(final int pPixelSize) {
this(pPixelSize, pPixelSize);
}
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
mPixelSizeX = pPixelSizeX;
mPixelSizeY = pPixelSizeY;
pixelSizeX = pPixelSizeX;
pixelSizeY = pPixelSizeY;
}
public Rectangle getSourceRegion() {
if (mSourceRegion == null) {
if (sourceRegion == null) {
return null;
}
return new Rectangle(mSourceRegion);
return new Rectangle(sourceRegion);
}
public void setSourceRegion(final Rectangle pSourceRegion) {
if (pSourceRegion == null) {
mSourceRegion = null;
sourceRegion = null;
}
else {
if (mSourceRegion == null) {
mSourceRegion = new Rectangle(pSourceRegion);
if (sourceRegion == null) {
sourceRegion = new Rectangle(pSourceRegion);
}
else {
mSourceRegion.setBounds(pSourceRegion);
sourceRegion.setBounds(pSourceRegion);
}
}
}
@@ -107,11 +107,11 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
//System.out.println("src: " + src);
//System.out.println("dest: " + dest);
if (mSourceRegion != null) {
int cx = mSourceRegion.x;
int cy = mSourceRegion.y;
int cw = mSourceRegion.width;
int ch = mSourceRegion.height;
if (sourceRegion != null) {
int cx = sourceRegion.x;
int cy = sourceRegion.y;
int cw = sourceRegion.width;
int ch = sourceRegion.height;
boolean same = src == dest;
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
@@ -122,8 +122,8 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
final int width = src.getWidth();
final int height = src.getHeight();
int w = (width + mPixelSizeX - 1) / mPixelSizeX;
int h = (height + mPixelSizeY - 1) / mPixelSizeY;
int w = (width + pixelSizeX - 1) / pixelSizeX;
int h = (height + pixelSizeY - 1) / pixelSizeY;
final boolean oddX = width % w != 0;
final boolean oddY = height % h != 0;
@@ -156,23 +156,23 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
for (int y = 0; y < h; y++) {
if (!oddY || y + 1 < h) {
scanH = mPixelSizeY;
scanH = pixelSizeY;
}
else {
scanH = height - (y * mPixelSizeY);
scanH = height - (y * pixelSizeY);
}
for (int x = 0; x < w; x++) {
if (!oddX || x + 1 < w) {
scanW = mPixelSizeX;
scanW = pixelSizeX;
}
else {
scanW = width - (x * mPixelSizeX);
scanW = width - (x * pixelSizeX);
}
final int pixelCount = scanW * scanH;
final int pixelLength = pixelCount * dataElements;
data = src.getDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
data = src.getDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
// NOTE: These are not neccessarily ARGB..
double valueA = 0.0;
@@ -277,7 +277,7 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
}
dest.setDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
dest.setDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
}
}
/*/
@@ -52,15 +52,12 @@
package com.twelvemonkeys.image;
import com.twelvemonkeys.lang.SystemUtil;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*;
/**
* Resamples (scales) a {@code BufferedImage} to a new width and height, using
* high performance and high quality algorithms.
@@ -138,7 +135,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// MagickAccelerator to work consistently (see magick.FilterType).
/**
* Undefined interpolation, filter method will use default filter
* Undefined interpolation, filter method will use default filter.
*/
public final static int FILTER_UNDEFINED = 0;
/**
@@ -194,11 +191,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
/**
* Mitchell interpolation. High quality.
*/
public final static int FILTER_MITCHELL = 12;// IM default scale with palette or alpha, or scale up
public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up
/**
* Lanczos interpolation. High quality.
*/
public final static int FILTER_LANCZOS = 13;// IM default
public final static int FILTER_LANCZOS = 13; // IM default
/**
* Blackman-Bessel interpolation. High quality.
*/
@@ -291,25 +288,23 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// Member variables
// Package access, to allow access from MagickAccelerator
int mWidth;
int mHeight;
int width;
int height;
int mFilterType;
private static final boolean TRANSFORM_OP_BICUBIC_SUPPORT = SystemUtil.isFieldAvailable(AffineTransformOp.class.getName(), "TYPE_BICUBIC");
int filterType;
/**
* RendereingHints.Key implementation, works only with Value values.
*/
// TODO: Move to abstract class AbstractBufferedImageOp?
static class Key extends RenderingHints.Key {
static int sIndex = 10000;
private final String mName;
private final String name;
public Key(final String pName) {
super(sIndex++);
mName = pName;
name = pName;
}
public boolean isCompatibleValue(Object pValue) {
@@ -317,36 +312,35 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
public String toString() {
return mName;
return name;
}
}
/**
* RenderingHints value implementaion, works with Key keys.
* RenderingHints value implementation, works with Key keys.
*/
// TODO: Extract abstract Value class, and move to AbstractBufferedImageOp
static final class Value {
final private RenderingHints.Key mKey;
final private String mName;
final private int mType;
final private RenderingHints.Key key;
final private String name;
final private int type;
public Value(final RenderingHints.Key pKey, final String pName, final int pType) {
mKey = pKey;
mName = pName;
validateFilterType(pType);
mType = pType;// TODO: test for duplicates
key = pKey;
name = pName;
type = validateFilterType(pType);
}
public boolean isCompatibleKey(Key pKey) {
return pKey == mKey;
return pKey == key;
}
public int getFilterType() {
return mType;
return type;
}
public String toString() {
return mName;
return name;
}
}
@@ -354,11 +348,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* Creates a {@code ResampleOp} that will resample input images to the
* given width and height, using the default interpolation filter.
*
* @param pWidth width of the resampled image
* @param pHeight height of the resampled image
* @param width width of the re-sampled image
* @param height height of the re-sampled image
*/
public ResampleOp(int pWidth, int pHeight) {
this(pWidth, pHeight, FILTER_UNDEFINED);
public ResampleOp(int width, int height) {
this(width, height, FILTER_UNDEFINED);
}
/**
@@ -394,41 +388,40 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* </ul>
* Other hints have no effect on this filter.
*
* @param pWidth width of the resampled image
* @param pHeight height of the resampled image
* @param pHints rendering hints, affecting interpolation algorithm
* @param width width of the re-sampled image
* @param height height of the re-sampled image
* @param hints rendering hints, affecting interpolation algorithm
* @see #KEY_RESAMPLE_INTERPOLATION
* @see RenderingHints#KEY_INTERPOLATION
* @see RenderingHints#KEY_RENDERING
* @see RenderingHints#KEY_COLOR_RENDERING
*/
public ResampleOp(int pWidth, int pHeight, RenderingHints pHints) {
this(pWidth, pHeight, getFilterType(pHints));
public ResampleOp(int width, int height, RenderingHints hints) {
this(width, height, getFilterType(hints));
}
/**
* Creates a {@code ResampleOp} that will resample input images to the
* given width and height, using the given interpolation filter.
*
* @param pWidth width of the resampled image
* @param pHeight height of the resampled image
* @param pFilterType interpolation filter algorithm
* @param width width of the re-sampled image
* @param height height of the re-sampled image
* @param filterType interpolation filter algorithm
* @see <a href="#field_summary">filter type constants</a>
*/
public ResampleOp(int pWidth, int pHeight, int pFilterType) {
if (pWidth <= 0 || pHeight <= 0) {
public ResampleOp(int width, int height, int filterType) {
if (width <= 0 || height <= 0) {
// NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
throw new IllegalArgumentException("width and height must be positive");
}
mWidth = pWidth;
mHeight = pHeight;
this.width = width;
this.height = height;
validateFilterType(pFilterType);
mFilterType = pFilterType;
this.filterType = validateFilterType(filterType);
}
private static void validateFilterType(int pFilterType) {
private static int validateFilterType(int pFilterType) {
switch (pFilterType) {
case FILTER_UNDEFINED:
case FILTER_POINT:
@@ -446,7 +439,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
case FILTER_LANCZOS:
case FILTER_BLACKMAN_BESSEL:
case FILTER_BLACKMAN_SINC:
break;
return pFilterType;
default:
throw new IllegalArgumentException("Unknown filter type: " + pFilterType);
}
@@ -471,25 +464,21 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED;
}
else
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|| (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION)
&& (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING))
|| RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) {
// Nearest neighbour, or prioritze speed
// Nearest neighbour, or prioritize speed
return FILTER_POINT;
}
else
if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
// Triangle equals bi-linear interpolation
return FILTER_TRIANGLE;
}
else
if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
return FILTER_QUADRATIC;// No idea if this is correct..?
}
else
if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|| RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
// Prioritize quality
return FILTER_MITCHELL;
@@ -500,83 +489,87 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
/**
* Resamples (scales) the image to the size, and using the algorithm
* Re-samples (scales) the image to the size, and using the algorithm
* specified in the constructor.
*
* @param pInput The {@code BufferedImage} to be filtered
* @param pOutput The {@code BufferedImage} in which to store the resampled
* @param input The {@code BufferedImage} to be filtered
* @param output The {@code BufferedImage} in which to store the resampled
* image
* @return The resampled {@code BufferedImage}.
* @throws NullPointerException if {@code pInput} is {@code null}
* @throws IllegalArgumentException if {@code pInput == pOutput}.
* @return The re-sampled {@code BufferedImage}.
* @throws NullPointerException if {@code input} is {@code null}
* @throws IllegalArgumentException if {@code input == output}.
* @see #ResampleOp(int,int,int)
*/
public final BufferedImage filter(final BufferedImage pInput, final BufferedImage pOutput) {
if (pInput == null) {
public final BufferedImage filter(final BufferedImage input, final BufferedImage output) {
if (input == null) {
throw new NullPointerException("Input == null");
}
if (pInput == pOutput) {
if (input == output) {
throw new IllegalArgumentException("Output image cannot be the same as the input image");
}
InterpolationFilter filter;
// Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
// Java implementation is very fast (possibly H/W accellerated)
switch (mFilterType) {
// Java implementation is very fast (possibly H/W accelerated)
switch (filterType) {
case FILTER_POINT:
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
case FILTER_TRIANGLE:
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
case FILTER_QUADRATIC:
if (TRANSFORM_OP_BICUBIC_SUPPORT) {
return fastResample(pInput, pOutput, mWidth, mHeight, 3); // AffineTransformOp.TYPE_BICUBIC
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
}
// Fall through
// Else fall through
case FILTER_TRIANGLE:
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
}
// Else fall through
case FILTER_QUADRATIC:
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BICUBIC);
}
// Else fall through
default:
filter = createFilter(mFilterType);
filter = createFilter(filterType);
// NOTE: Workaround for filter throwing exceptions when input or output is less than support...
if (Math.min(pInput.getWidth(), pInput.getHeight()) <= filter.support() || Math.min(mWidth, mHeight) <= filter.support()) {
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) {
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
}
// Fall through
}
// Try to use native ImageMagick code
BufferedImage result = MagickAccelerator.filter(this, pInput, pOutput);
BufferedImage result = MagickAccelerator.filter(this, input, output);
if (result != null) {
return result;
}
// Otherwise, continue in pure Java mode
// TODO: What if pOutput != null and wrong size? Create new? Render on only a part? Document?
// TODO: What if output != null and wrong size? Create new? Render on only a part? Document?
// If filter type != POINT or BOX an input has IndexColorModel, convert
// to true color, with alpha reflecting that of the original colormodel.
BufferedImage input;
// to true color, with alpha reflecting that of the original color model.
BufferedImage temp;
ColorModel cm;
if (mFilterType != FILTER_BOX && (cm = pInput.getColorModel()) instanceof IndexColorModel) {
// TODO: OPTIMIZE: If colormodel has only b/w or gray, we could skip color info
input = ImageUtil.toBuffered(pInput, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) {
// TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info
temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
}
else {
input = pInput;
temp = input;
}
// Create or convert output to a suitable image
// TODO: OPTIMIZE: Don't really need to convert all types to same as input
result = pOutput != null ? /*pOutput*/ ImageUtil.toBuffered(pOutput, input.getType()) : createCompatibleDestImage(input, null);
result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
resample(input, result, filter);
resample(temp, result, filter);
// If pOutput != null and needed to be converted, draw it back
if (pOutput != null && pOutput != result) {
//pOutput.setData(output.getRaster());
ImageUtil.drawOnto(pOutput, result);
result = pOutput;
// If output != null and needed to be converted, draw it back
if (output != null && output != result) {
//output.setData(output.getRaster());
ImageUtil.drawOnto(output, result);
result = output;
}
return result;
@@ -672,8 +665,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
*/
private static BufferedImage fastResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight, final int pType) {
BufferedImage temp = pInput;
private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) {
BufferedImage temp = input;
double xScale;
double yScale;
@@ -681,20 +674,20 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
AffineTransform transform;
AffineTransformOp scale;
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
// Initially scale so all remaining operations will halve the image
if (pWidth < pInput.getWidth() || pHeight < pInput.getHeight()) {
int w = pWidth;
int h = pHeight;
while (w < pInput.getWidth() / 2) {
if (width < input.getWidth() || height < input.getHeight()) {
int w = width;
int h = height;
while (w < input.getWidth() / 2) {
w *= 2;
}
while (h < pInput.getHeight() / 2) {
while (h < input.getHeight() / 2) {
h *= 2;
}
xScale = w / (double) pInput.getWidth();
yScale = h / (double) pInput.getHeight();
xScale = w / (double) input.getWidth();
yScale = h / (double) input.getHeight();
//System.out.println("First scale by x=" + xScale + ", y=" + yScale);
@@ -704,12 +697,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
}
scale = null;// NOTE: This resets!
scale = null; // NOTE: This resets!
xScale = pWidth / (double) temp.getWidth();
yScale = pHeight / (double) temp.getHeight();
xScale = width / (double) temp.getWidth();
yScale = height / (double) temp.getHeight();
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
// TODO: Test skipping first scale (above), and instead scale once
// more here, and a little less than .5 each time...
// That would probably make the scaling smoother...
@@ -740,17 +733,15 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
temp = scale.filter(temp, null);
}
}
//System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);
transform = AffineTransform.getScaleInstance(xScale, yScale);
scale = new AffineTransformOp(transform, pType);
return scale.filter(temp, pOutput);
scale = new AffineTransformOp(transform, type);
return scale.filter(temp, output);
}
/**
@@ -760,7 +751,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
* @see <a href="#field_summary">filter type constants</a>
*/
public int getFilterType() {
return mFilterType;
return filterType;
}
private static InterpolationFilter createFilter(int pFilterType) {
@@ -770,7 +761,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
}
switch (pFilterType) {
//case FILTER_POINT: // Should never happen
case FILTER_POINT:
return new PointFilter();
case FILTER_BOX:
return new BoxFilter();
case FILTER_TRIANGLE:
@@ -815,14 +807,13 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// If indexcolormodel, we probably don't want to use that...
// NOTE: Either BOTH or NONE of the images must have ALPHA
return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, mWidth, mHeight),
return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height),
cm.isAlphaPremultiplied(), null);
}
public RenderingHints getRenderingHints() {
Object value;
switch (mFilterType) {
switch (filterType) {
case FILTER_UNDEFINED:
return null;
case FILTER_POINT:
@@ -871,14 +862,14 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
value = VALUE_INTERPOLATION_BLACKMAN_SINC;
break;
default:
throw new IllegalStateException("Unknown filter type: " + mFilterType);
throw new IllegalStateException("Unknown filter type: " + filterType);
}
return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
}
public Rectangle2D getBounds2D(BufferedImage src) {
return new Rectangle(mWidth, mHeight);
return new Rectangle(width, height);
}
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
@@ -1439,10 +1430,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
resample()
Resizes bitmaps while resampling them.
Returns -1 if error, 0 if success.
*/
private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) {
// TODO: Don't work... Could fix by creating a temporary image in filter method
final int dstWidth = pDest.getWidth();
final int dstHeight = pDest.getHeight();
@@ -1451,7 +1440,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
/* create intermediate column to hold horizontal dst column zoom */
final ColorModel cm = pSource.getColorModel();
final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
// final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight);
double xscale = (double) dstWidth / (double) srcWidth;
double yscale = (double) dstHeight / (double) srcHeight;
@@ -1566,7 +1556,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
final WritableRaster out = pDest.getRaster();
// TODO: This is not optimal for non-byte-packed rasters...
// (What? Maybe I implemented the fix, but forgot to remove the qTODO?)
// (What? Maybe I implemented the fix, but forgot to remove the TODO?)
final int numChannels = raster.getNumBands();
final int[] channelMax = new int[numChannels];
for (int k = 0; k < numChannels; k++) {
@@ -1575,7 +1565,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
for (int xx = 0; xx < dstWidth; xx++) {
ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx);
/* Apply horz filter to make dst column in tmp. */
/* Apply horiz filter to make dst column in tmp. */
for (int k = 0; k < srcHeight; k++) {
for (int channel = 0; channel < numChannels; channel++) {
@@ -42,8 +42,8 @@ import java.awt.image.ReplicateScaleFilter;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/SubsamplingFilter.java#1 $
*/
public class SubsamplingFilter extends ReplicateScaleFilter {
private int mXSub;
private int mYSub;
private int xSub;
private int ySub;
/**
* Creates a {@code SubsamplingFilter}.
@@ -62,16 +62,16 @@ public class SubsamplingFilter extends ReplicateScaleFilter {
throw new IllegalArgumentException("Subsampling factors must be positive.");
}
mXSub = pXSub;
mYSub = pYSub;
xSub = pXSub;
ySub = pYSub;
}
/** {@code ImageFilter} implementation, do not invoke. */
public void setDimensions(int pWidth, int pHeight) {
destWidth = (pWidth + mXSub - 1) / mXSub;
destHeight = (pHeight + mYSub - 1) / mYSub;
destWidth = (pWidth + xSub - 1) / xSub;
destHeight = (pHeight + ySub - 1) / ySub;
//System.out.println("Subsampling: " + mXSub + "," + mYSub + "-> " + destWidth + ", " + destHeight);
//System.out.println("Subsampling: " + xSub + "," + ySub + "-> " + destWidth + ", " + destHeight);
super.setDimensions(pWidth, pHeight);
}
}
@@ -4,6 +4,6 @@
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
*
* @version 1.0
* @author <a href="mailto:harald@escenic.com">Harald Kuhr</a>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
package com.twelvemonkeys.image;
@@ -0,0 +1,366 @@
package com.twelvemonkeys.image;
import org.junit.Ignore;
import org.junit.Test;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.net.URL;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* BufferedImageFactoryTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedImageFactoryTestCase.java,v 1.0 May 7, 2010 12:40:08 PM haraldk Exp$
*/
public class BufferedImageFactoryTestCase {
@Test(expected = IllegalArgumentException.class)
public void testCreateNullImage() {
new BufferedImageFactory((Image) null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNullProducer() {
new BufferedImageFactory((ImageProducer) null);
}
// NPE in Toolkit, ok
@Test(expected = RuntimeException.class)
public void testGetBufferedImageErrorSourceByteArray() {
Image source = Toolkit.getDefaultToolkit().createImage((byte[]) null);
new BufferedImageFactory(source);
}
@Test(expected = IllegalArgumentException.class)
public void testGetBufferedImageErrorSourceImageProducer() {
Image source = Toolkit.getDefaultToolkit().createImage((ImageProducer) null);
new BufferedImageFactory(source);
}
// TODO: This is a quite serious bug, however, the bug is in the Toolkit, allowing such images in the first place...
// In any case, there's not much we can do, except until someone is bored and kills the app/thread... :-P
@Ignore("Bug in Toolkit")
@Test(timeout = 1000, expected = ImageConversionException.class)
public void testGetBufferedImageErrorSourceString() {
Image source = Toolkit.getDefaultToolkit().createImage((String) null);
BufferedImageFactory factory = new BufferedImageFactory(source);
factory.getBufferedImage();
}
// This is a little random, and it would be nicer if we could throw an IllegalArgumentException on create.
// Unfortunately, the API doesn't allow this...
@Test(timeout = 1000, expected = ImageConversionException.class)
public void testGetBufferedImageErrorSourceURL() {
Image source = Toolkit.getDefaultToolkit().createImage(getClass().getResource("/META-INF/MANIFEST.MF"));
BufferedImageFactory factory = new BufferedImageFactory(source);
factory.getBufferedImage();
}
@Test
public void testGetBufferedImageJPEG() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage image = factory.getBufferedImage();
assertEquals(187, image.getWidth());
assertEquals(283, image.getHeight());
}
@Test
public void testGetColorModelJPEG() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
ColorModel colorModel = factory.getColorModel();
assertNotNull(colorModel);
assertEquals(3, colorModel.getNumColorComponents()); // getNumComponents may include alpha, we don't care
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
for (int i = 0; i < colorModel.getNumComponents(); i++) {
assertEquals(8, colorModel.getComponentSize(i));
}
}
@Test
public void testGetBufferedImageGIF() {
URL resource = getClass().getResource("/tux.gif");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage image = factory.getBufferedImage();
assertEquals(250, image.getWidth());
assertEquals(250, image.getHeight());
assertEquals(Transparency.BITMASK, image.getTransparency());
// All corners of image should be fully transparent
assertEquals(0, image.getRGB(0, 0) >>> 24);
assertEquals(0, image.getRGB(249, 0) >>> 24);
assertEquals(0, image.getRGB(0, 249) >>> 24);
assertEquals(0, image.getRGB(249, 249) >>> 24);
}
@Test
public void testGetColorModelGIF() {
URL resource = getClass().getResource("/tux.gif");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
ColorModel colorModel = factory.getColorModel();
assertNotNull(colorModel);
assertEquals(3, colorModel.getNumColorComponents());
assertEquals(ColorSpace.getInstance(ColorSpace.CS_sRGB), colorModel.getColorSpace());
assertTrue(colorModel instanceof IndexColorModel);
assertTrue(colorModel.hasAlpha());
assertEquals(4, colorModel.getNumComponents());
assertTrue(((IndexColorModel) colorModel).getTransparentPixel() >= 0);
assertEquals(Transparency.BITMASK, colorModel.getTransparency());
for (int i = 0; i < colorModel.getNumComponents(); i++) {
assertEquals(8, colorModel.getComponentSize(i));
}
}
@Test
public void testGetBufferedImageSubsampled() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceSubsampling(2, 2);
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
// Values rounded up
assertEquals(94, image.getWidth());
assertEquals(142, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(x * 2, y * 2), image.getRGB(x, y));
}
}
}
@Test
public void testGetBufferedImageSourceRegion() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
assertEquals(40, image.getWidth());
assertEquals(40, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x, 40 + y), image.getRGB(x, y));
}
}
}
@Test
public void testGetBufferedImageSubsampledSourceRegion() throws Exception{
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
BufferedImage original = factory.getBufferedImage();
factory.setSourceRegion(new Rectangle(40, 40, 40, 40));
factory.setSourceSubsampling(2, 2);
BufferedImage image = factory.getBufferedImage(); // Accidentally also tests reuse...
assertEquals(20, image.getWidth());
assertEquals(20, image.getHeight());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
assertEquals("RGB[" + x + ", " + y + "]", original.getRGB(40 + x * 2, 40 + y * 2), image.getRGB(x, y));
}
}
}
@Test
public void testAbort() throws Exception {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
final BufferedImageFactory factory = new BufferedImageFactory(source);
// Listener should abort ASAP
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
if (pPercentage > 5) {
pFactory.abort();
}
}
});
BufferedImage image = factory.getBufferedImage();
assertEquals(187, image.getWidth());
assertEquals(283, image.getHeight());
// Upper right should be loaded
assertEquals((image.getRGB(186, 0) & 0xFF0000) >> 16 , 0x68, 10);
assertEquals((image.getRGB(186, 0) & 0xFF00) >> 8, 0x91, 10);
assertEquals(image.getRGB(186, 0) & 0xFF, 0xE0, 10);
// Lower right should be blank
assertEquals(image.getRGB(186, 282) & 0xFFFFFF, 0);
}
@Test
public void testListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.getBufferedImage();
listener.verify(100f);
}
@Test
public void testRemoveListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(listener);
factory.getBufferedImage();
listener.verify(0);
}
@Test
public void testRemoveNullListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(null);
factory.getBufferedImage();
listener.verify(100);
}
@Test
public void testRemoveNotAdddedListener() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
}
});
factory.getBufferedImage();
listener.verify(100);
}
@Test
public void testRemoveAllListeners() {
URL resource = getClass().getResource("/sunflower.jpg");
assertNotNull(resource);
Image source = Toolkit.getDefaultToolkit().createImage(resource);
assertNotNull(source);
BufferedImageFactory factory = new BufferedImageFactory(source);
VerifyingListener listener = new VerifyingListener(factory);
VerifyingListener listener2 = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.addProgressListener(listener);
factory.addProgressListener(listener2);
factory.removeAllProgressListeners();
factory.getBufferedImage();
listener.verify(0);
listener2.verify(0);
}
private static class VerifyingListener implements BufferedImageFactory.ProgressListener {
private final BufferedImageFactory factory;
private float progress;
public VerifyingListener(BufferedImageFactory factory) {
this.factory = factory;
}
public void progress(BufferedImageFactory pFactory, float pPercentage) {
assertEquals(factory, pFactory);
assertTrue(pPercentage >= progress && pPercentage <= 100f);
progress = pPercentage;
}
public void verify(final float expectedProgress) {
assertEquals(expectedProgress, progress, .1f); // Sanity test that the listener was invoked
}
}
}
@@ -1,61 +1,53 @@
package com.twelvemonkeys.image;
import junit.framework.TestCase;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
/**
* Created by IntelliJ IDEA.
*
* @author $author wmhakur$
* @version $id: $
* To change this template use Options | File Templates.
*/
public class ImageUtilTestCase extends TestCase {
import static org.junit.Assert.*;
public class ImageUtilTestCase {
private final static String IMAGE_NAME = "/sunflower.jpg";
private BufferedImage mOriginal;
private BufferedImage mImage;
private Image mScaled;
private BufferedImage original;
private BufferedImage image;
private Image scaled;
public ImageUtilTestCase() throws Exception {
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
// Read image from class path
InputStream is = getClass().getResourceAsStream(IMAGE_NAME);
mOriginal = ImageIO.read(is);
original = ImageIO.read(is);
assertNotNull(mOriginal);
assertNotNull(original);
}
/*
public void setUp() throws Exception {
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
// Read image from class path
InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME);
mOriginal = ImageIO.read(is);
original = ImageIO.read(is);
assertNotNull(mOriginal);
assertNotNull(original);
}
protected void tearDown() throws Exception {
mOriginal = null;
original = null;
}
*/
@Test
public void testToBufferedImageNull() {
BufferedImage img = null;
boolean threwRuntimeException = false;
@@ -73,6 +65,7 @@ public class ImageUtilTestCase extends TestCase {
assertTrue(threwRuntimeException);
}
@Test
public void testToBufferedImageTypeNull() {
BufferedImage img = null;
boolean threwRuntimeException = false;
@@ -90,54 +83,58 @@ public class ImageUtilTestCase extends TestCase {
assertTrue(threwRuntimeException);
}
@Test
public void testImageIsNotBufferedImage() {
// Should not be a buffered image
assertFalse(
"FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.",
mScaled instanceof BufferedImage
scaled instanceof BufferedImage
);
}
@Test
public void testToBufferedImage() {
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) mImage);
BufferedImage bufferedScaled = ImageUtil.toBuffered(mScaled);
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image);
BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled);
// Should be no need to convert
assertSame(mImage, sameAsImage);
assertSame(image, sameAsImage);
// Should have same dimensions
assertEquals(mScaled.getWidth(null), bufferedScaled.getWidth());
assertEquals(mScaled.getHeight(null), bufferedScaled.getHeight());
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);
|| bufferedScaled.getPropertyNames() == null
|| bufferedScaled.getPropertyNames().length == 0);
}
@Test
public void testToBufferedImageType() {
// Assumes mImage is TYPE_INT_ARGB
BufferedImage converted = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_INDEXED);
BufferedImage convertedToo = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_BINARY);
// Assumes image is TYPE_INT_ARGB
BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED);
BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY);
// Should not be the same
assertNotSame(mImage, converted);
assertNotSame(mImage, convertedToo);
assertNotSame(image, converted);
assertNotSame(image, convertedToo);
// Correct type
assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED);
assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY);
// Should have same dimensions
assertEquals(mImage.getWidth(), converted.getWidth());
assertEquals(mImage.getHeight(), converted.getHeight());
assertEquals(image.getWidth(), converted.getWidth());
assertEquals(image.getHeight(), converted.getHeight());
assertEquals(mImage.getWidth(), convertedToo.getWidth());
assertEquals(mImage.getHeight(), convertedToo.getHeight());
assertEquals(image.getWidth(), convertedToo.getWidth());
assertEquals(image.getHeight(), convertedToo.getHeight());
}
@Test
public void testBrightness() {
final BufferedImage original = mOriginal;
final BufferedImage original = this.original;
assertNotNull(original);
final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f));
@@ -181,7 +178,7 @@ public class ImageUtilTestCase extends TestCase {
final BufferedImage brightenedMaxNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -2f));
for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) {
for (int x = 0; x < brightenedMaxNegative.getWidth(); x++) {
assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF);
assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF);
}
}
@@ -215,9 +212,9 @@ public class ImageUtilTestCase extends TestCase {
*/
}
@Test
public void testContrast() {
final BufferedImage original = mOriginal;
final BufferedImage original = this.original;
assertNotNull(original);
@@ -273,7 +270,6 @@ public class ImageUtilTestCase extends TestCase {
else {
assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB);
}
}
}
// Assumed: Only primary colors (w/b/r/g/b/c/y/m)
@@ -337,7 +333,7 @@ public class ImageUtilTestCase extends TestCase {
int r = rgb >> 16 & 0xFF;
int g = rgb >> 8 & 0xFF;
int b = rgb & 0xFF;
assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 &&b == 127);
assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 && b == 127);
}
}
@@ -369,8 +365,9 @@ public class ImageUtilTestCase extends TestCase {
*/
}
@Test
public void testSharpen() {
final BufferedImage original = mOriginal;
final BufferedImage original = this.original;
assertNotNull(original);
@@ -390,10 +387,10 @@ public class ImageUtilTestCase extends TestCase {
final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f);
final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f);
long diffOriginal = 0;
long diffSharpened = 0;
long diffDefault = 0;
long diffMore = 0;
// long diffOriginal = 0;
// long diffSharpened = 0;
// long diffDefault = 0;
// long diffMore = 0;
long absDiffOriginal = 0;
long absDiffSharpened = 0;
@@ -412,10 +409,10 @@ public class ImageUtilTestCase extends TestCase {
int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y);
int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y);
diffOriginal += poRGB - oRGB;
diffSharpened += psRGB - sRGB;
diffDefault += pdRGB - dRGB;
diffMore += pmRGB - mRGB;
// diffOriginal += poRGB - oRGB;
// diffSharpened += psRGB - sRGB;
// diffDefault += pdRGB - dRGB;
// diffMore += pmRGB - mRGB;
absDiffOriginal += Math.abs(poRGB - oRGB);
absDiffSharpened += Math.abs(psRGB - sRGB);
@@ -424,10 +421,6 @@ public class ImageUtilTestCase extends TestCase {
}
}
//*
showEm(original, notSharpened, sharpened, sharpenedDefault, sharpenedMore, "sharpen");
//*/
// assertEquals("Difference should not change", diffOriginal, diffSharpened);
assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened);
// assertEquals("Difference should not change", diffOriginal, diffDefault);
@@ -438,64 +431,9 @@ public class ImageUtilTestCase extends TestCase {
assertTrue("Abs difference should increase", absDiffSharpened < absDiffMore);
}
private void showEm(final BufferedImage pOriginal, final BufferedImage pNotSharpened, final BufferedImage pSharpened, final BufferedImage pSharpenedDefault, final BufferedImage pSharpenedMore, final String pTitle) {
if (pOriginal != null) {
return;
}
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
JFrame frame = new JFrame("Sunflower - " + pTitle);
frame.setSize(pOriginal.getWidth() * 4, pOriginal.getHeight() * 2);
Canvas canvas = new Canvas() {
public void paint(Graphics g) {
// Draw original for comparison
g.drawImage(pOriginal, 0, 0, null);
// This should look like original
g.drawImage(pNotSharpened, 0, pOriginal.getHeight(), null);
// Different versions
g.drawImage(pSharpened, pOriginal.getWidth(), 0, null);
g.drawImage(pSharpenedDefault, pOriginal.getWidth() * 2, 0, null);
g.drawImage(pSharpenedMore, pOriginal.getWidth() * 3, 0, null);
}
};
frame.getContentPane().add(canvas);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
synchronized (ImageUtilTestCase.this) {
ImageUtilTestCase.this.notify();
}
}
});
frame.setVisible(true);
}
});
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
synchronized (ImageUtilTestCase.this) {
try {
ImageUtilTestCase.this.wait();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@Test
public void testBlur() {
final BufferedImage original = mOriginal;
final BufferedImage original = this.original;
assertNotNull(original);
@@ -515,17 +453,16 @@ public class ImageUtilTestCase extends TestCase {
final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f);
final BufferedImage blurredMore = ImageUtil.blur(original, 3f);
long diffOriginal = 0;
long diffBlurred = 0;
long diffDefault = 0;
long diffMore = 0;
// long diffOriginal = 0;
// long diffBlurred = 0;
// long diffDefault = 0;
// long diffMore = 0;
long absDiffOriginal = 0;
long absDiffBlurred = 0;
long absDiffDefault = 0;
long absDiffMore = 0;
for (int y = 0; y < original.getHeight(); y++) {
for (int x = 1; x < original.getWidth(); x++) {
int oRGB = 0x00FFFFFF & original.getRGB(x, y);
@@ -538,10 +475,10 @@ public class ImageUtilTestCase extends TestCase {
int pdRGB = 0x00FFFFFF & blurredDefault.getRGB(x - 1, y);
int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y);
diffOriginal += poRGB - oRGB;
diffBlurred += pbRGB - bRGB;
diffDefault += pdRGB - dRGB;
diffMore += pmRGB - mRGB;
// diffOriginal += poRGB - oRGB;
// diffBlurred += pbRGB - bRGB;
// diffDefault += pdRGB - dRGB;
// diffMore += pmRGB - mRGB;
absDiffOriginal += Math.abs(poRGB - oRGB);
absDiffBlurred += Math.abs(pbRGB - bRGB);
@@ -550,8 +487,6 @@ public class ImageUtilTestCase extends TestCase {
}
}
showEm(original, notBlurred, blurred, blurredDefault, blurredMore, "blur");
// assertEquals("Difference should not change", diffOriginal, diffBlurred);
assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred);
// assertEquals("Difference should not change", diffOriginal, diffDefault);
@@ -562,8 +497,9 @@ public class ImageUtilTestCase extends TestCase {
assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore);
}
@Test
public void testIndexImage() {
BufferedImage sunflower = mOriginal;
BufferedImage sunflower = original;
assertNotNull(sunflower);
@@ -571,4 +507,4 @@ public class ImageUtilTestCase extends TestCase {
assertNotNull("Image was null", image);
assertTrue(image.getColorModel() instanceof IndexColorModel);
}
}
}
@@ -1,6 +1,7 @@
package com.twelvemonkeys.image;
import junit.framework.TestCase;
import org.junit.Ignore;
import org.junit.Test;
import java.awt.*;
import java.awt.image.BufferedImage;
@@ -9,6 +10,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.*;
/**
* ResampleOpTestCase
*
@@ -16,7 +19,7 @@ import java.util.List;
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java#1 $
*/
public class ResampleOpTestCase extends TestCase {
public class ResampleOpTestCase {
protected BufferedImage createImage(final int pWidth, final int pHeigth) {
return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB);
@@ -36,6 +39,7 @@ public class ResampleOpTestCase extends TestCase {
return image;
}
@Test
public void testCreateImage() {
// Sanity test the create method
BufferedImage image = createImage(79, 84);
@@ -94,170 +98,225 @@ public class ResampleOpTestCase extends TestCase {
}
// 1x1
@Test
public void testResample1x1Point() {
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample1x1Box() {
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample1x1Triangle() {
assertResample(createImage(1, 1), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample1x1Lanczos() {
assertResample(createImage(1, 1), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample1x1Gaussian() {
assertResample(createImage(1, 1), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample1x1Sinc() {
assertResample(createImage(1, 1), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// 2x2
@Test
public void testResample2x2Point() {
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample2x2Box() {
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample2x2Triangle() {
assertResample(createImage(2, 2), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample2x2Lanczos() {
assertResample(createImage(2, 2), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample2x2Gaussian() {
assertResample(createImage(2, 2), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample2x2Sinc() {
assertResample(createImage(2, 2), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// 3x3
@Test
public void testResample3x3Point() {
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample3x3Box() {
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample3x3Triangle() {
assertResample(createImage(3, 3), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample3x3Lanczos() {
assertResample(createImage(3, 3), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample3x3Gaussian() {
assertResample(createImage(3, 3), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample3x3Sinc() {
assertResample(createImage(3, 3), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// 4x4
@Test
public void testResample4x4Point() {
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample4x4Box() {
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample4x4Triangle() {
assertResample(createImage(4, 4), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample4x4Lanczos() {
assertResample(createImage(4, 4), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample4x4Gaussian() {
assertResample(createImage(4, 4), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample4x4Sinc() {
assertResample(createImage(4, 4), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// 20x20
@Test
public void testResample20x20Point() {
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample20x20Box() {
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample20x20Triangle() {
assertResample(createImage(20, 20), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample20x20Lanczos() {
assertResample(createImage(20, 20), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample20x20Gaussian() {
assertResample(createImage(20, 20), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample20x20Sinc() {
assertResample(createImage(20, 20), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// 200x160
@Test
public void testResample200x160Point() {
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_POINT);
}
@Test
public void testResample200x160Box() {
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_BOX);
}
@Test
public void testResample200x160Triangle() {
assertResample(createImage(200, 160), 19, 13, ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResample200x160Lanczos() {
assertResample(createImage(200, 160), 7, 49, ResampleOp.FILTER_LANCZOS);
}
@Test
public void testResample200x160Gaussian() {
assertResample(createImage(200, 160), 11, 34, ResampleOp.FILTER_GAUSSIAN);
}
@Test
public void testResample200x160Sinc() {
assertResample(createImage(200, 160), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
}
// Test 10x10 -> 15x5 with different algorithms and types
@Test
public void testResamplePoint() {
assertResampleBufferedImageTypes(ResampleOp.FILTER_POINT);
}
@Test
public void testResampleBox() {
assertResampleBufferedImageTypes(ResampleOp.FILTER_BOX);
}
@Test
public void testResampleTriangle() {
assertResampleBufferedImageTypes(ResampleOp.FILTER_TRIANGLE);
}
@Test
public void testResampleLanczos() {
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
}
@Ignore("Not for general unit testing")
@Test
public void testTime() {
int iterations = 1000;
for (int i = 0; i < iterations; i++) {
assertResample(createImage(50, 50), 33, 33, ResampleOp.FILTER_LANCZOS);
}
long start = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
assertResample(createImage(512, 512), 145, 145, ResampleOp.FILTER_LANCZOS);
}
long end = System.currentTimeMillis();
System.out.printf("time: %d ms, avg %s ms%n", end - start, (end - start) / (double) iterations);
}
}

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

+6
View File
@@ -0,0 +1,6 @@
TODO:
Remove compile-time dependency on JMagick:
- Extract interface for MagickAccelerator
- Move implementation to separate module
- Instantiate impl via reflection
DONE:
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.0.3-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: IO</name>
<description>
The TwelveMonkeys Common IO support
</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>common-lang</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -14,28 +14,28 @@ import java.io.InputStream;
*/
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
/** The backing stream */
protected final InputStream mStream;
protected final InputStream stream;
/** The stream positon in the backing stream (mStream) */
protected long mStreamPosition;
/** The stream positon in the backing stream (stream) */
protected long streamPosition;
private StreamCache mCache;
private StreamCache cache;
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
Validate.notNull(pStream, "stream");
Validate.notNull(pCache, "cache");
mStream = pStream;
mCache = pCache;
stream = pStream;
cache = pCache;
}
protected final StreamCache getCache() {
return mCache;
return cache;
}
@Override
public int available() throws IOException {
long avail = mStreamPosition - mPosition + mStream.available();
long avail = streamPosition - position + stream.available();
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
}
@@ -43,26 +43,26 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
checkOpen();
int read;
if (mPosition == mStreamPosition) {
if (position == streamPosition) {
// TODO: Read more bytes here!
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
// Read a byte from the stream
read = mStream.read();
read = stream.read();
if (read >= 0) {
mStreamPosition++;
mCache.write(read);
streamPosition++;
cache.write(read);
}
}
else {
// ..or read byte from the cache
syncPosition();
read = mCache.read();
read = cache.read();
}
// TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) {
mPosition++;
position++;
}
return read;
@@ -73,32 +73,32 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
checkOpen();
int length;
if (mPosition == mStreamPosition) {
if (position == streamPosition) {
// Read bytes from the stream
length = mStream.read(pBytes, pOffset, pLength);
length = stream.read(pBytes, pOffset, pLength);
if (length > 0) {
mStreamPosition += length;
mCache.write(pBytes, pOffset, length);
streamPosition += length;
cache.write(pBytes, pOffset, length);
}
}
else {
// ...or read bytes from the cache
syncPosition();
length = mCache.read(pBytes, pOffset, pLength);
length = cache.read(pBytes, pOffset, pLength);
}
// TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) {
mPosition += length;
position += length;
}
return length;
}
protected final void syncPosition() throws IOException {
if (mCache.getPosition() != mPosition) {
mCache.seek(mPosition); // Assure EOF is correctly thrown
if (cache.getPosition() != position) {
cache.seek(position); // Assure EOF is correctly thrown
}
}
@@ -111,14 +111,14 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
public abstract boolean isCachedFile();
protected void seekImpl(long pPosition) throws IOException {
if (mStreamPosition < pPosition) {
if (streamPosition < pPosition) {
// Make sure we append at end of cache
if (mCache.getPosition() != mStreamPosition) {
mCache.seek(mStreamPosition);
if (cache.getPosition() != streamPosition) {
cache.seek(streamPosition);
}
// Read diff from stream into cache
long left = pPosition - mStreamPosition;
long left = pPosition - streamPosition;
// TODO: Use fixed buffer, instead of allocating here...
int bufferLen = left > 1024 ? 1024 : (int) left;
@@ -126,11 +126,11 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
while (left > 0) {
int length = buffer.length < left ? buffer.length : (int) left;
int read = mStream.read(buffer, 0, length);
int read = stream.read(buffer, 0, length);
if (read > 0) {
mCache.write(buffer, 0, read);
mStreamPosition += read;
cache.write(buffer, 0, read);
streamPosition += read;
left -= read;
}
else if (read < 0) {
@@ -138,27 +138,27 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
}
}
}
else if (mStreamPosition >= pPosition) {
else /*if (streamPosition >= pPosition) */ {
// Seek backwards into the cache
mCache.seek(pPosition);
cache.seek(pPosition);
}
// System.out.println("pPosition: " + pPosition);
// System.out.println("mPosition: " + mPosition);
// System.out.println("mStreamPosition: " + mStreamPosition);
// System.out.println("mCache.mPosition: " + mCache.getPosition());
// System.out.println("position: " + position);
// System.out.println("streamPosition: " + streamPosition);
// System.out.println("cache.position: " + cache.getPosition());
// NOTE: If mPosition == pPosition then we're good to go
// NOTE: If position == pPosition then we're good to go
}
protected void flushBeforeImpl(long pPosition) {
mCache.flush(pPosition);
cache.flush(pPosition);
}
protected void closeImpl() throws IOException {
mCache.flush(mPosition);
mCache = null;
mStream.close();
cache.flush(position);
cache = null;
stream.close();
}
/**
@@ -46,15 +46,16 @@ import java.util.List;
*/
public class CompoundReader extends Reader {
private Reader mCurrent;
private List<Reader> mReaders;
protected final Object mLock;
private Reader current;
private List<Reader> readers;
protected final boolean mMarkSupported;
protected final Object finalLock;
private int mCurrentReader;
private int mMarkedReader;
private int mMark;
protected final boolean markSupported;
private int currentReader;
private int markedReader;
private int mark;
private int mNext;
/**
@@ -71,10 +72,10 @@ public class CompoundReader extends Reader {
public CompoundReader(final Iterator<Reader> pReaders) {
super(Validate.notNull(pReaders, "readers"));
mLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
// reference can't change, only it's elements
mReaders = new ArrayList<Reader>();
readers = new ArrayList<Reader>();
boolean markSupported = true;
while (pReaders.hasNext()) {
@@ -82,25 +83,25 @@ public class CompoundReader extends Reader {
if (reader == null) {
throw new NullPointerException("readers cannot contain null-elements");
}
mReaders.add(reader);
readers.add(reader);
markSupported = markSupported && reader.markSupported();
}
mMarkSupported = markSupported;
this.markSupported = markSupported;
mCurrent = nextReader();
current = nextReader();
}
protected final Reader nextReader() {
if (mCurrentReader >= mReaders.size()) {
mCurrent = new EmptyReader();
if (currentReader >= readers.size()) {
current = new EmptyReader();
}
else {
mCurrent = mReaders.get(mCurrentReader++);
current = readers.get(currentReader++);
}
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0;
return mCurrent;
return current;
}
/**
@@ -109,17 +110,18 @@ public class CompoundReader extends Reader {
* @throws IOException if the stream is closed
*/
protected final void ensureOpen() throws IOException {
if (mReaders == null) {
if (readers == null) {
throw new IOException("Stream closed");
}
}
public void close() throws IOException {
// Close all readers
for (Reader reader : mReaders) {
for (Reader reader : readers) {
reader.close();
}
mReaders = null;
readers = null;
}
@Override
@@ -130,46 +132,46 @@ public class CompoundReader extends Reader {
// TODO: It would be nice if we could actually close some readers now
synchronized (mLock) {
synchronized (finalLock) {
ensureOpen();
mMark = mNext;
mMarkedReader = mCurrentReader;
mark = mNext;
markedReader = currentReader;
mCurrent.mark(pReadLimit);
current.mark(pReadLimit);
}
}
@Override
public void reset() throws IOException {
synchronized (mLock) {
synchronized (finalLock) {
ensureOpen();
if (mCurrentReader != mMarkedReader) {
if (currentReader != markedReader) {
// Reset any reader before this
for (int i = mCurrentReader; i >= mMarkedReader; i--) {
mReaders.get(i).reset();
for (int i = currentReader; i >= markedReader; i--) {
readers.get(i).reset();
}
mCurrentReader = mMarkedReader - 1;
currentReader = markedReader - 1;
nextReader();
}
mCurrent.reset();
current.reset();
mNext = mMark;
mNext = mark;
}
}
@Override
public boolean markSupported() {
return mMarkSupported;
return markSupported;
}
@Override
public int read() throws IOException {
synchronized (mLock) {
int read = mCurrent.read();
synchronized (finalLock) {
int read = current.read();
if (read < 0 && mCurrentReader < mReaders.size()) {
if (read < 0 && currentReader < readers.size()) {
nextReader();
return read(); // In case of 0-length readers
}
@@ -181,10 +183,10 @@ public class CompoundReader extends Reader {
}
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength);
synchronized (finalLock) {
int read = current.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentReader < mReaders.size()) {
if (read < 0 && currentReader < readers.size()) {
nextReader();
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
}
@@ -197,15 +199,15 @@ public class CompoundReader extends Reader {
@Override
public boolean ready() throws IOException {
return mCurrent.ready();
return current.ready();
}
@Override
public long skip(long pChars) throws IOException {
synchronized (mLock) {
long skipped = mCurrent.skip(pChars);
synchronized (finalLock) {
long skipped = current.skip(pChars);
if (skipped == 0 && mCurrentReader < mReaders.size()) {
if (skipped == 0 && currentReader < readers.size()) {
nextReader();
return skip(pChars); // In case of 0-length readers
}
@@ -39,11 +39,12 @@ import java.io.ByteArrayInputStream;
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java#2 $
* @version $Id: FastByteArrayOutputStream.java#2 $
*/
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
/** Max grow size (unless if writing more than this ammount of bytes) */
protected int mMaxGrowSize = 1024 * 1024; // 1 MB
/** Max grow size (unless if writing more than this amount of bytes) */
protected int maxGrowSize = 1024 * 1024; // 1 MB
/**
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
@@ -69,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
}
@Override
public synchronized void write(byte pBytes[], int pOffset, int pLength) {
public void write(byte pBytes[], int pOffset, int pLength) {
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException();
@@ -77,23 +78,24 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
else if (pLength == 0) {
return;
}
int newcount = count + pLength;
growIfNeeded(newcount);
int newCount = count + pLength;
growIfNeeded(newCount);
System.arraycopy(pBytes, pOffset, buf, count, pLength);
count = newcount;
count = newCount;
}
@Override
public synchronized void write(int pByte) {
int newcount = count + 1;
growIfNeeded(newcount);
public void write(int pByte) {
int newCount = count + 1;
growIfNeeded(newCount);
buf[count] = (byte) pByte;
count = newcount;
count = newCount;
}
private void growIfNeeded(int pNewcount) {
if (pNewcount > buf.length) {
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount);
private void growIfNeeded(int pNewCount) {
if (pNewCount > buf.length) {
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
byte newBuf[] = new byte[newSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
@@ -109,9 +111,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
// Non-synchronized version of toByteArray
@Override
public byte[] toByteArray() {
byte newbuf[] = new byte[count];
System.arraycopy(buf, 0, newbuf, 0, count);
return newbuf;
byte newBuf[] = new byte[count];
System.arraycopy(buf, 0, newBuf, 0, count);
return newBuf;
}
/**
@@ -121,7 +124,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
* <p/>
* Note that care needs to be taken to avoid writes to
* this output stream after the input stream is created.
* Failing to do so, may result in unpredictable behviour.
* Failing to do so, may result in unpredictable behaviour.
*
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
*/
@@ -48,16 +48,7 @@ import java.io.*;
*/
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
// private final InputStream mStream;
// private final RandomAccessFile mCache;
private byte[] mBuffer;
/** The stream positon in the backing stream (mStream) */
// private long mStreamPosition;
// TODO: getStreamPosition() should always be the same as
// mCache.getFilePointer()
// otherwise there's some inconsistency here... Enforce this?
private byte[] buffer;
/**
* Creates a {@code FileCacheSeekableStream} reading from the given
@@ -118,7 +109,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
super(pStream, new FileCache(pFile));
// TODO: Allow for custom buffer sizes?
mBuffer = new byte[1024];
buffer = new byte[1024];
}
public final boolean isCachedMemory() {
@@ -132,39 +123,19 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
@Override
protected void closeImpl() throws IOException {
super.closeImpl();
mBuffer = null;
buffer = null;
}
/*
public final boolean isCached() {
return true;
}
// InputStream overrides
@Override
public int available() throws IOException {
long avail = mStreamPosition - mPosition + mStream.available();
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
}
public void closeImpl() throws IOException {
mStream.close();
mCache.close();
// TODO: Delete cache file here?
// ThreadPool.invokeLater(new DeleteFileAction(mCacheFile));
}
*/
@Override
public int read() throws IOException {
checkOpen();
int read;
if (mPosition == mStreamPosition) {
if (position == streamPosition) {
// Read ahead into buffer, for performance
read = readAhead(mBuffer, 0, mBuffer.length);
read = readAhead(buffer, 0, buffer.length);
if (read >= 0) {
read = mBuffer[0] & 0xff;
read = buffer[0] & 0xff;
}
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
@@ -179,7 +150,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
// TODO: This field is not REALLY considered accessible.. :-P
if (read != -1) {
mPosition++;
position++;
}
return read;
}
@@ -189,7 +160,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
checkOpen();
int length;
if (mPosition == mStreamPosition) {
if (position == streamPosition) {
// Read bytes from the stream
length = readAhead(pBytes, pOffset, pLength);
@@ -198,83 +169,29 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
else {
// ...or read bytes from the cache
syncPosition();
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, mStreamPosition - mPosition));
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, streamPosition - position));
//System.out.println("Read " + length + " byte from cache");
}
// TODO: This field is not REALLY considered accessible.. :-P
if (length > 0) {
mPosition += length;
position += length;
}
return length;
}
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
int length;
length = mStream.read(pBytes, pOffset, pLength);
length = stream.read(pBytes, pOffset, pLength);
if (length > 0) {
mStreamPosition += length;
streamPosition += length;
getCache().write(pBytes, pOffset, length);
}
return length;
}
/*
private void syncPosition() throws IOException {
if (mCache.getFilePointer() != mPosition) {
mCache.seek(mPosition); // Assure EOF is correctly thrown
}
}
// Seekable overrides
protected void flushBeforeImpl(long pPosition) {
// TODO: Implement
// For now, it's probably okay to do nothing, this is just for
// performance (as long as people follow spec, not behaviour)
}
protected void seekImpl(long pPosition) throws IOException {
if (mStreamPosition < pPosition) {
// Make sure we append at end of cache
if (mCache.getFilePointer() != mStreamPosition) {
mCache.seek(mStreamPosition);
}
// Read diff from stream into cache
long left = pPosition - mStreamPosition;
int bufferLen = left > 1024 ? 1024 : (int) left;
byte[] buffer = new byte[bufferLen];
while (left > 0) {
int length = buffer.length < left ? buffer.length : (int) left;
int read = mStream.read(buffer, 0, length);
if (read > 0) {
mCache.write(buffer, 0, read);
mStreamPosition += read;
left -= read;
}
else if (read < 0) {
break;
}
}
}
else if (mStreamPosition >= pPosition) {
// Seek backwards into the cache
mCache.seek(pPosition);
}
// System.out.println("pPosition: " + pPosition);
// System.out.println("mStreamPosition: " + mStreamPosition);
// System.out.println("mCache.getFilePointer(): " + mCache.getFilePointer());
// NOTE: If mPosition == pPosition then we're good to go
}
*/
final static class FileCache extends StreamCache {
private RandomAccessFile mCacheFile;
@@ -87,7 +87,7 @@ public final class FileSeekableStream extends SeekableInputStream {
@Override
public int available() throws IOException {
long length = mRandomAccess.length() - mPosition;
long length = mRandomAccess.length() - position;
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
}
@@ -100,7 +100,7 @@ public final class FileSeekableStream extends SeekableInputStream {
int read = mRandomAccess.read();
if (read >= 0) {
mPosition++;
position++;
}
return read;
}
@@ -111,7 +111,7 @@ public final class FileSeekableStream extends SeekableInputStream {
int read = mRandomAccess.read(pBytes, pOffset, pLength);
if (read > 0) {
mPosition += read;
position += read;
}
return read;
}
@@ -38,7 +38,7 @@ import java.io.InputStreamReader;
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSystem.java#1 $
* @version $Id: FileSystem.java#1 $
*/
abstract class FileSystem {
abstract long getFreeSpace(File pPath);
@@ -57,21 +57,21 @@ abstract class FileSystem {
//System.out.println("os = " + os);
os = os.toLowerCase();
if (os.indexOf("windows") != -1) {
if (os.contains("windows")) {
return new Win32FileSystem();
}
else if (os.indexOf("linux") != -1 ||
os.indexOf("sun os") != -1 ||
os.indexOf("sunos") != -1 ||
os.indexOf("solaris") != -1 ||
os.indexOf("mpe/ix") != -1 ||
os.indexOf("hp-ux") != -1 ||
os.indexOf("aix") != -1 ||
os.indexOf("freebsd") != -1 ||
os.indexOf("irix") != -1 ||
os.indexOf("digital unix") != -1 ||
os.indexOf("unix") != -1 ||
os.indexOf("mac os x") != -1) {
else if (os.contains("linux") ||
os.contains("sun os") ||
os.contains("sunos") ||
os.contains("solaris") ||
os.contains("mpe/ix") ||
os.contains("hp-ux") ||
os.contains("aix") ||
os.contains("freebsd") ||
os.contains("irix") ||
os.contains("digital unix") ||
os.contains("unix") ||
os.contains("mac os x")) {
return new UnixFileSystem();
}
else {
@@ -80,10 +80,10 @@ abstract class FileSystem {
}
private static class UnknownFileSystem extends FileSystem {
private final String mOSName;
private final String osName;
UnknownFileSystem(String pOSName) {
mOSName = pOSName;
osName = pOSName;
}
long getFreeSpace(File pPath) {
@@ -95,7 +95,7 @@ abstract class FileSystem {
}
String getName() {
return "Unknown (" + mOSName + ")";
return "Unknown (" + osName + ")";
}
}
}
@@ -79,7 +79,7 @@ public final class FileUtil {
/*
* Method main for test only.
*/
*
public static void main0(String[] pArgs) {
if (pArgs.length != 2) {
System.out.println("usage: java Copy in out");
@@ -94,6 +94,7 @@ public final class FileUtil {
System.out.println(e.getMessage());
}
}
//*/
// Avoid instances/constructor showing up in API doc
private FileUtil() {}
@@ -186,6 +187,7 @@ public final class FileUtil {
if (!pOverWrite && pToFile.exists()) {
return false;
}
InputStream in = null;
OutputStream out = null;
@@ -202,7 +204,8 @@ public final class FileUtil {
close(in);
close(out);
}
return true; // If we got here, everything's probably okay.. ;-)
return true; // If we got here, everything is probably okay.. ;-)
}
/**
@@ -307,6 +310,8 @@ public final class FileUtil {
Validate.notNull(pFrom, "from");
Validate.notNull(pTo, "to");
// TODO: Consider using file channels for faster copy where possible
// Use buffer size two times byte array, to avoid i/o bottleneck
// TODO: Consider letting the client decide as this is sometimes not a good thing!
InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
@@ -322,31 +327,9 @@ public final class FileUtil {
// Flush out stream, to write any remaining buffered data
out.flush();
return true; // If we got here, everything's probably okay.. ;-)
return true; // If we got here, everything is probably okay.. ;-)
}
/*
// Consider using the example from
// http://developer.java.sun.com/developer/Books/performance/ch04.pdf
// Test if this is really faster. And what about a lot of concurrence?
// Have a pool of buffers? :-)
static final int BUFF_SIZE = 100000;
static final byte[] buffer = new byte[BUFF_SIZE];
public static void copy(InputStream in, OutputStream out) throws IOException {
while (true) {
synchronized (buffer) {
int amountRead = in.read(buffer);
if (amountRead == -1) {
break;
}
out.write(buffer, 0, amountRead);
}
}
}
*/
/**
* Gets the file (type) extension of the given file.
* A file extension is the part of the filename, after the last occurence
@@ -568,6 +551,7 @@ public final class FileUtil {
if (!pFile.exists()) {
throw new FileNotFoundException(pFile.toString());
}
byte[] bytes = new byte[(int) pFile.length()];
InputStream in = null;
@@ -586,6 +570,7 @@ public final class FileUtil {
finally {
close(in);
}
return bytes;
}
@@ -597,7 +582,7 @@ public final class FileUtil {
* @throws IOException if an i/o error occurs during read.
*/
public static byte[] read(InputStream pInput) throws IOException {
// Create bytearray
// Create byte array
ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
// Copy from stream to byte array
@@ -685,28 +670,28 @@ public final class FileUtil {
// a file array, which may throw OutOfMemoryExceptions for
// large directories/in low memory situations
class DeleteFilesVisitor implements Visitor<File> {
private int mFailedCount = 0;
private IOException mException = null;
private int failedCount = 0;
private IOException exception = null;
public void visit(final File pFile) {
try {
if (!delete(pFile, true)) {
mFailedCount++;
failedCount++;
}
}
catch (IOException e) {
mFailedCount++;
if (mException == null) {
mException = e;
failedCount++;
if (exception == null) {
exception = e;
}
}
}
boolean succeeded() throws IOException {
if (mException != null) {
throw mException;
if (exception != null) {
throw exception;
}
return mFailedCount == 0;
return failedCount == 0;
}
}
DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
@@ -886,6 +871,8 @@ public final class FileUtil {
return folder.listFiles();
}
// TODO: Rewrite to use regexp
FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
return folder.listFiles(filter);
}
@@ -1029,6 +1016,7 @@ public final class FileUtil {
* @return a human readable string representation
*/
public static String toHumanReadableSize(final long pSizeInBytes) {
// TODO: Rewrite to use String.format?
if (pSizeInBytes < 1024L) {
return pSizeInBytes + " Bytes";
}
@@ -1053,7 +1041,7 @@ public final class FileUtil {
private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() {
protected NumberFormat initialValue() {
NumberFormat format = NumberFormat.getNumberInstance();
// TODO: Consider making this locale/platfor specific, OR a method parameter...
// TODO: Consider making this locale/platform specific, OR a method parameter...
// format.setMaximumFractionDigits(2);
format.setMaximumFractionDigits(0);
return format;
@@ -1075,6 +1063,7 @@ public final class FileUtil {
*
* @see com.twelvemonkeys.util.Visitor
*/
@SuppressWarnings({"ResultOfMethodCallIgnored"})
public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) {
Validate.notNull(pDirectory, "directory");
Validate.notNull(pVisitor, "visitor");
@@ -56,13 +56,16 @@ import java.io.FilenameFilter;
* @see File#list(java.io.FilenameFilter) java.io.File.list
* @see FilenameFilter java.io.FilenameFilter
* @see WildcardStringParser
* @deprecated
*/
public class FilenameMaskFilter implements FilenameFilter {
// TODO: Rewrite to use regexp, or create new class
// Members
private String[] mFilenameMasksForInclusion;
private String[] mFilenameMasksForExclusion;
private boolean mInclusion = true;
private String[] filenameMasksForInclusion;
private String[] filenameMasksForExclusion;
private boolean inclusion = true;
/**
@@ -127,29 +130,29 @@ public class FilenameMaskFilter implements FilenameFilter {
* @param pFilenameMasksForInclusion the filename masks to include
*/
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
mFilenameMasksForInclusion = pFilenameMasksForInclusion;
filenameMasksForInclusion = pFilenameMasksForInclusion;
}
/**
* @return the current inclusion masks
*/
public String[] getFilenameMasksForInclusion() {
return mFilenameMasksForInclusion.clone();
return filenameMasksForInclusion.clone();
}
/**
* @param pFilenameMasksForExclusion the filename masks to exclude
*/
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
mFilenameMasksForExclusion = pFilenameMasksForExclusion;
mInclusion = false;
filenameMasksForExclusion = pFilenameMasksForExclusion;
inclusion = false;
}
/**
* @return the current exclusion masks
*/
public String[] getFilenameMasksForExclusion() {
return mFilenameMasksForExclusion.clone();
return filenameMasksForExclusion.clone();
}
/**
@@ -164,8 +167,8 @@ public class FilenameMaskFilter implements FilenameFilter {
WildcardStringParser parser;
// Check each filename string mask whether the file is to be accepted
if (mInclusion) { // Inclusion
for (String mask : mFilenameMasksForInclusion) {
if (inclusion) { // Inclusion
for (String mask : filenameMasksForInclusion) {
parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) {
@@ -181,7 +184,7 @@ public class FilenameMaskFilter implements FilenameFilter {
}
else {
// Exclusion
for (String mask : mFilenameMasksForExclusion) {
for (String mask : filenameMasksForExclusion) {
parser = new WildcardStringParser(mask);
if (parser.parseString(pName)) {
@@ -204,32 +207,32 @@ public class FilenameMaskFilter implements FilenameFilter {
StringBuilder retVal = new StringBuilder();
int i;
if (mInclusion) {
if (inclusion) {
// Inclusion
if (mFilenameMasksForInclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!");
if (filenameMasksForInclusion == null) {
retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
}
else {
retVal.append(mFilenameMasksForInclusion.length);
retVal.append(filenameMasksForInclusion.length);
retVal.append(" filename mask(s) - ");
for (i = 0; i < mFilenameMasksForInclusion.length; i++) {
for (i = 0; i < filenameMasksForInclusion.length; i++) {
retVal.append("\"");
retVal.append(mFilenameMasksForInclusion[i]);
retVal.append(filenameMasksForInclusion[i]);
retVal.append("\", \"");
}
}
}
else {
// Exclusion
if (mFilenameMasksForExclusion == null) {
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!");
if (filenameMasksForExclusion == null) {
retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
}
else {
retVal.append(mFilenameMasksForExclusion.length);
retVal.append(filenameMasksForExclusion.length);
retVal.append(" exclusion filename mask(s) - ");
for (i = 0; i < mFilenameMasksForExclusion.length; i++) {
for (i = 0; i < filenameMasksForExclusion.length; i++) {
retVal.append("\"");
retVal.append(mFilenameMasksForExclusion[i]);
retVal.append(filenameMasksForExclusion[i]);
retVal.append("\", \"");
}
}
@@ -38,6 +38,8 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.*;
/**
@@ -63,7 +65,8 @@ import java.io.*;
* @see java.io.DataOutput
*
* @author Elliotte Rusty Harold
* @version 1.0.3, 28 December 2002
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version 2
*/
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
@@ -75,10 +78,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
* @see java.io.FilterInputStream#in
*/
public LittleEndianDataInputStream(final InputStream pStream) {
super(pStream);
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
super(Validate.notNull(pStream, "stream"));
}
/**
@@ -93,9 +93,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/
public boolean readBoolean() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return b != 0;
}
@@ -110,9 +112,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/
public byte readByte() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
@@ -128,9 +132,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
*/
public int readUnsignedByte() throws IOException {
int b = in.read();
if (b < 0) {
throw new EOFException();
}
return b;
}
@@ -146,12 +152,14 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public short readShort() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
// only need to test last byte read
// if byte1 is -1 so is byte2
if (byte2 < 0) {
throw new EOFException();
}
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24);
}
/**
@@ -166,10 +174,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public int readUnsignedShort() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1;
}
@@ -185,10 +194,12 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public char readChar() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24));
}
@@ -210,8 +221,9 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (byte4 < 0) {
throw new EOFException();
}
return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
return (byte4 << 24) | ((byte3 << 24) >>> 8)
| ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24);
}
/**
@@ -236,11 +248,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (byte8 < 0) {
throw new EOFException();
}
return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
return (byte8 << 56) | ((byte7 << 56) >>> 8)
| ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24)
| ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40)
| ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56);
}
/**
@@ -260,16 +272,17 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
public String readUTF() throws IOException {
int byte1 = in.read();
int byte2 = in.read();
if (byte2 < 0) {
throw new EOFException();
}
int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes];
int numread = 0;
int numchars = 0;
while (numread < numbytes) {
int c1 = readUnsignedByte();
int c2, c3;
@@ -281,27 +294,34 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
}
else if (test == 12 || test == 13) { // two bytes
numread += 2;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
}
else if (test == 14) { // three bytes
numread += 3;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException();
}
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
}
else { // malformed
throw new UTFDataFormatException();
@@ -396,12 +416,16 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
if (pLength < 0) {
throw new IndexOutOfBoundsException();
}
int count = 0;
while (count < pLength) {
int read = in.read(pBytes, pOffset + count, pLength - count);
if (read < 0) {
throw new EOFException();
}
count += read;
}
}
@@ -38,6 +38,8 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.*;
/**
@@ -69,7 +71,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
/**
* The number of bytes written so far to the little endian output stream.
*/
protected int mWritten;
protected int bytesWritten;
/**
* Creates a new little endian output stream and chains it to the
@@ -79,10 +81,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* @see java.io.FilterOutputStream#out
*/
public LittleEndianDataOutputStream(OutputStream pStream) {
super(pStream);
if (pStream == null) {
throw new IllegalArgumentException("stream == null");
}
super(Validate.notNull(pStream, "stream"));
}
/**
@@ -93,7 +92,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/
public synchronized void write(int pByte) throws IOException {
out.write(pByte);
mWritten++;
bytesWritten++;
}
/**
@@ -105,10 +104,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* @param pLength the number of bytes to write.
* @throws IOException if the underlying stream throws an IOException.
*/
public synchronized void write(byte[] pBytes, int pOffset, int pLength)
throws IOException {
public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
out.write(pBytes, pOffset, pLength);
mWritten += pLength;
bytesWritten += pLength;
}
@@ -137,7 +135,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/
public void writeByte(int pByte) throws IOException {
out.write(pByte);
mWritten++;
bytesWritten++;
}
/**
@@ -150,7 +148,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
public void writeShort(int pShort) throws IOException {
out.write(pShort & 0xFF);
out.write((pShort >>> 8) & 0xFF);
mWritten += 2;
bytesWritten += 2;
}
/**
@@ -163,7 +161,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
public void writeChar(int pChar) throws IOException {
out.write(pChar & 0xFF);
out.write((pChar >>> 8) & 0xFF);
mWritten += 2;
bytesWritten += 2;
}
/**
@@ -178,7 +176,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((pInt >>> 8) & 0xFF);
out.write((pInt >>> 16) & 0xFF);
out.write((pInt >>> 24) & 0xFF);
mWritten += 4;
bytesWritten += 4;
}
@@ -198,7 +196,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((int) (pLong >>> 40) & 0xFF);
out.write((int) (pLong >>> 48) & 0xFF);
out.write((int) (pLong >>> 56) & 0xFF);
mWritten += 8;
bytesWritten += 8;
}
/**
@@ -235,10 +233,12 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/
public void writeBytes(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
out.write((byte) pString.charAt(i));
}
mWritten += length;
bytesWritten += length;
}
/**
@@ -253,12 +253,14 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
*/
public void writeChars(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
int c = pString.charAt(i);
out.write(c & 0xFF);
out.write((c >>> 8) & 0xFF);
}
mWritten += length * 2;
bytesWritten += length * 2;
}
/**
@@ -282,6 +284,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++;
}
@@ -299,8 +302,10 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write((numbytes >>> 8) & 0xFF);
out.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
out.write(c);
}
@@ -308,16 +313,16 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
out.write(0xE0 | ((c >> 12) & 0x0F));
out.write(0x80 | ((c >> 6) & 0x3F));
out.write(0x80 | (c & 0x3F));
mWritten += 2;
bytesWritten += 2;
}
else {
out.write(0xC0 | ((c >> 6) & 0x1F));
out.write(0x80 | (c & 0x3F));
mWritten += 1;
bytesWritten += 1;
}
}
mWritten += numchars + 2;
bytesWritten += numchars + 2;
}
/**
@@ -326,9 +331,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
* possible that this number is temporarily less than the actual
* number of bytes written.)
* @return the value of the {@code written} field.
* @see #mWritten
* @see #bytesWritten
*/
public int size() {
return mWritten;
return bytesWritten;
}
}
@@ -56,58 +56,58 @@ import java.nio.channels.FileChannel;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java#1 $
*/
public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
private RandomAccessFile mFile;
private RandomAccessFile file;
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
this(FileUtil.resolve(pName), pMode);
}
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
mFile = new RandomAccessFile(pFile, pMode);
file = new RandomAccessFile(pFile, pMode);
}
public void close() throws IOException {
mFile.close();
file.close();
}
public FileChannel getChannel() {
return mFile.getChannel();
return file.getChannel();
}
public FileDescriptor getFD() throws IOException {
return mFile.getFD();
return file.getFD();
}
public long getFilePointer() throws IOException {
return mFile.getFilePointer();
return file.getFilePointer();
}
public long length() throws IOException {
return mFile.length();
return file.length();
}
public int read() throws IOException {
return mFile.read();
return file.read();
}
public int read(final byte[] b) throws IOException {
return mFile.read(b);
return file.read(b);
}
public int read(final byte[] b, final int off, final int len) throws IOException {
return mFile.read(b, off, len);
return file.read(b, off, len);
}
public void readFully(final byte[] b) throws IOException {
mFile.readFully(b);
file.readFully(b);
}
public void readFully(final byte[] b, final int off, final int len) throws IOException {
mFile.readFully(b, off, len);
file.readFully(b, off, len);
}
public String readLine() throws IOException {
return mFile.readLine();
return file.readLine();
}
/**
@@ -121,10 +121,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public boolean readBoolean() throws IOException {
int b = mFile.read();
int b = file.read();
if (b < 0) {
throw new EOFException();
}
return b != 0;
}
@@ -138,10 +140,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public byte readByte() throws IOException {
int b = mFile.read();
int b = file.read();
if (b < 0) {
throw new EOFException();
}
return (byte) b;
}
@@ -156,10 +160,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedByte() throws IOException {
int b = mFile.read();
int b = file.read();
if (b < 0) {
throw new EOFException();
}
return b;
}
@@ -173,13 +179,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public short readShort() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte1 = file.read();
int byte2 = file.read();
// only need to test last byte read
// if byte1 is -1 so is byte2
if (byte2 < 0) {
throw new EOFException();
}
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
}
@@ -193,11 +201,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public int readUnsignedShort() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte1 = file.read();
int byte2 = file.read();
if (byte2 < 0) {
throw new EOFException();
}
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
return (byte2 << 8) + byte1;
}
@@ -212,11 +222,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public char readChar() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte1 = file.read();
int byte2 = file.read();
if (byte2 < 0) {
throw new EOFException();
}
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
}
@@ -231,16 +243,16 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public int readInt() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte3 = mFile.read();
int byte4 = mFile.read();
int byte1 = file.read();
int byte2 = file.read();
int byte3 = file.read();
int byte4 = file.read();
if (byte4 < 0) {
throw new EOFException();
}
return (byte4 << 24) + ((byte3 << 24) >>> 8)
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
return (byte4 << 24) + ((byte3 << 24) >>> 8) + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
}
/**
@@ -253,18 +265,19 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public long readLong() throws IOException {
long byte1 = mFile.read();
long byte2 = mFile.read();
long byte3 = mFile.read();
long byte4 = mFile.read();
long byte5 = mFile.read();
long byte6 = mFile.read();
long byte7 = mFile.read();
long byte8 = mFile.read();
long byte1 = file.read();
long byte2 = file.read();
long byte3 = file.read();
long byte4 = file.read();
long byte5 = file.read();
long byte6 = file.read();
long byte7 = file.read();
long byte8 = file.read();
if (byte8 < 0) {
throw new EOFException();
}
return (byte8 << 56) + ((byte7 << 56) >>> 8)
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
@@ -287,11 +300,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public String readUTF() throws IOException {
int byte1 = mFile.read();
int byte2 = mFile.read();
int byte1 = file.read();
int byte2 = file.read();
if (byte2 < 0) {
throw new EOFException();
}
int numbytes = (byte1 << 8) + byte2;
char result[] = new char[numbytes];
int numread = 0;
@@ -310,27 +325,34 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
}
else if (test == 12 || test == 13) { // two bytes
numread += 2;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
if ((c2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
}
else if (test == 14) { // three bytes
numread += 3;
if (numread > numbytes) {
throw new UTFDataFormatException();
}
c2 = readUnsignedByte();
c3 = readUnsignedByte();
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException();
}
result[numchars++] = (char)
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
}
else { // malformed
throw new UTFDataFormatException();
@@ -378,27 +400,27 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* {@code 0} or if an I/O error occurs.
*/
public void seek(final long pos) throws IOException {
mFile.seek(pos);
file.seek(pos);
}
public void setLength(final long newLength) throws IOException {
mFile.setLength(newLength);
file.setLength(newLength);
}
public int skipBytes(final int n) throws IOException {
return mFile.skipBytes(n);
return file.skipBytes(n);
}
public void write(final byte[] b) throws IOException {
mFile.write(b);
file.write(b);
}
public void write(final byte[] b, final int off, final int len) throws IOException {
mFile.write(b, off, len);
file.write(b, off, len);
}
public void write(final int b) throws IOException {
mFile.write(b);
file.write(b);
}
/**
@@ -425,7 +447,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeByte(int pByte) throws IOException {
mFile.write(pByte);
file.write(pByte);
}
/**
@@ -436,8 +458,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeShort(int pShort) throws IOException {
mFile.write(pShort & 0xFF);
mFile.write((pShort >>> 8) & 0xFF);
file.write(pShort & 0xFF);
file.write((pShort >>> 8) & 0xFF);
}
/**
@@ -448,8 +470,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeChar(int pChar) throws IOException {
mFile.write(pChar & 0xFF);
mFile.write((pChar >>> 8) & 0xFF);
file.write(pChar & 0xFF);
file.write((pChar >>> 8) & 0xFF);
}
/**
@@ -460,11 +482,10 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeInt(int pInt) throws IOException {
mFile.write(pInt & 0xFF);
mFile.write((pInt >>> 8) & 0xFF);
mFile.write((pInt >>> 16) & 0xFF);
mFile.write((pInt >>> 24) & 0xFF);
file.write(pInt & 0xFF);
file.write((pInt >>> 8) & 0xFF);
file.write((pInt >>> 16) & 0xFF);
file.write((pInt >>> 24) & 0xFF);
}
/**
@@ -475,14 +496,14 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @throws IOException if the underlying stream throws an IOException.
*/
public void writeLong(long pLong) throws IOException {
mFile.write((int) pLong & 0xFF);
mFile.write((int) (pLong >>> 8) & 0xFF);
mFile.write((int) (pLong >>> 16) & 0xFF);
mFile.write((int) (pLong >>> 24) & 0xFF);
mFile.write((int) (pLong >>> 32) & 0xFF);
mFile.write((int) (pLong >>> 40) & 0xFF);
mFile.write((int) (pLong >>> 48) & 0xFF);
mFile.write((int) (pLong >>> 56) & 0xFF);
file.write((int) pLong & 0xFF);
file.write((int) (pLong >>> 8) & 0xFF);
file.write((int) (pLong >>> 16) & 0xFF);
file.write((int) (pLong >>> 24) & 0xFF);
file.write((int) (pLong >>> 32) & 0xFF);
file.write((int) (pLong >>> 40) & 0xFF);
file.write((int) (pLong >>> 48) & 0xFF);
file.write((int) (pLong >>> 56) & 0xFF);
}
/**
@@ -515,12 +536,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @param pString the {@code String} value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeByte(int)
* @see #mFile
* @see #file
*/
public void writeBytes(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
mFile.write((byte) pString.charAt(i));
file.write((byte) pString.charAt(i));
}
}
@@ -532,14 +554,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
* @param pString a {@code String} value to be written.
* @throws IOException if the underlying stream throws an IOException.
* @see #writeChar(int)
* @see #mFile
* @see #file
*/
public void writeChars(String pString) throws IOException {
int length = pString.length();
for (int i = 0; i < length; i++) {
int c = pString.charAt(i);
mFile.write(c & 0xFF);
mFile.write((c >>> 8) & 0xFF);
file.write(c & 0xFF);
file.write((c >>> 8) & 0xFF);
}
}
@@ -564,6 +587,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
numbytes++;
}
@@ -579,21 +603,23 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
throw new UTFDataFormatException();
}
mFile.write((numbytes >>> 8) & 0xFF);
mFile.write(numbytes & 0xFF);
file.write((numbytes >>> 8) & 0xFF);
file.write(numbytes & 0xFF);
for (int i = 0; i < numchars; i++) {
int c = pString.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
mFile.write(c);
file.write(c);
}
else if (c > 0x07FF) {
mFile.write(0xE0 | ((c >> 12) & 0x0F));
mFile.write(0x80 | ((c >> 6) & 0x3F));
mFile.write(0x80 | (c & 0x3F));
file.write(0xE0 | ((c >> 12) & 0x0F));
file.write(0x80 | ((c >> 6) & 0x3F));
file.write(0x80 | (c & 0x3F));
}
else {
mFile.write(0xC0 | ((c >> 6) & 0x1F));
mFile.write(0x80 | (c & 0x3F));
file.write(0xC0 | ((c >> 6) & 0x1F));
file.write(0x80 | (c & 0x3F));
}
}
}
@@ -65,13 +65,13 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
final static class MemoryCache extends StreamCache {
final static int BLOCK_SIZE = 1 << 13;
private final List<byte[]> mCache = new ArrayList<byte[]>();
private long mLength;
private long mPosition;
private long mStart;
private final List<byte[]> cache = new ArrayList<byte[]>();
private long length;
private long position;
private long start;
private byte[] getBlock() throws IOException {
final long currPos = mPosition - mStart;
final long currPos = position - start;
if (currPos < 0) {
throw new IOException("StreamCache flushed before read position");
}
@@ -82,31 +82,31 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
throw new IOException("Memory cache max size exceeded");
}
if (index >= mCache.size()) {
if (index >= cache.size()) {
try {
mCache.add(new byte[BLOCK_SIZE]);
cache.add(new byte[BLOCK_SIZE]);
// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
// System.out.println("New total size: " + mCache.size() * BLOCK_SIZE + " (" + mCache.size() + " blocks)");
// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)");
}
catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + mCache.size() * BLOCK_SIZE);
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
}
}
//System.out.println("index: " + index);
return mCache.get((int) index);
return cache.get((int) index);
}
public void write(final int pByte) throws IOException {
byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE);
int idx = (int) (position % BLOCK_SIZE);
buffer[idx] = (byte) pByte;
mPosition++;
position++;
if (mPosition > mLength) {
mLength = mPosition;
if (position > length) {
length = position;
}
}
@@ -115,28 +115,28 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
byte[] buffer = getBlock();
for (int i = 0; i < pLength; i++) {
int index = (int) mPosition % BLOCK_SIZE;
int index = (int) position % BLOCK_SIZE;
if (index == 0) {
buffer = getBlock();
}
buffer[index] = pBuffer[pOffset + i];
mPosition++;
position++;
}
if (mPosition > mLength) {
mLength = mPosition;
if (position > length) {
length = position;
}
}
public int read() throws IOException {
if (mPosition >= mLength) {
if (position >= length) {
return -1;
}
byte[] buffer = getBlock();
int idx = (int) (mPosition % BLOCK_SIZE);
mPosition++;
int idx = (int) (position % BLOCK_SIZE);
position++;
return buffer[idx] & 0xff;
}
@@ -144,33 +144,33 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
// TODO: OptimizeMe!!!
@Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mPosition >= mLength) {
if (position >= length) {
return -1;
}
byte[] buffer = getBlock();
int bufferPos = (int) (mPosition % BLOCK_SIZE);
int bufferPos = (int) (position % BLOCK_SIZE);
// Find maxIdx and simplify test in for-loop
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), mLength - mPosition);
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position);
int i;
//for (i = 0; i < pLength && i < buffer.length - idx && i < mLength - mPosition; i++) {
//for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) {
for (i = 0; i < maxLen; i++) {
pBytes[pOffset + i] = buffer[bufferPos + i];
}
mPosition += i;
position += i;
return i;
}
public void seek(final long pPosition) throws IOException {
if (pPosition < mStart) {
if (pPosition < start) {
throw new IOException("Seek before flush position");
}
mPosition = pPosition;
position = pPosition;
}
@Override
@@ -178,14 +178,14 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
for (int i = 0; i < firstPos; i++) {
mCache.remove(0);
cache.remove(0);
}
mStart = pPosition;
start = pPosition;
}
public long getPosition() {
return mPosition;
return position;
}
}
}
@@ -50,13 +50,12 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
// TODO: Package private SeekableDelegate?
// TODO: Both read and write must update stream position
//private int mPosition = -1;
//private int position = -1;
/** This random access stream, wrapped in an {@code InputStream} */
SeekableInputStream mInputView = null;
SeekableInputStream inputView = null;
/** This random access stream, wrapped in an {@code OutputStream} */
SeekableOutputStream mOutputView = null;
SeekableOutputStream outputView = null;
// TODO: Create an Input and an Output interface matching InputStream and OutputStream?
public int read() throws IOException {
@@ -119,10 +118,10 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
* @return a {@code SeekableInputStream} reading from this stream
*/
public final SeekableInputStream asInputStream() {
if (mInputView == null) {
mInputView = new InputStreamView(this);
if (inputView == null) {
inputView = new InputStreamView(this);
}
return mInputView;
return inputView;
}
/**
@@ -134,15 +133,15 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
* @return a {@code SeekableOutputStream} writing to this stream
*/
public final SeekableOutputStream asOutputStream() {
if (mOutputView == null) {
mOutputView = new OutputStreamView(this);
if (outputView == null) {
outputView = new OutputStreamView(this);
}
return mOutputView;
return outputView;
}
static final class InputStreamView extends SeekableInputStream {
// TODO: Consider adding synchonization (on mStream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream?
// TODO: Consider adding synchonization (on stream) for all operations
// TODO: Is is a good thing that close/flush etc works on stream?
// - Or should it rather just work on the views?
// - Allow multiple views?
@@ -190,8 +189,8 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
}
static final class OutputStreamView extends SeekableOutputStream {
// TODO: Consider adding synchonization (on mStream) for all operations
// TODO: Is is a good thing that close/flush etc works on mStream?
// TODO: Consider adding synchonization (on stream) for all operations
// TODO: Is is a good thing that close/flush etc works on stream?
// - Or should it rather just work on the views?
// - Allow multiple views?
@@ -43,15 +43,15 @@ import java.util.Stack;
public abstract class SeekableInputStream extends InputStream implements Seekable {
// TODO: It's at the moment not possible to create subclasses outside this
// package, as there's no access to mPosition. mPosition needs to be
// package, as there's no access to position. position needs to be
// updated from the read/read/read methods...
/** The stream position in this stream */
long mPosition;
long mFlushedPosition;
boolean mClosed;
long position;
long flushedPosition;
boolean closed;
protected Stack<Long> mMarkedPositions = new Stack<Long>();
protected Stack<Long> markedPositions = new Stack<Long>();
/// InputStream overrides
@Override
@@ -69,17 +69,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
* @throws IOException if an I/O exception occurs during skip
*/
@Override
public final long skip(long pLength) throws IOException {
long pos = mPosition;
if (pos + pLength < mFlushedPosition) {
public final long skip(final long pLength) throws IOException {
long pos = position;
long wantedPosition = pos + pLength;
if (wantedPosition < flushedPosition) {
throw new IOException("position < flushedPosition");
}
// Stop at stream length for compatibility, even though it's allowed
// Stop at stream length for compatibility, even though it might be allowed
// to seek past end of stream
seek(Math.min(pos + pLength, pos + available()));
int available = available();
if (available > 0) {
seek(Math.min(wantedPosition, pos + available));
}
// TODO: Add optimization for streams with known length!
else {
// Slow mode...
int toSkip = (int) Math.max(Math.min(pLength, 512), -512);
while (toSkip > 0 && read() >= 0) {
toSkip--;
}
}
return mPosition - pos;
return position - pos;
}
@Override
@@ -88,7 +100,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
// TODO: We don't really need to do this.. Is it a good idea?
try {
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition));
flushBefore(Math.max(position - pLimit, flushedPosition));
}
catch (IOException ignore) {
// Ignore, as it's not really critical
@@ -111,29 +123,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's kind of inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) {
if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition");
}
seekImpl(pPosition);
mPosition = pPosition;
position = pPosition;
}
protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() {
mMarkedPositions.push(mPosition);
markedPositions.push(position);
}
@Override
public final void reset() throws IOException {
checkOpen();
if (!mMarkedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop();
if (!markedPositions.isEmpty()) {
long newPos = markedPositions.pop();
// NOTE: This is correct according to javax.imageio (IOException),
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) {
if (newPos < flushedPosition) {
throw new IOException("Previous marked position has been discarded");
}
@@ -150,7 +162,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
}
public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) {
if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition");
}
if (pPosition > getStreamPosition()) {
@@ -158,7 +170,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
}
checkOpen();
flushBeforeImpl(pPosition);
mFlushedPosition = pPosition;
flushedPosition = pPosition;
}
/**
@@ -172,21 +184,21 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
public final void flush() throws IOException {
flushBefore(mFlushedPosition);
flushBefore(flushedPosition);
}
public final long getFlushedPosition() throws IOException {
checkOpen();
return mFlushedPosition;
return flushedPosition;
}
public final long getStreamPosition() throws IOException {
checkOpen();
return mPosition;
return position;
}
protected final void checkOpen() throws IOException {
if (mClosed) {
if (closed) {
throw new IOException("closed");
}
}
@@ -194,7 +206,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
@Override
public final void close() throws IOException {
checkOpen();
mClosed = true;
closed = true;
closeImpl();
}
@@ -211,7 +223,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
*/
@Override
protected void finalize() throws Throwable {
if (!mClosed) {
if (!closed) {
try {
close();
}
@@ -43,11 +43,11 @@ import java.util.Stack;
*/
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
// TODO: Implement
long mPosition;
long mFlushedPosition;
boolean mClosed;
long position;
long flushedPosition;
boolean closed;
protected Stack<Long> mMarkedPositions = new Stack<Long>();
protected Stack<Long> markedPositions = new Stack<Long>();
/// Outputstream overrides
@Override
@@ -63,28 +63,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
// but it's inconsistent with reset that throws IOException...
if (pPosition < mFlushedPosition) {
if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!");
}
seekImpl(pPosition);
mPosition = pPosition;
position = pPosition;
}
protected abstract void seekImpl(long pPosition) throws IOException;
public final void mark() {
mMarkedPositions.push(mPosition);
markedPositions.push(position);
}
public final void reset() throws IOException {
checkOpen();
if (!mMarkedPositions.isEmpty()) {
long newPos = mMarkedPositions.pop();
if (!markedPositions.isEmpty()) {
long newPos = markedPositions.pop();
// TODO: This is correct according to javax.imageio (IOException),
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
if (newPos < mFlushedPosition) {
if (newPos < flushedPosition) {
throw new IOException("Previous marked position has been discarded!");
}
@@ -93,7 +93,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
}
public final void flushBefore(long pPosition) throws IOException {
if (pPosition < mFlushedPosition) {
if (pPosition < flushedPosition) {
throw new IndexOutOfBoundsException("position < flushedPosition!");
}
if (pPosition > getStreamPosition()) {
@@ -101,28 +101,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
}
checkOpen();
flushBeforeImpl(pPosition);
mFlushedPosition = pPosition;
flushedPosition = pPosition;
}
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
@Override
public final void flush() throws IOException {
flushBefore(mFlushedPosition);
flushBefore(flushedPosition);
}
public final long getFlushedPosition() throws IOException {
checkOpen();
return mFlushedPosition;
return flushedPosition;
}
public final long getStreamPosition() throws IOException {
checkOpen();
return mPosition;
return position;
}
protected final void checkOpen() throws IOException {
if (mClosed) {
if (closed) {
throw new IOException("closed");
}
}
@@ -130,7 +130,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
@Override
public final void close() throws IOException {
checkOpen();
mClosed = true;
closed = true;
closeImpl();
}
@@ -28,6 +28,8 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.Validate;
import java.io.StringReader;
import java.io.IOException;
import java.io.Reader;
@@ -42,13 +44,13 @@ import java.io.Reader;
*/
public class StringArrayReader extends StringReader {
private StringReader mCurrent;
private String[] mStrings;
protected final Object mLock;
private int mCurrentSting;
private int mMarkedString;
private int mMark;
private int mNext;
private StringReader current;
private String[] strings;
protected final Object finalLock;
private int currentSting;
private int markedString;
private int mark;
private int next;
/**
* Create a new string array reader.
@@ -57,28 +59,28 @@ public class StringArrayReader extends StringReader {
*/
public StringArrayReader(final String[] pStrings) {
super("");
if (pStrings == null) {
throw new NullPointerException("strings == null");
}
mLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
Validate.notNull(pStrings, "strings");
finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
// reference can't change, only it's elements
mStrings = pStrings.clone(); // Defensive copy for content
strings = pStrings.clone(); // Defensive copy for content
nextReader();
}
protected final Reader nextReader() {
if (mCurrentSting >= mStrings.length) {
mCurrent = new EmptyReader();
if (currentSting >= strings.length) {
current = new EmptyReader();
}
else {
mCurrent = new StringReader(mStrings[mCurrentSting++]);
current = new StringReader(strings[currentSting++]);
}
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
mNext = 0;
// NOTE: Reset next for every reader, and record marked reader in mark/reset methods!
next = 0;
return mCurrent;
return current;
}
/**
@@ -87,15 +89,15 @@ public class StringArrayReader extends StringReader {
* @throws IOException if the stream is closed
*/
protected final void ensureOpen() throws IOException {
if (mStrings == null) {
if (strings == null) {
throw new IOException("Stream closed");
}
}
public void close() {
super.close();
mStrings = null;
mCurrent.close();
strings = null;
current.close();
}
public void mark(int pReadLimit) throws IOException {
@@ -103,29 +105,29 @@ public class StringArrayReader extends StringReader {
throw new IllegalArgumentException("Read limit < 0");
}
synchronized (mLock) {
synchronized (finalLock) {
ensureOpen();
mMark = mNext;
mMarkedString = mCurrentSting;
mark = next;
markedString = currentSting;
mCurrent.mark(pReadLimit);
current.mark(pReadLimit);
}
}
public void reset() throws IOException {
synchronized (mLock) {
synchronized (finalLock) {
ensureOpen();
if (mCurrentSting != mMarkedString) {
mCurrentSting = mMarkedString - 1;
if (currentSting != markedString) {
currentSting = markedString - 1;
nextReader();
mCurrent.skip(mMark);
current.skip(mark);
}
else {
mCurrent.reset();
current.reset();
}
mNext = mMark;
next = mark;
}
}
@@ -134,49 +136,49 @@ public class StringArrayReader extends StringReader {
}
public int read() throws IOException {
synchronized (mLock) {
int read = mCurrent.read();
synchronized (finalLock) {
int read = current.read();
if (read < 0 && mCurrentSting < mStrings.length) {
if (read < 0 && currentSting < strings.length) {
nextReader();
return read(); // In case of empty strings
}
mNext++;
next++;
return read;
}
}
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (mLock) {
int read = mCurrent.read(pBuffer, pOffset, pLength);
synchronized (finalLock) {
int read = current.read(pBuffer, pOffset, pLength);
if (read < 0 && mCurrentSting < mStrings.length) {
if (read < 0 && currentSting < strings.length) {
nextReader();
return read(pBuffer, pOffset, pLength); // In case of empty strings
}
mNext += read;
next += read;
return read;
}
}
public boolean ready() throws IOException {
return mCurrent.ready();
return current.ready();
}
public long skip(long pChars) throws IOException {
synchronized (mLock) {
long skipped = mCurrent.skip(pChars);
synchronized (finalLock) {
long skipped = current.skip(pChars);
if (skipped == 0 && mCurrentSting < mStrings.length) {
if (skipped == 0 && currentSting < strings.length) {
nextReader();
return skip(pChars);
}
mNext += skipped;
next += skipped;
return skipped;
}
@@ -43,8 +43,8 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
*/
public final class SubStream extends FilterInputStream {
private long mLeft;
private int mMarkLimit;
private long bytesLeft;
private int markLimit;
/**
* Creates a {@code SubStream} of the given {@code pStream}.
@@ -54,7 +54,7 @@ public final class SubStream extends FilterInputStream {
*/
public SubStream(final InputStream pStream, final long pLength) {
super(Validate.notNull(pStream, "stream"));
mLeft = pLength;
bytesLeft = pLength;
}
/**
@@ -64,32 +64,32 @@ public final class SubStream extends FilterInputStream {
@Override
public void close() throws IOException {
// NOTE: Do not close the underlying stream
while (mLeft > 0) {
while (bytesLeft > 0) {
//noinspection ResultOfMethodCallIgnored
skip(mLeft);
skip(bytesLeft);
}
}
@Override
public int available() throws IOException {
return (int) Math.min(super.available(), mLeft);
return (int) Math.min(super.available(), bytesLeft);
}
@Override
public void mark(int pReadLimit) {
super.mark(pReadLimit);// This either succeeds or does nothing...
mMarkLimit = pReadLimit;
markLimit = pReadLimit;
}
@Override
public void reset() throws IOException {
super.reset();// This either succeeds or throws IOException
mLeft += mMarkLimit;
bytesLeft += markLimit;
}
@Override
public int read() throws IOException {
if (mLeft-- <= 0) {
if (bytesLeft-- <= 0) {
return -1;
}
return super.read();
@@ -102,12 +102,12 @@ public final class SubStream extends FilterInputStream {
@Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (mLeft <= 0) {
if (bytesLeft <= 0) {
return -1;
}
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
mLeft = read < 0 ? 0 : mLeft - read;
bytesLeft = read < 0 ? 0 : bytesLeft - read;
return read;
}
@@ -118,8 +118,8 @@ public final class SubStream extends FilterInputStream {
* @return the maximum number of bytes to read
*/
private long findMaxLen(long pLength) {
if (mLeft < pLength) {
return (int) Math.max(mLeft, 0);
if (bytesLeft < pLength) {
return (int) Math.max(bytesLeft, 0);
}
else {
return pLength;
@@ -129,7 +129,7 @@ public final class SubStream extends FilterInputStream {
@Override
public long skip(long pLength) throws IOException {
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
mLeft -= skipped;
bytesLeft -= skipped;
return skipped;
}
}
@@ -50,7 +50,8 @@ final class Win32Lnk extends File {
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
};
private final File mTarget;
private final File target;
private static final int FLAG_ITEM_ID_LIST = 0x01;
private static final int FLAG_FILE_LOC_INFO = 0x02;
private static final int FLAG_DESC_STRING = 0x04;
@@ -65,10 +66,10 @@ final class Win32Lnk extends File {
File target = parse(this);
if (target == this) {
// NOTE: This is a workaround
// mTarget = this causes infinite loops in some methods
// target = this causes infinite loops in some methods
target = new File(pPath);
}
mTarget = target;
this.target = target;
}
Win32Lnk(final File pPath) throws IOException {
@@ -336,24 +337,24 @@ final class Win32Lnk extends File {
*/
public File getTarget() {
return mTarget;
return target;
}
// java.io.File overrides below
@Override
public boolean isDirectory() {
return mTarget.isDirectory();
return target.isDirectory();
}
@Override
public boolean canRead() {
return mTarget.canRead();
return target.canRead();
}
@Override
public boolean canWrite() {
return mTarget.canWrite();
return target.canWrite();
}
// NOTE: equals is implemented using compareto == 0
@@ -362,7 +363,7 @@ final class Win32Lnk extends File {
// TODO: Verify this
// Probably not a good idea, as it IS NOT THE SAME file
// It's probably better to not override
return mTarget.compareTo(pathname);
return target.compareTo(pathname);
}
*/
@@ -375,7 +376,7 @@ final class Win32Lnk extends File {
@Override
public boolean exists() {
return mTarget.exists();
return target.exists();
}
// A .lnk may be absolute
@@ -385,12 +386,12 @@ final class Win32Lnk extends File {
// Theses should be resolved according to the API (for Unix).
@Override
public File getCanonicalFile() throws IOException {
return mTarget.getCanonicalFile();
return target.getCanonicalFile();
}
@Override
public String getCanonicalPath() throws IOException {
return mTarget.getCanonicalPath();
return target.getCanonicalPath();
}
//public String getName() {
@@ -402,47 +403,47 @@ final class Win32Lnk extends File {
// public boolean isAbsolute() {
@Override
public boolean isFile() {
return mTarget.isFile();
return target.isFile();
}
@Override
public boolean isHidden() {
return mTarget.isHidden();
return target.isHidden();
}
@Override
public long lastModified() {
return mTarget.lastModified();
return target.lastModified();
}
@Override
public long length() {
return mTarget.length();
return target.length();
}
@Override
public String[] list() {
return mTarget.list();
return target.list();
}
@Override
public String[] list(final FilenameFilter filter) {
return mTarget.list(filter);
return target.list(filter);
}
@Override
public File[] listFiles() {
return Win32File.wrap(mTarget.listFiles());
return Win32File.wrap(target.listFiles());
}
@Override
public File[] listFiles(final FileFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter));
return Win32File.wrap(target.listFiles(filter));
}
@Override
public File[] listFiles(final FilenameFilter filter) {
return Win32File.wrap(mTarget.listFiles(filter));
return Win32File.wrap(target.listFiles(filter));
}
// Makes no sense, does it?
@@ -454,19 +455,19 @@ final class Win32Lnk extends File {
@Override
public boolean setLastModified(long time) {
return mTarget.setLastModified(time);
return target.setLastModified(time);
}
@Override
public boolean setReadOnly() {
return mTarget.setReadOnly();
return target.setReadOnly();
}
@Override
public String toString() {
if (mTarget.equals(this)) {
if (target.equals(this)) {
return super.toString();
}
return super.toString() + " -> " + mTarget.toString();
return super.toString() + " -> " + target.toString();
}
}
@@ -51,10 +51,11 @@ import java.nio.CharBuffer;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $
*/
public class WriterOutputStream extends OutputStream {
protected Writer mWriter;
final protected Decoder mDecoder;
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024);
private volatile boolean mIsFlushing = false; // Ugly but critical...
protected Writer writer;
final protected Decoder decoder;
final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
private volatile boolean isFlushing = false; // Ugly but critical...
private static final boolean NIO_AVAILABLE = isNIOAvailable();
@@ -71,8 +72,8 @@ public class WriterOutputStream extends OutputStream {
}
public WriterOutputStream(final Writer pWriter, final String pCharset) {
mWriter = pWriter;
mDecoder = getDecoder(pCharset);
writer = pWriter;
decoder = getDecoder(pCharset);
}
public WriterOutputStream(final Writer pWriter) {
@@ -94,14 +95,14 @@ public class WriterOutputStream extends OutputStream {
@Override
public void close() throws IOException {
flush();
mWriter.close();
mWriter = null;
writer.close();
writer = null;
}
@Override
public void flush() throws IOException {
flushBuffer();
mWriter.flush();
writer.flush();
}
@Override
@@ -115,22 +116,22 @@ public class WriterOutputStream extends OutputStream {
@Override
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
flushBuffer();
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength);
decoder.decodeTo(writer, pBytes, pOffset, pLength);
}
@Override
public final void write(int pByte) {
// TODO: Is it possible to know if this is a good place in the stream to
// flush? It might be in the middle of a multi-byte encoded character..
mBufferStream.write(pByte);
bufferStream.write(pByte);
}
private void flushBuffer() throws IOException {
if (!mIsFlushing && mBufferStream.size() > 0) {
mIsFlushing = true;
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
mBufferStream.reset();
mIsFlushing = false;
if (!isFlushing && bufferStream.size() > 0) {
isFlushing = true;
bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
bufferStream.reset();
isFlushing = false;
}
}
@@ -138,7 +139,7 @@ public class WriterOutputStream extends OutputStream {
public static void main(String[] pArgs) throws IOException {
int iterations = 1000000;
byte[] bytes = "åøæÅØÆ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
byte[] bytes = " klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
Decoder d;
long start;
@@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
@@ -41,86 +42,85 @@ import java.io.InputStream;
*/
// TODO: Move to other package or make public
abstract class AbstractRLEDecoder implements Decoder {
protected final byte[] mRow;
protected final int mWidth;
protected int mSrcX;
protected int mSrcY;
protected int mDstX;
protected int mDstY;
protected final byte[] row;
protected final int width;
protected int srcX;
protected int srcY;
protected int dstX;
protected int dstY;
/**
* Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas,
* Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas,
* etc, we need to know height and width of the image.
*
* @param pWidth width of the image
* @param pHeight heigth of the image
* @param pHeight height of the image
*/
AbstractRLEDecoder(int pWidth, int pHeight) {
mWidth = pWidth;
int bytesPerRow = mWidth;
AbstractRLEDecoder(final int pWidth, final int pHeight) {
width = pWidth;
int bytesPerRow = width;
int mod = bytesPerRow % 4;
if (mod != 0) {
bytesPerRow += 4 - mod;
}
mRow = new byte[bytesPerRow];
row = new byte[bytesPerRow];
mSrcX = 0;
mSrcY = pHeight - 1;
srcX = 0;
srcY = pHeight - 1;
mDstX = mSrcX;
mDstY = mSrcY;
dstX = srcX;
dstY = srcY;
}
/**
* Decodes one full row of image data.
*
* @param pStream the input stream containint RLE data
* @param pStream the input stream containing RLE data
*
* @throws IOException if an I/O related exception ocurs while reading
* @throws IOException if an I/O related exception occurs while reading
*/
protected abstract void decodeRow(InputStream pStream) throws IOException;
protected abstract void decodeRow(final InputStream pStream) throws IOException;
/**
* Decodes as much data as possible, from the stream into the buffer.
*
* @param pStream the input stream containing RLE data
* @param pBuffer tge buffer to decode the data to
* @param stream the input stream containing RLE data
* @param buffer the buffer to decode the data to
*
* @return the number of bytes decoded from the stream, to the buffer
*
* @throws IOException if an I/O related exception ocurs while reading
*/
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
int decoded = 0;
while (decoded < pBuffer.length && mDstY >= 0) {
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
while (buffer.hasRemaining() && dstY >= 0) {
// NOTE: Decode only full rows, don't decode if y delta
if (mDstX == 0 && mSrcY == mDstY) {
decodeRow(pStream);
if (dstX == 0 && srcY == dstY) {
decodeRow(stream);
}
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded);
System.arraycopy(mRow, mDstX, pBuffer, decoded, length);
mDstX += length;
decoded += length;
int length = Math.min(row.length - dstX, buffer.remaining());
// System.arraycopy(row, dstX, buffer, decoded, length);
buffer.put(row, 0, length);
dstX += length;
// decoded += length;
if (mDstX == mRow.length) {
mDstX = 0;
mDstY--;
if (dstX == row.length) {
dstX = 0;
dstY--;
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
// gap with zero-bytes
if (mDstY > mSrcY) {
for (int i = 0; i < mRow.length; i++) {
mRow[i] = 0x00;
if (dstY > srcY) {
for (int i = 0; i < row.length; i++) {
row[i] = 0x00;
}
}
}
}
return decoded;
return buffer.position();
}
/**
@@ -131,7 +131,7 @@ abstract class AbstractRLEDecoder implements Decoder {
*
* @throws EOFException if {@code pByte} is negative
*/
protected static int checkEOF(int pByte) throws EOFException {
protected static int checkEOF(final int pByte) throws EOFException {
if (pByte < 0) {
throw new EOFException("Premature end of file");
}
@@ -28,9 +28,9 @@
package com.twelvemonkeys.io.enc;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* {@code Decoder} implementation for standard base64 encoding.
@@ -47,7 +47,7 @@ public final class Base64Decoder implements Decoder {
/**
* This array maps the characters to their 6 bit values
*/
final static char[] PEM_ARRAY = {
final static byte[] PEM_ARRAY = {
//0 1 2 3 4 5 6 7
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
@@ -61,9 +61,7 @@ public final class Base64Decoder implements Decoder {
final static byte[] PEM_CONVERT_ARRAY;
private byte[] mDecodeBuffer = new byte[4];
private ByteArrayOutputStream mWrapped;
private Object mWrappedObject;
private byte[] decodeBuffer = new byte[4];
static {
PEM_CONVERT_ARRAY = new byte[256];
@@ -93,7 +91,7 @@ public final class Base64Decoder implements Decoder {
return pLength;
}
protected boolean decodeAtom(final InputStream pInput, final OutputStream pOutput, final int pLength)
protected boolean decodeAtom(final InputStream pInput, final ByteBuffer pOutput, final int pLength)
throws IOException {
byte byte0 = -1;
@@ -116,8 +114,8 @@ public final class Base64Decoder implements Decoder {
}
} while (read == 10 || read == 13);
mDecodeBuffer[0] = (byte) read;
read = readFully(pInput, mDecodeBuffer, 1, pLength - 1);
decodeBuffer[0] = (byte) read;
read = readFully(pInput, decodeBuffer, 1, pLength - 1);
if (read == -1) {
return false;
@@ -125,38 +123,38 @@ public final class Base64Decoder implements Decoder {
int length = pLength;
if (length > 3 && mDecodeBuffer[3] == 61) {
if (length > 3 && decodeBuffer[3] == 61) {
length = 3;
}
if (length > 2 && mDecodeBuffer[2] == 61) {
if (length > 2 && decodeBuffer[2] == 61) {
length = 2;
}
switch (length) {
case 4:
byte3 = PEM_CONVERT_ARRAY[mDecodeBuffer[3] & 255];
byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
// fall through
case 3:
byte2 = PEM_CONVERT_ARRAY[mDecodeBuffer[2] & 255];
byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
// fall through
case 2:
byte1 = PEM_CONVERT_ARRAY[mDecodeBuffer[1] & 255];
byte0 = PEM_CONVERT_ARRAY[mDecodeBuffer[0] & 255];
byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
// fall through
default:
switch (length) {
case 2:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
break;
case 3:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
break;
case 4:
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63));
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63));
break;
}
@@ -166,34 +164,23 @@ public final class Base64Decoder implements Decoder {
return true;
}
void decodeBuffer(final InputStream pInput, final ByteArrayOutputStream pOutput, final int pLength) throws IOException {
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
do {
int k = 72;
int i;
for (i = 0; i + 4 < k; i += 4) {
if(!decodeAtom(pInput, pOutput, 4)) {
if(!decodeAtom(stream, buffer, 4)) {
break;
}
}
if (!decodeAtom(pInput, pOutput, k - i)) {
if (!decodeAtom(stream, buffer, k - i)) {
break;
}
}
while (pOutput.size() + 54 < pLength); // 72 char lines should produce no more than 54 bytes
}
while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mWrappedObject != pBuffer) {
// NOTE: Array not cloned in FastByteArrayOutputStream
mWrapped = new FastByteArrayOutputStream(pBuffer);
mWrappedObject = pBuffer;
}
mWrapped.reset(); // NOTE: This only resets count to 0
decodeBuffer(pStream, mWrapped, pBuffer.length);
return mWrapped.size();
return buffer.position();
}
}
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
import java.io.OutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* {@code Encoder} implementation for standard base64 encoding.
@@ -44,15 +45,9 @@ import java.io.IOException;
*/
public class Base64Encoder implements Encoder {
public void encode(final OutputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength)
public void encode(final OutputStream stream, final ByteBuffer buffer)
throws IOException
{
if (pOffset < 0 || pOffset > pLength || pOffset > pBuffer.length) {
throw new IndexOutOfBoundsException("offset outside [0...length]");
}
else if (pLength > pBuffer.length) {
throw new IndexOutOfBoundsException("length > buffer length");
}
// TODO: Implement
// NOTE: This is impossible, given the current spec, as we need to either:
@@ -61,48 +56,47 @@ public class Base64Encoder implements Encoder {
// to ensure proper end of stream handling
int length;
int offset = pOffset;
// TODO: Temp impl, will only work for single writes
while ((pBuffer.length - offset) > 0) {
while (buffer.hasRemaining()) {
byte a, b, c;
if ((pBuffer.length - offset) > 2) {
length = 3;
}
else {
length = pBuffer.length - offset;
}
// if ((buffer.remaining()) > 2) {
// length = 3;
// }
// else {
// length = buffer.remaining();
// }
length = Math.min(3, buffer.remaining());
switch (length) {
case 1:
a = pBuffer[offset];
a = buffer.get();
b = 0;
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write('=');
pStream.write('=');
offset++;
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
stream.write('=');
stream.write('=');
break;
case 2:
a = pBuffer[offset];
b = pBuffer[offset + 1];
a = buffer.get();
b = buffer.get();
c = 0;
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
pStream.write('=');
offset += offset + 2; // ???
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
stream.write('=');
break;
default:
a = pBuffer[offset];
b = pBuffer[offset + 1];
c = pBuffer[offset + 2];
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
pStream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
offset = offset + 3;
a = buffer.get();
b = buffer.get();
c = buffer.get();
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
break;
}
}
@@ -31,7 +31,7 @@ package com.twelvemonkeys.io.enc;
import java.io.IOException;
/**
* Thrown by {@code Decoder}s when encoded data can not be decocded.
* Thrown by {@code Decoder}s when encoded data can not be decoded.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@@ -28,8 +28,9 @@
package com.twelvemonkeys.io.enc;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Interface for decoders.
@@ -47,18 +48,19 @@ import java.io.IOException;
public interface Decoder {
/**
* Decodes up to {@code pBuffer.length} bytes from the given inputstream,
* Decodes up to {@code buffer.length} bytes from the given input stream,
* into the given buffer.
*
* @param pStream the inputstream to decode data from
* @param pBuffer buffer to store the read data
* @param stream the input stream to decode data from
* @param buffer buffer to store the read data
*
* @return the total number of bytes read into the buffer, or {@code -1}
* @return the total number of bytes read into the buffer, or {@code 0}
* if there is no more data because the end of the stream has been reached.
*
* @throws DecodeException if encoded data is corrupt
* @throws IOException if an I/O error occurs
* @throws java.io.EOFException if a premature end-of-file is encountered
* @throws DecodeException if encoded data is corrupt.
* @throws IOException if an I/O error occurs.
* @throws java.io.EOFException if a premature end-of-file is encountered.
* @throws java.lang.NullPointerException if either argument is {@code null}.
*/
int decode(InputStream pStream, byte[] pBuffer) throws IOException;
int decode(InputStream stream, ByteBuffer buffer) throws IOException;
}
@@ -28,9 +28,10 @@
package com.twelvemonkeys.io.enc;
import java.io.InputStream;
import java.io.IOException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* An {@code InputStream} that provides on-the-fly decoding from an underlying
@@ -43,11 +44,8 @@ import java.io.FilterInputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
*/
public final class DecoderStream extends FilterInputStream {
protected int mBufferPos;
protected int mBufferLimit;
protected final byte[] mBuffer;
protected final Decoder mDecoder;
protected final ByteBuffer buffer;
protected final Decoder decoder;
/**
* Creates a new decoder stream and chains it to the
@@ -76,30 +74,24 @@ public final class DecoderStream extends FilterInputStream {
*/
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
super(pStream);
mDecoder = pDecoder;
mBuffer = new byte[pBufferSize];
mBufferPos = 0;
mBufferLimit = 0;
decoder = pDecoder;
buffer = ByteBuffer.allocate(pBufferSize);
buffer.flip();
}
public int available() throws IOException {
return mBufferLimit - mBufferPos + super.available();
return buffer.remaining();
}
public int read() throws IOException {
if (mBufferPos == mBufferLimit) {
mBufferLimit = fill();
if (!buffer.hasRemaining()) {
if (fill() < 0) {
return -1;
}
}
if (mBufferLimit < 0) {
return -1;
}
return mBuffer[mBufferPos++] & 0xff;
}
public int read(final byte pBytes[]) throws IOException {
return read(pBytes, 0, pBytes.length);
return buffer.get() & 0xff;
}
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
@@ -115,8 +107,10 @@ public final class DecoderStream extends FilterInputStream {
}
// End of file?
if ((mBufferLimit - mBufferPos) < 0) {
return -1;
if (!buffer.hasRemaining()) {
if (fill() < 0) {
return -1;
}
}
// Read until we have read pLength bytes, or have reached EOF
@@ -124,26 +118,20 @@ public final class DecoderStream extends FilterInputStream {
int off = pOffset;
while (pLength > count) {
int avail = mBufferLimit - mBufferPos;
if (avail <= 0) {
mBufferLimit = fill();
if (mBufferLimit < 0) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
break;
}
}
// Copy as many bytes as possible
int dstLen = Math.min(pLength - count, avail);
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen);
mBufferPos += dstLen;
int dstLen = Math.min(pLength - count, buffer.remaining());
buffer.get(pBytes, off, dstLen);
// Update offset (rest)
off += dstLen;
// Inrease count
// Increase count
count += dstLen;
}
@@ -152,29 +140,25 @@ public final class DecoderStream extends FilterInputStream {
public long skip(final long pLength) throws IOException {
// End of file?
if (mBufferLimit - mBufferPos < 0) {
return 0;
if (!buffer.hasRemaining()) {
if (fill() < 0) {
return 0; // Yes, 0, not -1
}
}
// Skip until we have skipped pLength bytes, or have reached EOF
long total = 0;
while (total < pLength) {
int avail = mBufferLimit - mBufferPos;
if (avail == 0) {
mBufferLimit = fill();
if (mBufferLimit < 0) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
break;
}
}
// NOTE: Skipped can never be more than avail, which is
// an int, so the cast is safe
int skipped = (int) Math.min(pLength - total, avail);
mBufferPos += skipped; // Just skip these bytes
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
int skipped = (int) Math.min(pLength - total, buffer.remaining());
buffer.position(buffer.position() + skipped);
total += skipped;
}
@@ -190,19 +174,20 @@ public final class DecoderStream extends FilterInputStream {
* @throws IOException if an I/O error occurs
*/
protected int fill() throws IOException {
int read = mDecoder.decode(in, mBuffer);
buffer.clear();
int read = decoder.decode(in, buffer);
// TODO: Enforce this in test case, leave here to aid debugging
if (read > mBuffer.length) {
if (read > buffer.capacity()) {
throw new AssertionError(
String.format(
"Decode beyond buffer (%d): %d (using %s decoder)",
mBuffer.length, read, mDecoder.getClass().getName()
buffer.capacity(), read, decoder.getClass().getName()
)
);
}
mBufferPos = 0;
buffer.flip();
if (read == 0) {
return -1;
@@ -30,11 +30,12 @@ package com.twelvemonkeys.io.enc;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Interface for endcoders.
* Interface for encoders.
* An {@code Encoder} may be used with an {@code EncoderStream}, to perform
* on-the-fly enoding to an {@code OutputStream}.
* on-the-fly encoding to an {@code OutputStream}.
* <p/>
* Important note: Encoder implementations are typically not synchronized.
*
@@ -47,18 +48,15 @@ import java.io.OutputStream;
public interface Encoder {
/**
* Encodes up to {@code pBuffer.length} bytes into the given input stream,
* Encodes up to {@code buffer.remaining()} bytes into the given input stream,
* from the given buffer.
*
* @param pStream the outputstream to encode data to
* @param pBuffer buffer to read data from
* @param pOffset offset into the buffer array
* @param pLength length of data in the buffer
* @param stream the output stream to encode data to
* @param buffer buffer to read data from
*
* @throws java.io.IOException if an I/O error occurs
*/
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength)
throws IOException;
void encode(OutputStream stream, ByteBuffer buffer) throws IOException;
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
// void flush()?
@@ -29,8 +29,9 @@
package com.twelvemonkeys.io.enc;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* An {@code OutputStream} that provides on-the-fly encoding to an underlying
@@ -43,12 +44,12 @@ import java.io.IOException;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
*/
public final class EncoderStream extends FilterOutputStream {
// TODO: This class need a test case ASAP!!!
protected final Encoder mEncoder;
private final boolean mFlushOnWrite;
protected final Encoder encoder;
private final boolean flushOnWrite;
protected int mBufferPos;
protected final byte[] mBuffer;
protected final ByteBuffer buffer;
/**
* Creates an output stream filter built on top of the specified
@@ -73,11 +74,11 @@ public final class EncoderStream extends FilterOutputStream {
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
super(pStream);
mEncoder = pEncoder;
mFlushOnWrite = pFlushOnWrite;
encoder = pEncoder;
flushOnWrite = pFlushOnWrite;
mBuffer = new byte[1024];
mBufferPos = 0;
buffer = ByteBuffer.allocate(1024);
buffer.flip();
}
public void close() throws IOException {
@@ -91,12 +92,14 @@ public final class EncoderStream extends FilterOutputStream {
}
private void encodeBuffer() throws IOException {
if (mBufferPos != 0) {
if (buffer.position() != 0) {
buffer.flip();
// Make sure all remaining data in buffer is written to the stream
mEncoder.encode(out, mBuffer, 0, mBufferPos);
encoder.encode(out, buffer);
// Reset buffer
mBufferPos = 0;
buffer.clear();
}
}
@@ -109,27 +112,24 @@ public final class EncoderStream extends FilterOutputStream {
// that the encoder can't buffer. In that case, the encoder should probably
// tell the EncoderStream how large buffer it prefers...
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (!mFlushOnWrite && mBufferPos + pLength < mBuffer.length) {
if (!flushOnWrite && pLength < buffer.remaining()) {
// Buffer data
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength);
mBufferPos += pLength;
buffer.put(pBytes, pOffset, pLength);
}
else {
// Encode data already in the buffer
if (mBufferPos != 0) {
encodeBuffer();
}
encodeBuffer();
// Encode rest without buffering
mEncoder.encode(out, pBytes, pOffset, pLength);
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
}
}
public void write(final int pByte) throws IOException {
if (mBufferPos >= mBuffer.length - 1) {
encodeBuffer(); // Resets mBufferPos to 0
if (!buffer.hasRemaining()) {
encodeBuffer(); // Resets bufferPos to 0
}
mBuffer[mBufferPos++] = (byte) pByte;
buffer.put((byte) pByte);
}
}
@@ -28,9 +28,10 @@
package com.twelvemonkeys.io.enc;
import java.io.InputStream;
import java.io.IOException;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Decoder implementation for 16 bit-chunked Apple PackBits-like run-length
@@ -46,11 +47,11 @@ import java.io.EOFException;
*/
public final class PackBits16Decoder implements Decoder {
// TODO: Refactor this into an option for the PackBitsDecoder?
private final boolean mDisableNoop;
private final boolean disableNoop;
private int mLeftOfRun;
private boolean mSplitRun;
private boolean mEOF;
private int leftOfRun;
private boolean splitRun;
private boolean reachedEOF;
/**
* Creates a {@code PackBitsDecoder}.
@@ -71,40 +72,40 @@ public final class PackBits16Decoder implements Decoder {
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/
public PackBits16Decoder(final boolean pDisableNoop) {
mDisableNoop = pDisableNoop;
disableNoop = pDisableNoop;
}
/**
* Decodes bytes from the given input stream, to the given buffer.
*
* @param pStream the stream to decode from
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
* @param stream the stream to decode from
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled)
* bytes long
* @return The number of bytes decoded
*
* @throws java.io.IOException
*/
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mEOF) {
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
if (reachedEOF) {
return -1;
}
int read = 0;
final int max = pBuffer.length;
final int max = buffer.capacity();
while (read < max) {
int n;
if (mSplitRun) {
if (splitRun) {
// Continue run
n = mLeftOfRun;
mSplitRun = false;
n = leftOfRun;
splitRun = false;
}
else {
// Start new run
int b = pStream.read();
int b = stream.read();
if (b < 0) {
mEOF = true;
reachedEOF = true;
break;
}
n = (byte) b;
@@ -112,13 +113,13 @@ public final class PackBits16Decoder implements Decoder {
// Split run at or before max
if (n >= 0 && 2 * (n + 1) + read > max) {
mLeftOfRun = n;
mSplitRun = true;
leftOfRun = n;
splitRun = true;
break;
}
else if (n < 0 && 2 * (-n + 1) + read > max) {
mLeftOfRun = n;
mSplitRun = true;
leftOfRun = n;
splitRun = true;
break;
}
@@ -126,18 +127,18 @@ public final class PackBits16Decoder implements Decoder {
if (n >= 0) {
// Copy next n + 1 shorts literally
int len = 2 * (n + 1);
readFully(pStream, pBuffer, read, len);
readFully(stream, buffer, len);
read += len;
}
// Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) {
else if (disableNoop || n != -128) {
// Replicate the next short -n + 1 times
byte value1 = readByte(pStream);
byte value2 = readByte(pStream);
byte value1 = readByte(stream);
byte value2 = readByte(stream);
for (int i = -n + 1; i > 0; i--) {
pBuffer[read++] = value1;
pBuffer[read++] = value2;
buffer.put(value1);
buffer.put(value2);
}
}
// else NOOP (-128)
@@ -160,7 +161,7 @@ public final class PackBits16Decoder implements Decoder {
return (byte) read;
}
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
private static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException {
if (pLength < 0) {
throw new IndexOutOfBoundsException();
}
@@ -168,7 +169,7 @@ public final class PackBits16Decoder implements Decoder {
int read = 0;
while (read < pLength) {
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + read, pLength - read);
if (count < 0) {
throw new EOFException("Unexpected end of PackBits stream");
@@ -28,9 +28,10 @@
package com.twelvemonkeys.io.enc;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.EOFException;
import java.nio.ByteBuffer;
/**
* Decoder implementation for Apple PackBits run-length encoding.
@@ -63,11 +64,13 @@ import java.io.EOFException;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $
*/
public final class PackBitsDecoder implements Decoder {
private final boolean mDisableNoop;
// TODO: Look at ICNSImageReader#unpackbits... What is this weirdness?
private int mLeftOfRun;
private boolean mSplitRun;
private boolean mEOF;
private final boolean disableNoop;
private int leftOfRun;
private boolean splitRun;
private boolean reachedEOF;
/** Creates a {@code PackBitsDecoder}. */
public PackBitsDecoder() {
@@ -78,80 +81,72 @@ public final class PackBitsDecoder implements Decoder {
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
* <p/>
* As some implementations of PackBits-like encoders treat {@code -128} as length of
* a compressed run, instead of a no-op, it's possible to disable no-ops
* for compatibility.
* Should be used with caution, even though, most known encoders never write
* no-ops in the compressed streams.
* a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility.
* Should be used with caution, even though, most known encoders never write no-ops in the compressed streams.
*
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/
public PackBitsDecoder(final boolean pDisableNoop) {
mDisableNoop = pDisableNoop;
disableNoop = pDisableNoop;
}
/**
* Decodes bytes from the given input stream, to the given buffer.
*
* @param pStream the stream to decode from
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
* bytes long
* @param stream the stream to decode from
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long
* @return The number of bytes decoded
*
* @throws IOException
* @throws java.io.IOException
*/
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
if (mEOF) {
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
if (reachedEOF) {
return -1;
}
int read = 0;
final int max = pBuffer.length;
// TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream...
while (read < max) {
while (buffer.hasRemaining()) {
int n;
if (mSplitRun) {
if (splitRun) {
// Continue run
n = mLeftOfRun;
mSplitRun = false;
n = leftOfRun;
splitRun = false;
}
else {
// Start new run
int b = pStream.read();
int b = stream.read();
if (b < 0) {
mEOF = true;
reachedEOF = true;
break;
}
n = (byte) b;
}
// Split run at or before max
if (n >= 0 && n + 1 + read > max) {
mLeftOfRun = n;
mSplitRun = true;
if (n >= 0 && n + 1 > buffer.remaining()) {
leftOfRun = n;
splitRun = true;
break;
}
else if (n < 0 && -n + 1 + read > max) {
mLeftOfRun = n;
mSplitRun = true;
else if (n < 0 && -n + 1 > buffer.remaining()) {
leftOfRun = n;
splitRun = true;
break;
}
try {
if (n >= 0) {
// Copy next n + 1 bytes literally
readFully(pStream, pBuffer, read, n + 1);
read += n + 1;
readFully(stream, buffer, n + 1);
}
// Allow -128 for compatibility, see above
else if (mDisableNoop || n != -128) {
else if (disableNoop || n != -128) {
// Replicate the next byte -n + 1 times
byte value = readByte(pStream);
byte value = readByte(stream);
for (int i = -n + 1; i > 0; i--) {
pBuffer[read++] = value;
buffer.put(value);
}
}
// else NOOP (-128)
@@ -161,10 +156,10 @@ public final class PackBitsDecoder implements Decoder {
}
}
return read;
return buffer.position();
}
private static byte readByte(final InputStream pStream) throws IOException {
static byte readByte(final InputStream pStream) throws IOException {
int read = pStream.read();
if (read < 0) {
@@ -174,21 +169,23 @@ public final class PackBitsDecoder implements Decoder {
return (byte) read;
}
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException {
if (pLength < 0) {
throw new IndexOutOfBoundsException();
throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength));
}
int read = 0;
int total = 0;
while (read < pLength) {
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
while (total < pLength) {
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total);
if (count < 0) {
throw new EOFException("Unexpected end of PackBits stream");
}
read += count;
total += count;
}
pBuffer.position(pBuffer.position() + total);
}
}
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
import java.io.OutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Encoder implementation for Apple PackBits run-length encoding.
@@ -63,7 +64,7 @@ import java.io.IOException;
*/
public final class PackBitsEncoder implements Encoder {
final private byte[] mBuffer = new byte[128];
final private byte[] buffer = new byte[128];
/**
* Creates a {@code PackBitsEncoder}.
@@ -71,7 +72,12 @@ public final class PackBitsEncoder implements Encoder {
public PackBitsEncoder() {
}
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
buffer.position(buffer.remaining());
}
private void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
// NOTE: It's best to encode a 2 byte repeat
// run as a replicate run except when preceded and followed by a
// literal run, in which case it's best to merge the three into one
@@ -86,7 +92,7 @@ public final class PackBitsEncoder implements Encoder {
// Compressed run
int run = 1;
byte replicate = pBuffer[offset];
while(run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
while (run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
offset++;
run++;
}
@@ -101,17 +107,17 @@ public final class PackBitsEncoder implements Encoder {
run = 0;
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
mBuffer[run++] = pBuffer[offset++];
buffer[run++] = pBuffer[offset++];
}
// If last byte, include it in literal run, if space
if (offset == max && run > 0 && run < 128) {
mBuffer[run++] = pBuffer[offset++];
buffer[run++] = pBuffer[offset++];
}
if (run > 0) {
pStream.write(run - 1);
pStream.write(mBuffer, 0, run);
pStream.write(buffer, 0, run);
}
// If last byte, and not space, start new literal run
@@ -32,7 +32,7 @@ import java.io.InputStream;
import java.io.IOException;
/**
* Implements 4 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
* Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@@ -41,7 +41,7 @@ import java.io.IOException;
// TODO: Move to other package or make public
final class RLE4Decoder extends AbstractRLEDecoder {
public RLE4Decoder(int pWidth, int pHeight) {
public RLE4Decoder(final int pWidth, final int pHeight) {
super((pWidth + 1) / 2, pHeight);
}
@@ -49,7 +49,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
int deltaX = 0;
int deltaY = 0;
while (mSrcY >= 0) {
while (srcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
@@ -58,20 +58,20 @@ final class RLE4Decoder extends AbstractRLEDecoder {
case 0x00:
// End of line
// NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) {
mSrcX = mRow.length;
if (srcX != 0) {
srcX = row.length;
}
break;
case 0x01:
// End of bitmap
mSrcX = mRow.length;
mSrcY = 0;
srcX = row.length;
srcY = 0;
break;
case 0x02:
// Delta
deltaX = mSrcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read());
mSrcX = mRow.length;
deltaX = srcX + pInput.read();
deltaY = srcY - checkEOF(pInput.read());
srcX = row.length;
break;
default:
// Absolute mode
@@ -82,13 +82,13 @@ final class RLE4Decoder extends AbstractRLEDecoder {
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
while (byte2 > 1) {
int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) packed;
row[srcX++] = (byte) packed;
byte2 -= 2;
}
if (byte2 == 1) {
// TODO: Half byte alignment? Seems to be ok...
int packed = checkEOF(pInput.read());
mRow[mSrcX++] = (byte) (packed & 0xf0);
row[srcX++] = (byte) (packed & 0xf0);
}
if (paddingByte) {
checkEOF(pInput.read());
@@ -100,24 +100,24 @@ final class RLE4Decoder extends AbstractRLEDecoder {
// Encoded mode
// Replicate the two samples in byte2 as many times as byte1 says
while (byte1 > 1) {
mRow[mSrcX++] = (byte) byte2;
row[srcX++] = (byte) byte2;
byte1 -= 2;
}
if (byte1 == 1) {
// TODO: Half byte alignment? Seems to be ok...
mRow[mSrcX++] = (byte) (byte2 & 0xf0);
row[srcX++] = (byte) (byte2 & 0xf0);
}
}
// If we're done with a complete row, copy the data
if (mSrcX == mRow.length) {
if (srcX == row.length) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
mSrcX = (deltaX + 1) / 2;
srcX = (deltaX + 1) / 2;
if (deltaY > mSrcY) {
mSrcY = deltaY;
if (deltaY > srcY) {
srcY = deltaY;
break;
}
@@ -125,8 +125,8 @@ final class RLE4Decoder extends AbstractRLEDecoder {
deltaY = 0;
}
else {
mSrcX = 0;
mSrcY--;
srcX = 0;
srcY--;
break;
}
}
@@ -32,7 +32,7 @@ import java.io.InputStream;
import java.io.IOException;
/**
* Implements 8 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
* Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@@ -41,7 +41,7 @@ import java.io.IOException;
// TODO: Move to other package or make public
final class RLE8Decoder extends AbstractRLEDecoder {
public RLE8Decoder(int pWidth, int pHeight) {
public RLE8Decoder(final int pWidth, final int pHeight) {
super(pWidth, pHeight);
}
@@ -49,7 +49,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
int deltaX = 0;
int deltaY = 0;
while (mSrcY >= 0) {
while (srcY >= 0) {
int byte1 = pInput.read();
int byte2 = checkEOF(pInput.read());
@@ -58,27 +58,27 @@ final class RLE8Decoder extends AbstractRLEDecoder {
case 0x00:
// End of line
// NOTE: Some BMPs have double EOLs..
if (mSrcX != 0) {
mSrcX = mRow.length;
if (srcX != 0) {
srcX = row.length;
}
break;
case 0x01:
// End of bitmap
mSrcX = mRow.length;
mSrcY = 0;
srcX = row.length;
srcY = 0;
break;
case 0x02:
// Delta
deltaX = mSrcX + pInput.read();
deltaY = mSrcY - checkEOF(pInput.read());
mSrcX = mRow.length;
deltaX = srcX + pInput.read();
deltaY = srcY - checkEOF(pInput.read());
srcX = row.length;
break;
default:
// Absolute mode
// Copy the next byte2 (3..255) bytes from file to output
boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) {
mRow[mSrcX++] = (byte) checkEOF(pInput.read());
row[srcX++] = (byte) checkEOF(pInput.read());
}
if (paddingByte) {
checkEOF(pInput.read());
@@ -90,26 +90,26 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// Replicate byte2 as many times as byte1 says
byte value = (byte) byte2;
while (byte1-- > 0) {
mRow[mSrcX++] = value;
row[srcX++] = value;
}
}
// If we're done with a complete row, copy the data
if (mSrcX == mRow.length) {
if (srcX == row.length) {
// Move to new position, either absolute (delta) or next line
if (deltaX != 0 || deltaY != 0) {
mSrcX = deltaX;
if (deltaY != mSrcY) {
mSrcY = deltaY;
srcX = deltaX;
if (deltaY != srcY) {
srcY = deltaY;
break;
}
deltaX = 0;
deltaY = 0;
}
else {
mSrcX = 0;
mSrcY--;
srcX = 0;
srcY--;
break;
}
}
@@ -54,41 +54,44 @@ public final class CompoundDocument {
// TODO: Write support...
// TODO: Properties: http://support.microsoft.com/kb/186898
private static final byte[] MAGIC = new byte[]{
static final byte[] MAGIC = new byte[]{
(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
(byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
};
public static final int HEADER_SIZE = 512;
private final DataInput mInput;
private UUID mUID;
private int mSectorSize;
private int mShortSectorSize;
private int mDirectorySId;
private int mMinStreamSize;
private int mShortSATSID;
private int mShortSATSize;
// Master Sector Allocation Table
private int[] mMasterSAT;
private int[] mSAT;
private int[] mShortSAT;
private Entry mRootEntry;
private SIdChain mShortStreamSIdChain;
private SIdChain mDirectorySIdChain;
private static final int END_OF_CHAIN_SID = -2;
private static final int FREE_SID = -1;
private static final int END_OF_CHAIN_SID = -2;
private static final int SAT_SECTOR_SID = -3; // Sector used by SAT
private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT
public static final int HEADER_SIZE = 512;
/** The epoch offset of CompoundDocument time stamps */
public final static long EPOCH_OFFSET = -11644477200000L;
private final DataInput input;
private UUID uUID;
private int sectorSize;
private int shortSectorSize;
private int directorySId;
private int minStreamSize;
private int shortSATSId;
private int shortSATSize;
// Master Sector Allocation Table
private int[] masterSAT;
private int[] SAT;
private int[] shortSAT;
private Entry rootEntry;
private SIdChain shortStreamSIdChain;
private SIdChain directorySIdChain;
/**
* Creates a (for now) read only {@code CompoundDocument}.
*
@@ -97,7 +100,7 @@ public final class CompoundDocument {
* @throws IOException if an I/O exception occurs while reading the header
*/
public CompoundDocument(final File pFile) throws IOException {
mInput = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
input = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
// TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make
@@ -118,7 +121,7 @@ public final class CompoundDocument {
// For testing only, consider exposing later
CompoundDocument(final SeekableInputStream pInput) throws IOException {
mInput = new SeekableLittleEndianDataInputStream(pInput);
input = new SeekableLittleEndianDataInputStream(pInput);
// TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make
@@ -134,7 +137,7 @@ public final class CompoundDocument {
* @throws IOException if an I/O exception occurs while reading the header
*/
public CompoundDocument(final ImageInputStream pInput) throws IOException {
mInput = pInput;
input = pInput;
// TODO: Might be better to read header on first read operation?!
// OTOH: It's also good to be fail-fast, so at least we should make
@@ -210,74 +213,81 @@ public final class CompoundDocument {
}
private void readHeader() throws IOException {
if (mMasterSAT != null) {
if (masterSAT != null) {
return;
}
if (!canRead(mInput, false)) {
if (!canRead(input, false)) {
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
}
// UID (seems to be all 0s)
mUID = new UUID(mInput.readLong(), mInput.readLong());
uUID = new UUID(input.readLong(), input.readLong());
// System.out.println("uUID: " + uUID);
/*int version = */mInput.readUnsignedShort();
//System.out.println("version: " + version);
/*int revision = */mInput.readUnsignedShort();
//System.out.println("revision: " + revision);
// int version =
input.readUnsignedShort();
// System.out.println("version: " + version);
// int revision =
input.readUnsignedShort();
// System.out.println("revision: " + revision);
int byteOrder = mInput.readUnsignedShort();
if (byteOrder != 0xfffe) {
// Reversed, as I'm allready reading little-endian
int byteOrder = input.readUnsignedShort();
// System.out.printf("byteOrder: 0x%04x\n", byteOrder);
if (byteOrder == 0xffff) {
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
}
else if (byteOrder != 0xfffe) {
// Reversed, as I'm already reading little-endian
throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder));
}
mSectorSize = 1 << mInput.readUnsignedShort();
//System.out.println("sectorSize: " + mSectorSize + " bytes");
mShortSectorSize = 1 << mInput.readUnsignedShort();
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes");
sectorSize = 1 << input.readUnsignedShort();
// System.out.println("sectorSize: " + sectorSize + " bytes");
shortSectorSize = 1 << input.readUnsignedShort();
// System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
// Reserved
if (mInput.skipBytes(10) != 10) {
if (skipBytesFully(10) != 10) {
throw new CorruptDocumentException();
}
int SATSize = mInput.readInt();
//System.out.println("normalSATSize: " + mSATSize);
int SATSize = input.readInt();
// System.out.println("normalSATSize: " + SATSize);
mDirectorySId = mInput.readInt();
//System.out.println("directorySId: " + mDirectorySId);
directorySId = input.readInt();
// System.out.println("directorySId: " + directorySId);
// Reserved
if (mInput.skipBytes(4) != 4) {
if (skipBytesFully(4) != 4) {
throw new CorruptDocumentException();
}
mMinStreamSize = mInput.readInt();
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes");
minStreamSize = input.readInt();
// System.out.println("minStreamSize: " + minStreamSize + " bytes");
mShortSATSID = mInput.readInt();
//System.out.println("shortSATSID: " + mShortSATSID);
mShortSATSize = mInput.readInt();
//System.out.println("shortSATSize: " + mShortSATSize);
int masterSATSId = mInput.readInt();
//System.out.println("masterSATSId: " + mMasterSATSID);
int masterSATSize = mInput.readInt();
//System.out.println("masterSATSize: " + mMasterSATSize);
shortSATSId = input.readInt();
// System.out.println("shortSATSId: " + shortSATSId);
shortSATSize = input.readInt();
// System.out.println("shortSATSize: " + shortSATSize);
int masterSATSId = input.readInt();
// System.out.println("masterSATSId: " + masterSATSId);
int masterSATSize = input.readInt();
// System.out.println("masterSATSize: " + masterSATSize);
// Read masterSAT: 436 bytes, containing up to 109 SIDs
//System.out.println("MSAT:");
mMasterSAT = new int[SATSize];
masterSAT = new int[SATSize];
final int headerSIds = Math.min(SATSize, 109);
for (int i = 0; i < headerSIds; i++) {
mMasterSAT[i] = mInput.readInt();
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]);
masterSAT[i] = input.readInt();
//System.out.println("\tSID(" + i + "): " + masterSAT[i]);
}
if (masterSATSId == END_OF_CHAIN_SID) {
// End of chain
int freeSIdLength = 436 - (SATSize * 4);
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) {
if (skipBytesFully(freeSIdLength) != freeSIdLength) {
throw new CorruptDocumentException();
}
}
@@ -288,17 +298,17 @@ public final class CompoundDocument {
int index = headerSIds;
for (int i = 0; i < masterSATSize; i++) {
for (int j = 0; j < 127; j++) {
int sid = mInput.readInt();
int sid = input.readInt();
switch (sid) {
case FREE_SID:// Free
break;
default:
mMasterSAT[index++] = sid;
masterSAT[index++] = sid;
break;
}
}
int next = mInput.readInt();
int next = input.readInt();
if (next == END_OF_CHAIN_SID) {// End of chain
break;
}
@@ -308,38 +318,53 @@ public final class CompoundDocument {
}
}
private int skipBytesFully(final int n) throws IOException {
int toSkip = n;
while (toSkip > 0) {
int skipped = input.skipBytes(n);
if (skipped <= 0) {
break;
}
toSkip -= skipped;
}
return n - toSkip;
}
private void readSAT() throws IOException {
if (mSAT != null) {
if (SAT != null) {
return;
}
final int intsPerSector = mSectorSize / 4;
final int intsPerSector = sectorSize / 4;
// Read the Sector Allocation Table
mSAT = new int[mMasterSAT.length * intsPerSector];
SAT = new int[masterSAT.length * intsPerSector];
for (int i = 0; i < mMasterSAT.length; i++) {
seekToSId(mMasterSAT[i], FREE_SID);
for (int i = 0; i < masterSAT.length; i++) {
seekToSId(masterSAT[i], FREE_SID);
for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt();
int nextSID = input.readInt();
int index = (j + (i * intsPerSector));
mSAT[index] = nextSID;
SAT[index] = nextSID;
}
}
// Read the short-stream Sector Allocation Table
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID);
mShortSAT = new int[mShortSATSize * intsPerSector];
for (int i = 0; i < mShortSATSize; i++) {
SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
shortSAT = new int[shortSATSize * intsPerSector];
for (int i = 0; i < shortSATSize; i++) {
seekToSId(chain.get(i), FREE_SID);
for (int j = 0; j < intsPerSector; j++) {
int nextSID = mInput.readInt();
int nextSID = input.readInt();
int index = (j + (i * intsPerSector));
mShortSAT[index] = nextSID;
shortSAT[index] = nextSID;
}
}
}
@@ -355,7 +380,7 @@ public final class CompoundDocument {
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
SIdChain chain = new SIdChain();
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT;
int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT;
int sid = pSId;
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
@@ -367,7 +392,7 @@ public final class CompoundDocument {
}
private boolean isShortStream(final long pStreamSize) {
return pStreamSize != FREE_SID && pStreamSize < mMinStreamSize;
return pStreamSize != FREE_SID && pStreamSize < minStreamSize;
}
/**
@@ -381,70 +406,76 @@ public final class CompoundDocument {
long pos;
if (isShortStream(pStreamSize)) {
// The short-stream is not continouos...
// The short stream is not continuous...
Entry root = getRootEntry();
if (mShortStreamSIdChain == null) {
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
if (shortStreamSIdChain == null) {
shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
}
int shortPerStd = mSectorSize / mShortSectorSize;
int offset = pSId / shortPerStd;
int shortOffset = pSId - (offset * shortPerStd);
// System.err.println("pSId: " + pSId);
int shortPerSId = sectorSize / shortSectorSize;
// System.err.println("shortPerSId: " + shortPerSId);
int offset = pSId / shortPerSId;
// System.err.println("offset: " + offset);
int shortOffset = pSId - (offset * shortPerSId);
// System.err.println("shortOffset: " + shortOffset);
// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset));
pos = HEADER_SIZE
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize)
+ (shortOffset * (long) mShortSectorSize);
+ (shortStreamSIdChain.get(offset) * (long) sectorSize)
+ (shortOffset * (long) shortSectorSize);
// System.err.println("pos: " + pos);
}
else {
pos = HEADER_SIZE + pSId * (long) mSectorSize;
pos = HEADER_SIZE + pSId * (long) sectorSize;
}
if (mInput instanceof LittleEndianRandomAccessFile) {
((LittleEndianRandomAccessFile) mInput).seek(pos);
if (input instanceof LittleEndianRandomAccessFile) {
((LittleEndianRandomAccessFile) input).seek(pos);
}
else if (mInput instanceof ImageInputStream) {
((ImageInputStream) mInput).seek(pos);
else if (input instanceof ImageInputStream) {
((ImageInputStream) input).seek(pos);
}
else {
((SeekableLittleEndianDataInputStream) mInput).seek(pos);
((SeekableLittleEndianDataInputStream) input).seek(pos);
}
}
private void seekToDId(final int pDId) throws IOException {
if (mDirectorySIdChain == null) {
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID);
if (directorySIdChain == null) {
directorySIdChain = getSIdChain(directorySId, FREE_SID);
}
int dIdsPerSId = mSectorSize / Entry.LENGTH;
int dIdsPerSId = sectorSize / Entry.LENGTH;
int sIdOffset = pDId / dIdsPerSId;
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
int sId = mDirectorySIdChain.get(sIdOffset);
int sId = directorySIdChain.get(sIdOffset);
seekToSId(sId, FREE_SID);
if (mInput instanceof LittleEndianRandomAccessFile) {
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput;
if (input instanceof LittleEndianRandomAccessFile) {
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
}
else if (mInput instanceof ImageInputStream) {
ImageInputStream input = (ImageInputStream) mInput;
else if (input instanceof ImageInputStream) {
ImageInputStream input = (ImageInputStream) this.input;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
}
else {
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput;
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
}
}
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
SIdChain chain = getSIdChain(pStreamId, pStreamSize);
// TODO: Detach? Means, we have to copy to a byte buffer, or keep track of
// positions, and seek back and forth (would be cool, but difficult)..
int sectorSize = pStreamSize < mMinStreamSize ? mShortSectorSize : mSectorSize;
int sectorSize = pStreamSize < minStreamSize ? shortSectorSize : this.sectorSize;
return new Stream(chain, pStreamSize, sectorSize, this);
return new MemoryCacheSeekableStream(new Stream(chain, pStreamSize, sectorSize, this));
}
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
@@ -453,7 +484,7 @@ public final class CompoundDocument {
byte[] bytes = new byte[Entry.LENGTH];
seekToDId(pDirectoryId);
mInput.readFully(bytes);
input.readFully(bytes);
return new ByteArrayInputStream(bytes);
}
@@ -462,8 +493,8 @@ public final class CompoundDocument {
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
getDirectoryStreamForDId(pDirectoryId)
));
entry.mParent = pParent;
entry.mDocument = this;
entry.parent = pParent;
entry.document = this;
return entry;
}
@@ -527,21 +558,23 @@ public final class CompoundDocument {
}
public Entry getRootEntry() throws IOException {
if (mRootEntry == null) {
if (rootEntry == null) {
readSAT();
mRootEntry = getEntry(0, null);
rootEntry = getEntry(0, null);
if (mRootEntry.type != Entry.ROOT_STORAGE) {
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type);
if (rootEntry.type != Entry.ROOT_STORAGE) {
throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type);
}
}
return mRootEntry;
return rootEntry;
}
// This is useless, as most documents on file have all-zero UUIDs...
// @Override
// public int hashCode() {
// return mUID.hashCode();
// return uUID.hashCode();
// }
//
// @Override
@@ -555,7 +588,7 @@ public final class CompoundDocument {
// }
//
// if (pOther.getClass() == getClass()) {
// return mUID.equals(((CompoundDocument) pOther).mUID);
// return uUID.equals(((CompoundDocument) pOther).uUID);
// }
//
// return false;
@@ -565,7 +598,7 @@ public final class CompoundDocument {
public String toString() {
return String.format(
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
getClass().getSimpleName(), mUID, mSectorSize, mShortSectorSize, mDirectorySId, mMasterSAT.length
getClass().getSimpleName(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.length
);
}
@@ -601,29 +634,29 @@ public final class CompoundDocument {
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
}
// TODO: Enforce stream length!
static class Stream extends SeekableInputStream {
private SIdChain mChain;
int mNextSectorPos;
byte[] mBuffer;
int mBufferPos;
static class Stream extends InputStream {
private final SIdChain chain;
private final CompoundDocument document;
private final long length;
private final CompoundDocument mDocument;
private final long mLength;
private long streamPos;
private int nextSectorPos;
private byte[] buffer;
private int bufferPos;
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) {
mChain = pChain;
mLength = pLength;
public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
this.chain = chain;
this.length = streamSize;
mBuffer = new byte[pSectorSize];
mBufferPos = mBuffer.length;
this.buffer = new byte[sectorSize];
this.bufferPos = buffer.length;
mDocument = pDocument;
this.document = document;
}
@Override
public int available() throws IOException {
return (int) Math.min(mBuffer.length - mBufferPos, mLength - getStreamPosition());
return (int) Math.min(buffer.length - bufferPos, length - streamPos);
}
public int read() throws IOException {
@@ -633,20 +666,23 @@ public final class CompoundDocument {
}
}
return mBuffer[mBufferPos++] & 0xff;
streamPos++;
return buffer[bufferPos++] & 0xff;
}
private boolean fillBuffer() throws IOException {
if (mNextSectorPos < mChain.length()) {
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-)
// TODO: We also need to sync other places...
synchronized (mDocument) {
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength);
mDocument.mInput.readFully(mBuffer);
if (streamPos < length && nextSectorPos < chain.length()) {
// TODO: Sync on document.input here, and we are completely detached... :-)
// TODO: Update: We also need to sync other places... :-P
synchronized (document) {
document.seekToSId(chain.get(nextSectorPos), length);
document.input.readFully(buffer);
}
mNextSectorPos++;
mBufferPos = 0;
nextSectorPos++;
bufferPos = 0;
return true;
}
@@ -663,99 +699,66 @@ public final class CompoundDocument {
int toRead = Math.min(len, available());
System.arraycopy(mBuffer, mBufferPos, b, off, toRead);
mBufferPos += toRead;
System.arraycopy(buffer, bufferPos, b, off, toRead);
bufferPos += toRead;
streamPos += toRead;
return toRead;
}
public boolean isCached() {
return true;
}
public boolean isCachedMemory() {
return false;
}
public boolean isCachedFile() {
return true;
}
protected void closeImpl() throws IOException {
mBuffer = null;
mChain = null;
}
protected void seekImpl(final long pPosition) throws IOException {
long pos = getStreamPosition();
if (pos - mBufferPos >= pPosition && pPosition <= pos + available()) {
// Skip inside buffer only
mBufferPos += (pPosition - pos);
}
else {
// Skip outside buffer
mNextSectorPos = (int) (pPosition / mBuffer.length);
if (!fillBuffer()) {
throw new EOFException();
}
mBufferPos = (int) (pPosition % mBuffer.length);
}
}
protected void flushBeforeImpl(long pPosition) throws IOException {
// No need to do anything here
@Override
public void close() throws IOException {
buffer = null;
}
}
// TODO: Add test case for this class!!!
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
private final SeekableInputStream mSeekable;
private final SeekableInputStream seekable;
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
super(pInput);
mSeekable = pInput;
seekable = pInput;
}
public void seek(final long pPosition) throws IOException {
mSeekable.seek(pPosition);
seekable.seek(pPosition);
}
public boolean isCachedFile() {
return mSeekable.isCachedFile();
return seekable.isCachedFile();
}
public boolean isCachedMemory() {
return mSeekable.isCachedMemory();
return seekable.isCachedMemory();
}
public boolean isCached() {
return mSeekable.isCached();
return seekable.isCached();
}
public long getStreamPosition() throws IOException {
return mSeekable.getStreamPosition();
return seekable.getStreamPosition();
}
public long getFlushedPosition() throws IOException {
return mSeekable.getFlushedPosition();
return seekable.getFlushedPosition();
}
public void flushBefore(final long pPosition) throws IOException {
mSeekable.flushBefore(pPosition);
seekable.flushBefore(pPosition);
}
public void flush() throws IOException {
mSeekable.flush();
seekable.flush();
}
@Override
public void reset() throws IOException {
mSeekable.reset();
seekable.reset();
}
public void mark() {
mSeekable.mark();
seekable.mark();
}
}
}
@@ -32,6 +32,7 @@ import com.twelvemonkeys.io.SeekableInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -61,9 +62,9 @@ public final class Entry implements Comparable<Entry> {
int startSId;
int streamSize;
CompoundDocument mDocument;
Entry mParent;
SortedSet<Entry> mChildren;
CompoundDocument document;
Entry parent;
SortedSet<Entry> children;
public final static int LENGTH = 128;
@@ -99,28 +100,26 @@ public final class Entry implements Comparable<Entry> {
* @throws IOException if an i/o exception occurs during reading
*/
private void read(final DataInput pInput) throws IOException {
char[] chars = new char[32];
for (int i = 0; i < chars.length; i++) {
chars[i] = pInput.readChar();
}
byte[] bytes = new byte[64];
pInput.readFully(bytes);
// NOTE: Length is in bytes, including the null-terminator...
int nameLength = pInput.readShort();
name = new String(chars, 0, (nameLength - 1) / 2);
//System.out.println("name: " + name);
name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
// System.out.println("name: " + name);
type = pInput.readByte();
//System.out.println("type: " + type);
// System.out.println("type: " + type);
nodeColor = pInput.readByte();
//System.out.println("nodeColor: " + nodeColor);
// System.out.println("nodeColor: " + nodeColor);
prevDId = pInput.readInt();
//System.out.println("prevDID: " + prevDID);
// System.out.println("prevDId: " + prevDId);
nextDId = pInput.readInt();
//System.out.println("nextDID: " + nextDID);
// System.out.println("nextDId: " + nextDId);
rootNodeDId = pInput.readInt();
//System.out.println("rootNodeDID: " + rootNodeDID);
// System.out.println("rootNodeDId: " + rootNodeDId);
// UID (16) + user flags (4), ignored
if (pInput.skipBytes(20) != 20) {
@@ -131,9 +130,9 @@ public final class Entry implements Comparable<Entry> {
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
startSId = pInput.readInt();
//System.out.println("startSID: " + startSID);
// System.out.println("startSId: " + startSId);
streamSize = pInput.readInt();
//System.out.println("streamSize: " + streamSize);
// System.out.println("streamSize: " + streamSize);
// Reserved
pInput.readInt();
@@ -186,11 +185,11 @@ public final class Entry implements Comparable<Entry> {
* @see #length()
*/
public SeekableInputStream getInputStream() throws IOException {
if (isDirectory()) {
if (!isFile()) {
return null;
}
return mDocument.getInputStreamForSId(startSId, streamSize);
return document.getInputStreamForSId(startSId, streamSize);
}
/**
@@ -201,9 +200,10 @@ public final class Entry implements Comparable<Entry> {
* @see #getInputStream()
*/
public long length() {
if (isDirectory()) {
if (!isFile()) {
return 0L;
}
return streamSize;
}
@@ -248,7 +248,7 @@ public final class Entry implements Comparable<Entry> {
* the root {@code Entry}
*/
public Entry getParentEntry() {
return mParent;
return parent;
}
/**
@@ -266,7 +266,7 @@ public final class Entry implements Comparable<Entry> {
Entry dummy = new Entry();
dummy.name = pName;
dummy.mParent = this;
dummy.parent = this;
SortedSet child = getChildEntries().tailSet(dummy);
return (Entry) child.first();
@@ -279,26 +279,26 @@ public final class Entry implements Comparable<Entry> {
* @throws java.io.IOException if an I/O exception occurs
*/
public SortedSet<Entry> getChildEntries() throws IOException {
if (mChildren == null) {
if (children == null) {
if (isFile() || rootNodeDId == -1) {
mChildren = NO_CHILDREN;
children = NO_CHILDREN;
}
else {
// Start at root node in R/B tree, and raed to the left and right,
// Start at root node in R/B tree, and read to the left and right,
// re-build tree, according to the docs
mChildren = mDocument.getEntries(rootNodeDId, this);
children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
}
}
return mChildren;
return children;
}
@Override
public String toString() {
return "\"" + name + "\""
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "")
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)"))
+ (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
+ (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)"))
+ ", SId=" + startSId + ", length=" + streamSize + ")";
}
@@ -312,8 +312,8 @@ public final class Entry implements Comparable<Entry> {
}
Entry other = (Entry) pOther;
return name.equals(other.name) && (mParent == other.mParent
|| (mParent != null && mParent.equals(other.mParent)));
return name.equals(other.name) && (parent == other.parent
|| (parent != null && parent.equals(other.parent)));
}
@Override
@@ -37,7 +37,7 @@ import java.util.NoSuchElementException;
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $
*/
class SIdChain {
final class SIdChain {
int[] chain;
int size = 0;
int next = 0;
@@ -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.net;
import com.twelvemonkeys.lang.DateUtil;
import com.twelvemonkeys.lang.StringUtil;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* HTTPUtil
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HTTPUtil.java,v 1.0 08.09.13 13:57 haraldk Exp$
*/
public class HTTPUtil {
/**
* RFC 1123 date format, as recommended by RFC 2616 (HTTP/1.1), sec 3.3
* NOTE: All date formats are private, to ensure synchronized access.
*/
private static final SimpleDateFormat HTTP_RFC1123_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
static {
HTTP_RFC1123_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
}
/**
* RFC 850 date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
*/
private static final SimpleDateFormat HTTP_RFC850_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US);
/**
* ANSI C asctime() date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3.
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
*/
private static final SimpleDateFormat HTTP_ASCTIME_FORMAT = new SimpleDateFormat("EEE MMM d HH:mm:ss yy", Locale.US);
private static long sNext50YearWindowChange = DateUtil.currentTimeDay();
static {
HTTP_RFC850_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
HTTP_ASCTIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3:
// - HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
// which appears to be more than 50 years in the future is in fact
// in the past (this helps solve the "year 2000" problem).
update50YearWindowIfNeeded();
}
private static void update50YearWindowIfNeeded() {
// Avoid class synchronization
long next = sNext50YearWindowChange;
if (next < System.currentTimeMillis()) {
// Next check in one day
next += DateUtil.DAY;
sNext50YearWindowChange = next;
Date startDate = new Date(next - (50l * DateUtil.CALENDAR_YEAR));
//System.out.println("next test: " + new Date(next) + ", 50 year start: " + startDate);
synchronized (HTTP_RFC850_FORMAT) {
HTTP_RFC850_FORMAT.set2DigitYearStart(startDate);
}
synchronized (HTTP_ASCTIME_FORMAT) {
HTTP_ASCTIME_FORMAT.set2DigitYearStart(startDate);
}
}
}
private HTTPUtil() {}
/**
* Formats the time to a HTTP date, using the RFC 1123 format, as described
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
*
* @param pTime the time
* @return a {@code String} representation of the time
*/
public static String formatHTTPDate(long pTime) {
return formatHTTPDate(new Date(pTime));
}
/**
* Formats the time to a HTTP date, using the RFC 1123 format, as described
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
*
* @param pTime the time
* @return a {@code String} representation of the time
*/
public static String formatHTTPDate(Date pTime) {
synchronized (HTTP_RFC1123_FORMAT) {
return HTTP_RFC1123_FORMAT.format(pTime);
}
}
/**
* Parses a HTTP date string into a {@code long} representing milliseconds
* since January 1, 1970 GMT.
* <p>
* Use this method with headers that contain dates, such as
* {@code If-Modified-Since} or {@code Last-Modified}.
* <p>
* The date string may be in either RFC 1123, RFC 850 or ANSI C asctime()
* format, as described in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>
*
* @param pDate the date to parse
*
* @return a {@code long} value representing the date, expressed as the
* number of milliseconds since January 1, 1970 GMT,
* @throws NumberFormatException if the date parameter is not parseable.
* @throws IllegalArgumentException if the date paramter is {@code null}
*/
public static long parseHTTPDate(String pDate) throws NumberFormatException {
return parseHTTPDateImpl(pDate).getTime();
}
/**
* ParseHTTPDate implementation
*
* @param pDate the date string to parse
*
* @return a {@code Date}
* @throws NumberFormatException if the date parameter is not parseable.
* @throws IllegalArgumentException if the date paramter is {@code null}
*/
private static Date parseHTTPDateImpl(final String pDate) throws NumberFormatException {
if (pDate == null) {
throw new IllegalArgumentException("date == null");
}
if (StringUtil.isEmpty(pDate)) {
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
}
DateFormat format;
if (pDate.indexOf('-') >= 0) {
format = HTTP_RFC850_FORMAT;
update50YearWindowIfNeeded();
}
else if (pDate.indexOf(',') < 0) {
format = HTTP_ASCTIME_FORMAT;
update50YearWindowIfNeeded();
}
else {
format = HTTP_RFC1123_FORMAT;
// NOTE: RFC1123 always uses 4-digit years
}
Date date;
try {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (format) {
date = format.parse(pDate);
}
}
catch (ParseException e) {
NumberFormatException nfe = new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
nfe.initCause(e);
throw nfe;
}
if (date == null) {
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
}
return date;
}
}
@@ -52,13 +52,13 @@ public final class DOMSerializer {
private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
private static final String PARAM_XML_DECLARATION = "xml-declaration";
private final LSSerializer mSerializer;
private final LSOutput mOutput;
private final LSSerializer serializer;
private final LSOutput output;
private DOMSerializer() {
DOMImplementationLS domImpl = Support.getImplementation();
mSerializer = domImpl.createLSSerializer();
mOutput = domImpl.createLSOutput();
serializer = domImpl.createLSSerializer();
output = domImpl.createLSOutput();
}
/**
@@ -71,8 +71,8 @@ public final class DOMSerializer {
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
this();
mOutput.setByteStream(pStream);
mOutput.setEncoding(pEncoding);
output.setByteStream(pStream);
output.setEncoding(pEncoding);
}
/**
@@ -84,17 +84,17 @@ public final class DOMSerializer {
public DOMSerializer(final Writer pStream) {
this();
mOutput.setCharacterStream(pStream);
output.setCharacterStream(pStream);
}
/*
// TODO: Is it useful?
public void setNewLine(final String pNewLine) {
mSerializer.setNewLine(pNewLine);
serializer.setNewLine(pNewLine);
}
public String getNewLine() {
return mSerializer.getNewLine();
return serializer.getNewLine();
}
*/
@@ -107,18 +107,18 @@ public final class DOMSerializer {
* @param pPrettyPrint {@code true} to enable pretty printing
*/
public void setPrettyPrint(final boolean pPrettyPrint) {
DOMConfiguration configuration = mSerializer.getDomConfig();
DOMConfiguration configuration = serializer.getDomConfig();
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
}
}
public boolean getPrettyPrint() {
return Boolean.TRUE.equals(mSerializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
return Boolean.TRUE.equals(serializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
}
private void setXMLDeclaration(boolean pXMLDeclaration) {
mSerializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
serializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
}
/**
@@ -142,7 +142,7 @@ public final class DOMSerializer {
private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
setXMLDeclaration(pOmitDecl);
mSerializer.write(pNode, mOutput);
serializer.write(pNode, output);
}
private static class Support {
@@ -59,28 +59,25 @@ public class XMLSerializer {
// TODO: Consider using IOException to communicate trouble, rather than RTE,
// to be more compatible...
// TODO: Idea: Create a SerializationContext that stores attributes on
// serialization, to keep the serialization thread-safe
// Store preserveSpace attribute in this context, to avoid costly traversals
// Store user options here too
// TODO: Push/pop?
private final OutputStream mOutput;
private final Charset mEncoding;
private final SerializationContext mContext;
private final OutputStream output;
private final Charset encoding;
private final SerializationContext context;
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
mOutput = pOutput;
mEncoding = Charset.forName(pEncoding);
mContext = new SerializationContext();
output = pOutput;
encoding = Charset.forName(pEncoding);
context = new SerializationContext();
}
public final void setIndentation(String pIndent) {
mContext.indent = pIndent != null ? pIndent : " ";
public final XMLSerializer indentation(String pIndent) {
// TODO: Verify that indent value is only whitespace?
context.indent = pIndent != null ? pIndent : "\t";
return this;
}
public final void setStripComments(boolean pStrip) {
mContext.stripComments = pStrip;
public final XMLSerializer stripComments(boolean pStrip) {
context.stripComments = pStrip;
return this;
}
/**
@@ -101,12 +98,12 @@ public class XMLSerializer {
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
*/
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding));
PrintWriter out = new PrintWriter(new OutputStreamWriter(output, encoding));
try {
if (pWriteXMLDeclaration) {
writeXMLDeclaration(out);
}
writeXML(out, pRootNode, mContext.copy());
writeXML(out, pRootNode, context.copy());
}
finally {
out.flush();
@@ -115,7 +112,7 @@ public class XMLSerializer {
private void writeXMLDeclaration(final PrintWriter pOut) {
pOut.print("<?xml version=\"1.0\" encoding=\"");
pOut.print(mEncoding.name());
pOut.print(encoding.name());
pOut.println("\"?>");
}
@@ -279,11 +276,7 @@ public class XMLSerializer {
pos = appendAndEscape(pValue, pos, i, builder, "&gt;");
break;
//case '\'':
// pos = appendAndEscape(pString, pos, i, builder, "&apos;");
// break;
//case '"':
// pos = appendAndEscape(pString, pos, i, builder, "&quot;");
// break;
default:
break;
}
@@ -347,17 +340,6 @@ public class XMLSerializer {
}
}
//StringBuilder builder = new StringBuilder(pValue.length() + 30);
//
//int start = 0;
//while (end >= 0) {
// builder.append(pValue.substring(start, end));
// builder.append("&quot;");
// start = end + 1;
// end = pValue.indexOf('"', start);
//}
//builder.append(pValue.substring(start));
builder.append(pValue.substring(pos));
return builder.toString();
@@ -389,14 +371,14 @@ public class XMLSerializer {
}
private static String validateCDataValue(final String pValue) {
if (pValue.indexOf("]]>") >= 0) {
if (pValue.contains("]]>")) {
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
}
return pValue;
}
private static String validateCommentValue(final String pValue) {
if (pValue.indexOf("--") >= 0) {
if (pValue.contains("--")) {
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
}
return pValue;
@@ -420,8 +402,6 @@ public class XMLSerializer {
// even if the document was created using attributes instead of namespaces...
// In that case, prefix will be null...
// TODO: Don't insert duplicate/unnecessary namesspace declarations
// Handle namespace
String namespace = pNode.getNamespaceURI();
if (namespace != null && !namespace.equals(pContext.defaultNamespace)) {
@@ -570,6 +550,11 @@ public class XMLSerializer {
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
test.appendChild(pre);
Element pre2 = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "tight");
pre2.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
pre2.appendChild(document.createTextNode("no-space-around-me"));
test.appendChild(pre2);
// Create serializer and output document
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
System.out.println("XMLSerializer:");
@@ -612,7 +597,7 @@ public class XMLSerializer {
}
static class SerializationContext implements Cloneable {
String indent = " ";
String indent = "\t";
int level = 0;
boolean preserveSpace = false;
boolean stripComments = false;
@@ -60,7 +60,7 @@ iff,ilbm=image/x-iff;image/iff
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
jpm=image/jpm
png=image/png;image/x-png
# NOTE: image/svg-xml is an old reccomendation, should not be used
# NOTE: image/svg-xml is an old recommendation, should not be used
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
tga=image/targa;image/x-targa
tif,tiff=image/tiff;image/x-tiff
@@ -2,6 +2,7 @@ package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.util.CollectionUtil;
import org.junit.Test;
import java.io.Reader;
import java.io.IOException;
@@ -9,6 +10,8 @@ import java.io.StringReader;
import java.util.List;
import java.util.ArrayList;
import static org.junit.Assert.*;
/**
* CompoundReaderTestCase
* <p/>
@@ -18,7 +21,6 @@ import java.util.ArrayList;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $
*/
public class CompoundReaderTestCase extends ReaderAbstractTestCase {
protected Reader makeReader(String pInput) {
// Split
String[] input = StringUtil.toStringArray(pInput, " ");
@@ -36,6 +38,7 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
return new CompoundReader(readers.iterator());
}
@Test
public void testNullConstructor() {
try {
new CompoundReader(null);
@@ -46,11 +49,13 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
}
}
@Test
public void testEmptyIteratorConstructor() throws IOException {
Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0]));
assertEquals(-1, reader.read());
}
@Test
public void testIteratorWithNullConstructor() throws IOException {
try {
new CompoundReader(CollectionUtil.iterator(new Reader[] {null}));
@@ -1,8 +1,12 @@
package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.assertEquals;
/**
* FastByteArrayOutputStreamTestCase
* <p/>
@@ -16,6 +20,7 @@ public class FastByteArrayOutputStreamTestCase extends OutputStreamAbstractTestC
return new FastByteArrayOutputStream(256);
}
@Test
public void testCreateInputStream() throws IOException {
FastByteArrayOutputStream out = makeObject();
@@ -11,10 +11,6 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileCacheSeekableStreamTestCase.java#3 $
*/
public class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public FileCacheSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) {
try {
return new FileCacheSeekableStream(pStream);
@@ -1,7 +1,11 @@
package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.*;
import static org.junit.Assert.*;
/**
* MemoryCacheSeekableStreamTestCase
* <p/>
@@ -10,10 +14,6 @@ import java.io.*;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $
*/
public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public FileSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) {
try {
return new FileSeekableStream(createFileWithContent(pStream));
@@ -37,11 +37,13 @@ public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestC
return temp;
}
@Test
@Override
public void testCloseUnderlyingStream() throws IOException {
// There is no underlying stream here...
}
@Test
public void testCloseUnderlyingFile() throws IOException {
final boolean[] closed = new boolean[1];
@@ -17,12 +17,15 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
import static org.junit.Assert.*;
/**
* InputStreamAbstractTestCase
* <p/>
@@ -38,10 +41,6 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
final static private long SEED = 29487982745l;
final static Random sRandom = new Random(SEED);
public InputStreamAbstractTestCase(String name) {
super(name);
}
protected final Object makeObject() {
return makeInputStream();
}
@@ -71,11 +70,12 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
return bytes;
}
@Test
public void testRead() throws Exception {
int size = 5;
InputStream input = makeInputStream(makeOrderedArray(size));
for (int i = 0; i < size; i++) {
assertEquals("Check Size [" + i + "]", (size - i), input.available());
assertTrue("Check Size [" + i + "]", (size - i) >= input.available());
assertEquals("Check Value [" + i + "]", i, input.read());
}
assertEquals("Available after contents all read", 0, input.available());
@@ -90,6 +90,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testAvailable() throws Exception {
InputStream input = makeInputStream(1);
assertFalse("Unexpected EOF", input.read() < 0);
@@ -100,6 +101,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Available after End of File", 0, input.available());
}
@Test
public void testReadByteArray() throws Exception {
byte[] bytes = new byte[10];
byte[] data = makeOrderedArray(15);
@@ -145,6 +147,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testEOF() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(2));
assertEquals("Read 1", 0, input.read());
@@ -154,6 +157,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Read 5", -1, input.read());
}
@Test
public void testMarkResetUnsupported() throws IOException {
InputStream input = makeInputStream(10);
if (input.markSupported()) {
@@ -163,7 +167,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
input.mark(100); // Should be a no-op
int read = input.read();
assertEquals(0, read);
assertTrue(read >= 0);
// TODO: According to InputStream#reset, it is allowed to do some
// implementation specific reset, and still be correct...
@@ -176,6 +180,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testResetNoMark() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(10));
@@ -196,6 +201,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testMarkReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25));
@@ -226,6 +232,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testResetAfterReadLimit() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25));
@@ -257,6 +264,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testResetAfterReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25));
@@ -264,7 +272,8 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
return; // Not supported, skip test
}
assertTrue("Expected to read positive value", input.read() >= 0);
int first = input.read();
assertTrue("Expected to read positive value", first >= 0);
int readlimit = 5;
@@ -273,19 +282,24 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
int read = input.read();
assertTrue("Expected to read positive value", read >= 0);
input.reset();
assertEquals("Expected value read differes from actual", read, input.read());
assertTrue(input.read() >= 0);
assertTrue(input.read() >= 0);
// Reset after read limit passed, may either throw exception, or reset to last mark
input.reset();
assertEquals("Expected value read differs from actual", read, input.read());
// Reset after read limit passed, may either throw exception, or reset to last good mark
try {
input.reset();
assertEquals("Re-read of reset data should be same", read, input.read());
int reRead = input.read();
assertTrue("Re-read of reset data should be same as initially marked or first", reRead == read || reRead == first);
}
catch (Exception e) {
assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark"));
}
}
@Test
public void testSkip() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(10));
@@ -302,6 +316,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
assertEquals("Unexpected value read after EOF", -1, input.read());
}
@Test
public void testSanityOrdered() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = makeOrderedArray(25);
@@ -314,6 +329,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testSanityOrdered2() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = makeOrderedArray(25);
@@ -332,6 +348,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testSanityNegative() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = new byte[25];
@@ -347,6 +364,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testSanityNegative2() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = new byte[25];
@@ -368,6 +386,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testSanityRandom() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = makeRandomArray(25);
@@ -380,6 +399,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
}
}
@Test
public void testSanityRandom2() throws IOException {
// This is to sanity check that the test itself is correct...
byte[] bytes = makeRandomArray(25);
@@ -0,0 +1,208 @@
/*
* 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.io;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.junit.Assert.*;
/**
* LittleEndianDataInputStreamTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: LittleEndianDataInputStreamTest.java,v 1.0 15.02.13 11:04 haraldk Exp$
*/
public class LittleEndianDataInputStreamTest {
@Test
public void testReadBoolean() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(new byte[] {0, 1, 0x7f, (byte) 0xff}));
assertFalse(data.readBoolean());
assertTrue(data.readBoolean());
assertTrue(data.readBoolean());
assertTrue(data.readBoolean());
}
@Test
public void testReadByte() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00,
(byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x01,
}
));
assertEquals(0, data.readByte());
assertEquals(0, data.readByte());
assertEquals(1, data.readByte());
assertEquals(0, data.readByte());
assertEquals(-1, data.readByte());
assertEquals(-1, data.readByte());
assertEquals(0, data.readByte());
assertEquals(Byte.MIN_VALUE, data.readByte());
assertEquals(-1, data.readByte());
assertEquals(Byte.MAX_VALUE, data.readByte());
assertEquals(0, data.readByte());
assertEquals(1, data.readByte());
}
@Test
public void testReadUnsignedByte() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00,
(byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x01,
}
));
assertEquals(0, data.readUnsignedByte());
assertEquals(0, data.readUnsignedByte());
assertEquals(1, data.readUnsignedByte());
assertEquals(0, data.readUnsignedByte());
assertEquals(255, data.readUnsignedByte());
assertEquals(255, data.readUnsignedByte());
assertEquals(0, data.readUnsignedByte());
assertEquals(128, data.readUnsignedByte());
assertEquals(255, data.readUnsignedByte());
assertEquals(Byte.MAX_VALUE, data.readUnsignedByte());
assertEquals(0, data.readUnsignedByte());
assertEquals(1, data.readUnsignedByte());
}
@Test
public void testReadShort() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00,
(byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x01,
}
));
assertEquals(0, data.readShort());
assertEquals(1, data.readShort());
assertEquals(-1, data.readShort());
assertEquals(Short.MIN_VALUE, data.readShort());
assertEquals(Short.MAX_VALUE, data.readShort());
assertEquals(256, data.readShort());
}
@Test
public void testReadUnsignedShort() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00,
(byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x01,
}
));
assertEquals(0, data.readUnsignedShort());
assertEquals(1, data.readUnsignedShort());
assertEquals(Short.MAX_VALUE * 2 + 1, data.readUnsignedShort());
assertEquals(Short.MAX_VALUE + 1, data.readUnsignedShort());
assertEquals(Short.MAX_VALUE, data.readUnsignedShort());
assertEquals(256, data.readUnsignedShort());
}
@Test
public void testReadInt() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
(byte) 0xff, (byte) 0x00, (byte) 0xff, (byte) 0x00,
(byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0xff,
(byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
(byte) 0xca, (byte) 0xfe, (byte) 0xd0, (byte) 0x0d,
}
));
assertEquals(0, data.readInt());
assertEquals(1, data.readInt());
assertEquals(-1, data.readInt());
assertEquals(Integer.MIN_VALUE, data.readInt());
assertEquals(Integer.MAX_VALUE, data.readInt());
assertEquals(16777216, data.readInt());
assertEquals(0xff00ff, data.readInt());
assertEquals(0xff00ff00, data.readInt());
assertEquals(0xCafeBabe, data.readInt());
assertEquals(0x0dd0feca, data.readInt());
}
@Test
public void testReadLong() throws IOException {
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
(byte) 0x0d, (byte) 0xd0, (byte) 0xfe, (byte) 0xca, (byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
}
));
assertEquals(0, data.readLong());
assertEquals(1, data.readLong());
assertEquals(-1, data.readLong());
assertEquals(Long.MIN_VALUE, data.readLong());
assertEquals(Long.MAX_VALUE, data.readLong());
assertEquals(72057594037927936L, data.readLong());
assertEquals(0xCafeBabeL << 32 | 0xCafeD00dL, data.readLong());
}
}
@@ -10,10 +10,6 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/MemoryCacheSeekableStreamTestCase.java#2 $
*/
public class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
public MemoryCacheSeekableStreamTestCase(String name) {
super(name);
}
protected SeekableInputStream makeInputStream(final InputStream pStream) {
return new MemoryCacheSeekableStream(pStream);
}
@@ -1,10 +1,13 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.OutputStream;
import java.io.IOException;
import static org.junit.Assert.*;
/**
* InputStreamAbstractTestCase
* <p/>
@@ -15,6 +18,7 @@ import java.io.IOException;
public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase {
protected abstract OutputStream makeObject();
@Test
public void testWrite() throws IOException {
OutputStream os = makeObject();
@@ -23,12 +27,14 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testWriteByteArray() throws IOException {
OutputStream os = makeObject();
os.write(new byte[256]);
}
@Test
public void testWriteByteArrayNull() {
OutputStream os = makeObject();
try {
@@ -46,7 +52,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
public void testWriteByteArrayOffsetLenght() throws IOException {
@Test
public void testWriteByteArrayOffsetLength() throws IOException {
byte[] input = new byte[256];
OutputStream os = makeObject();
@@ -65,7 +72,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
public void testWriteByteArrayZeroLenght() {
@Test
public void testWriteByteArrayZeroLength() {
OutputStream os = makeObject();
try {
os.write(new byte[1], 0, 0);
@@ -75,7 +83,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
public void testWriteByteArrayOffsetLenghtNull() {
@Test
public void testWriteByteArrayOffsetLengthNull() {
OutputStream os = makeObject();
try {
os.write(null, 5, 10);
@@ -92,6 +101,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testWriteByteArrayNegativeOffset() {
OutputStream os = makeObject();
try {
@@ -109,6 +119,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testWriteByteArrayNegativeLength() {
OutputStream os = makeObject();
try {
@@ -126,6 +137,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testWriteByteArrayOffsetOutOfBounds() {
OutputStream os = makeObject();
try {
@@ -143,6 +155,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testWriteByteArrayLengthOutOfBounds() {
OutputStream os = makeObject();
try {
@@ -160,14 +173,17 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testFlush() {
// TODO: Implement
}
@Test
public void testClose() {
// TODO: Implement
}
@Test
public void testWriteAfterClose() throws IOException {
OutputStream os = makeObject();
@@ -200,6 +216,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testFlushAfterClose() throws IOException {
OutputStream os = makeObject();
@@ -221,6 +238,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
}
}
@Test
public void testCloseAfterClose() throws IOException {
OutputStream os = makeObject();
@@ -1,10 +1,13 @@
package com.twelvemonkeys.io;
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import org.junit.Test;
import java.io.Reader;
import java.io.IOException;
import static org.junit.Assert.*;
/**
* ReaderAbstractTestCase
* <p/>
@@ -36,6 +39,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
protected abstract Reader makeReader(String pInput);
@Test
public void testRead() throws IOException {
Reader reader = makeReader();
@@ -51,6 +55,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(mInput, buffer.toString());
}
@Test
public void testReadBuffer() throws IOException {
Reader reader = makeReader();
@@ -70,6 +75,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(mInput, new String(chars));
}
@Test
public void testSkipToEnd() throws IOException {
Reader reader = makeReader();
@@ -83,6 +89,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
assertEquals(0, toSkip);
}
@Test
public void testSkipToEndAndRead() throws IOException {
Reader reader = makeReader();
@@ -95,6 +102,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
}
// TODO: It's possible to support reset and not mark (resets to beginning of stream, for example)
@Test
public void testResetMarkSupported() throws IOException {
Reader reader = makeReader();
@@ -154,6 +162,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
}
}
@Test
public void testResetMarkNotSupported() throws IOException {
Reader reader = makeReader();
@@ -198,7 +207,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
}
}
@Test
public void testReadAfterClose() throws IOException {
Reader reader = makeReader("foo bar");
@@ -1,6 +1,8 @@
package com.twelvemonkeys.io;
import junit.framework.TestCase;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* SeekableAbstractTestCase
@@ -9,14 +11,16 @@ import junit.framework.TestCase;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $
*/
public abstract class SeekableAbstractTestCase extends TestCase implements SeekableInterfaceTest {
public abstract class SeekableAbstractTestCase implements SeekableInterfaceTest {
protected abstract Seekable createSeekable();
@Test
public void testFail() {
fail();
fail("Do not create stand-alone test classes based on this class. Instead, create an inner class and delegate to it.");
}
@Test
public void testSeekable() {
assertTrue(createSeekable() instanceof Seekable);
}
@@ -1,10 +1,14 @@
package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.*;
/**
* SeekableInputStreamAbstractTestCase
* <p/>
@@ -13,13 +17,8 @@ import java.io.InputStream;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $
*/
public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest {
public SeekableInputStreamAbstractTestCase(String name) {
super(name);
}
//// TODO: Figure out a better way of creating interface tests without duplicating code
final SeekableAbstractTestCase mSeekableTestCase = new SeekableAbstractTestCase() {
final SeekableAbstractTestCase seekableTestCase = new SeekableAbstractTestCase() {
protected Seekable createSeekable() {
return makeInputStream();
}
@@ -41,6 +40,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
protected abstract SeekableInputStream makeInputStream(InputStream pStream);
@Test
@Override
public void testResetAfterReset() throws Exception {
InputStream input = makeInputStream(makeOrderedArray(25));
@@ -59,9 +59,9 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertTrue("Expected to read positive value", read >= 0);
input.reset();
assertEquals("Expected value read differes from actual", read, input.read());
assertEquals("Expected value read differs from actual", read, input.read());
// Reset after read limit passed, may either throw exception, or reset to last mark
// Reset after read limit passed, may either throw exception, or reset to last good mark
try {
input.reset();
assertEquals("Re-read of reset data should be first", 0, input.read());
@@ -71,10 +71,12 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
}
@Test
public void testSeekable() {
mSeekableTestCase.testSeekable();
seekableTestCase.testSeekable();
}
@Test
public void testFlushBeyondCurrentPos() throws Exception {
SeekableInputStream seekable = makeInputStream(20);
@@ -88,6 +90,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
}
@Test
public void testSeek() throws Exception {
SeekableInputStream seekable = makeInputStream(55);
int pos = 37;
@@ -97,6 +100,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertEquals("Stream positon should match seeked position", pos, streamPos);
}
@Test
public void testSeekFlush() throws Exception {
SeekableInputStream seekable = makeInputStream(133);
int pos = 45;
@@ -114,6 +118,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
}
@Test
public void testMarkFlushReset() throws Exception {
SeekableInputStream seekable = makeInputStream(77);
@@ -134,6 +139,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertEquals(position, seekable.getStreamPosition());
}
@Test
public void testSeekSkipRead() throws Exception {
SeekableInputStream seekable = makeInputStream(133);
int pos = 45;
@@ -147,7 +153,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
}
public void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException {
protected void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException {
System.out.println();
pSeekable.seek(pStr.length());
FileUtil.read(pSeekable);
@@ -330,6 +336,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
*/
@Test
public void testReadResetReadDirectBufferBug() throws IOException {
// Make sure we use the exact size of the buffer
final int size = 1024;
@@ -365,6 +372,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
assertTrue(rangeEquals(bytes, size, result, 0, size));
}
@Test
public void testReadAllByteValuesRegression() throws IOException {
final int size = 128;
@@ -401,6 +409,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
}
@Test
public void testCloseUnderlyingStream() throws IOException {
final boolean[] closed = new boolean[1];
@@ -476,5 +485,4 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
return true;
}
}

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