mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-21 00:00:01 -04:00
Compare commits
495 Commits
2.2
..
3.0-bugfix
| Author | SHA1 | Date | |
|---|---|---|---|
| c278f5f1f2 | |||
| 4c276df63d | |||
| 98616e9c9e | |||
| d8dedc86fc | |||
| fe9f345d7b | |||
| 31c3fdc0fd | |||
| 217d14095b | |||
| 1ee21061bd | |||
| 00b15746e4 | |||
| a42ccd031b | |||
| b4fde6ff17 | |||
| b275d7f777 | |||
| c544db9882 | |||
| 9da706dfbb | |||
| 977ecb0482 | |||
| 8143c957bf | |||
| a5d703b29b | |||
| b7265a5117 | |||
| a1769cd40b | |||
| d054ee9bb1 | |||
| 845944b25f | |||
| d12f9bdbbd | |||
| 3e9820bb22 | |||
| 2098e6a898 | |||
| feb20eefdd | |||
| 6977e52059 | |||
| 8299182f52 | |||
| 1ff764997b | |||
| f914d15677 | |||
| f32110c5b8 | |||
| e79f115ab4 | |||
| e956fedfcf | |||
| 61cbeb0a09 | |||
| 7634ca1261 | |||
| a67c0fb456 | |||
| 8c93be05a5 | |||
| afff6f78a8 | |||
| 3e0440b9f4 | |||
| fb9d9de6b0 | |||
| 2fb9a54618 | |||
| 06674d1273 | |||
| 7e88a6f7e3 | |||
| dd54793d3d | |||
| 96eb0d0b7f | |||
| 9e9f47a2fb | |||
| 9a6f4bba33 | |||
| d310b7c8e7 | |||
| 64668807e0 | |||
| 7430d0053a | |||
| a34a0e3f44 | |||
| c048928011 | |||
| f4ba4e081e | |||
| 9bb7b62987 | |||
| 552ab24658 | |||
| 52aa7e974b | |||
| cffc3af45c | |||
| 5952634671 | |||
| c3c23d0523 | |||
| aacad8a575 | |||
| 83a6d604a6 | |||
| e3bab84e82 | |||
| d607450ae4 | |||
| 037a47ca2a | |||
| 5c735674f0 | |||
| 822bea80b6 | |||
| e924fcefc0 | |||
| 0ab9294004 | |||
| ed5a5e0dca | |||
| ad68bb88a4 | |||
| ff786c2315 | |||
| a26f8e5851 | |||
| b49fd7b653 | |||
| 9fa1d97389 | |||
| 7c012323e5 | |||
| 83403c67f5 | |||
| db259bff10 | |||
| 1e42cf1499 | |||
| bb4e77406a | |||
| b9f04059bf | |||
| 14e12eb2c1 | |||
| 0ded2f211a | |||
| ed11259e58 | |||
| 5779a40a32 | |||
| 90c7ac7d98 | |||
| ecc08daa2d | |||
| fc99abb4b4 | |||
| 63d9029a3e | |||
| af245a80d9 | |||
| 9cf47aca98 | |||
| a1f9e979b9 | |||
| aafdb31a8c | |||
| ce87171026 | |||
| 55a373b0ff | |||
| 791e1b2d56 | |||
| cc5f763503 | |||
| d261105c6b | |||
| 9a02e90ab9 | |||
| d6f5a1281c | |||
| 534f964868 | |||
| cff4d836d1 | |||
| 2d42b58814 | |||
| aa0a0f96e9 | |||
| c7ecd7afc8 | |||
| 39d3fc426e | |||
| f08fbd0e21 | |||
| b5f2f9dbb8 | |||
| dd2327e70e | |||
| 371aa4298b | |||
| 1af9a0c48c | |||
| b34770658a | |||
| a11f007005 | |||
| ac9fa338b9 | |||
| 348ced52be | |||
| 9849cdb001 | |||
| 38fa2189bc | |||
| db0f8901dc | |||
| 18448b6a43 | |||
| c491c8a518 | |||
| f5a4fe03f4 | |||
| d04c29ae12 | |||
| 51f0b20bb0 | |||
| a5e6346647 | |||
| 15cb7dd71b | |||
| 3be5c1713b | |||
| 8aff3faa09 | |||
| 973fe9fa37 | |||
| 7527f2cdc6 | |||
| a36eb0cd5d | |||
| 8f33f906fb | |||
| 22d7ce80b1 | |||
| 3bd8900cbe | |||
| cc44e73d7d | |||
| c3ee44992a | |||
| e2d56659ca | |||
| 5189c7c1e7 | |||
| 9cab7903ed | |||
| dc31322518 | |||
| cb82b39e6a | |||
| 5064d08dd6 | |||
| 7e14f0b37c | |||
| ef13030cc7 | |||
| f83ca01e8f | |||
| 5508137c5c | |||
| ae58b859e4 | |||
| bf1aae6652 | |||
| 86921ad389 | |||
| d7958fc8a7 | |||
| ca48837e11 | |||
| b14363da3b | |||
| c8061eb0c4 | |||
| 1acc04eeaf | |||
| cd197afc04 | |||
| 086357694a | |||
| 602e5ec34b | |||
| aebfad914f | |||
| d1f00ce817 | |||
| cd6e9ebbf5 | |||
| 0ff99afe6d | |||
| 47425e2ca0 | |||
| 2116feb49f | |||
| cdc832623a | |||
| 5531c863cf | |||
| dc63fac8ef | |||
| 10b95b225f | |||
| 23ae6a966a | |||
| 55bd82491f | |||
| 13c8cd7f93 | |||
| 2f69847b23 | |||
| c5f1d8101b | |||
| 4c18c2a685 | |||
| 55b161b115 | |||
| f2ff00580a | |||
| 9a27f62dec | |||
| 4de927b657 | |||
| de81723912 | |||
| 9b81db5e32 | |||
| 0860db2166 | |||
| 37b223c29b | |||
| 2433075578 | |||
| 6ce9543c00 | |||
| ff3fbc8bd2 | |||
| cdce0aebff | |||
| f5b5e818c5 | |||
| 33ffc14e3f | |||
| a2effd7ba0 | |||
| 544d60dabb | |||
| f8c40a3748 | |||
| 28e2f3c21b | |||
| 2cf1c6e43b | |||
| e72988aa7b | |||
| 0e628f6e4c | |||
| 1d5cc6d266 | |||
| 28d8796e54 | |||
| 0ffd7cacc4 | |||
| b966254322 | |||
| 61e01e3316 | |||
| 09444ab083 | |||
| b97d95cca7 | |||
| 1ffe694538 | |||
| dd0f382d3c | |||
| 0319a6f84c | |||
| df9f5734bd | |||
| 59e5c3b3fd | |||
| 2764460db5 | |||
| c9809d0fa1 | |||
| bb7e1a4258 | |||
| cc604e650b | |||
| a0d4973d7f | |||
| d8867736b7 | |||
| ed6223fcab | |||
| f8369fb5b6 | |||
| 94db6b4a6f | |||
| 10f501e919 | |||
| 9c8ad3cb74 | |||
| 42831ea65b | |||
| 1548523336 | |||
| b3672be1d4 | |||
| 3b15653a10 | |||
| 46b53a824c | |||
| 02063c809e | |||
| 8b9d5c7abc | |||
| c394f8a4bc | |||
| fcd15a9e36 | |||
| 41a08761ba | |||
| b834a32b01 | |||
| 00f47e81a4 | |||
| f666610184 | |||
| 47fbf473db | |||
| 8c4f9d3ed6 | |||
| e68b3aa9e3 | |||
| dd849aeea6 | |||
| 59b91918e0 | |||
| 7846f497af | |||
| 6c082353d6 | |||
| 92690e1644 | |||
| 9ef8ac9930 | |||
| 7260c5baea | |||
| 381e229575 | |||
| 80d2f4ad89 | |||
| b0c2b4886f | |||
| 14869fb591 | |||
| f7b7b91fba | |||
| 5c9a3e8e58 | |||
| 2cbdd7fd82 | |||
| 5bac1e3a2b | |||
| 0d83ab5483 | |||
| 0aad4cb77a | |||
| 73a880a358 | |||
| c2245a503d | |||
| 75c09d3aef | |||
| 4db12d313b | |||
| 3095422a44 | |||
| d84acbf4b3 | |||
| c7f6dedaa7 | |||
| 37e9adcfec | |||
| 7f2ad765cf | |||
| 289be6ca12 | |||
| b8ff4af178 | |||
| dd7be5ef11 | |||
| 98361194ea | |||
| 9492ed67f1 | |||
| a4dfb7a009 | |||
| aaef2e4fad | |||
| 241c1882f4 | |||
| ae87726974 | |||
| b9a1c5c2f4 | |||
| 7bcfd228b9 | |||
| 465eb2ecb3 | |||
| 0bdb68ea6f | |||
| c16ffaca13 | |||
| 24db7e847c | |||
| 927723a472 | |||
| 2f07329296 | |||
| 08b5891298 | |||
| f940fed152 | |||
| da9b94bdf3 | |||
| bf4ad6265a | |||
| e95cf300ba | |||
| 13a4646ae4 | |||
| 36a05272a5 | |||
| a99c337348 | |||
| 93e57306d5 | |||
| 0307237852 | |||
| 7431065519 | |||
| de34ac7ede | |||
| 4463a00667 | |||
| 926359d9d2 | |||
| e712df3862 | |||
| 6430841dcc | |||
| f7bc246bad | |||
| 07a5c62a28 | |||
| 9cb21dbfc9 | |||
| 19ed19633c | |||
| 897da0ebca | |||
| ff3d578806 | |||
| 7904fefcd4 | |||
| e3dcca854b | |||
| 11f9b2bdf8 | |||
| 39dafd48ca | |||
| 3efae7cfba | |||
| c3524adbbc | |||
| f2e3f7ed03 | |||
| 1830808d56 | |||
| cda19ece0d | |||
| ed441a7d6a | |||
| 6c6c08a8f5 | |||
| b92caf121d | |||
| d36d828110 | |||
| c19338b5b9 | |||
| 3f381a9c4c | |||
| 84a2e8b10c | |||
| 529377aa01 | |||
| 280407d9c0 | |||
| 6ba32b657a | |||
| 7435c12a80 | |||
| 7867aeae76 | |||
| 5d6097baef | |||
| db526e07ec | |||
| 8f452930ac | |||
| 49f5ab8e64 | |||
| 0c4fc454b9 | |||
| 52a97cfb2f | |||
| 0b23d9f6c2 | |||
| 73fc08f8c1 | |||
| 5d3fb34e49 | |||
| 2a282cf8e4 | |||
| d1e72d1ece | |||
| f130e654ef | |||
| c006f22ac2 | |||
| 905a3da97b | |||
| 158504de5d | |||
| dc6b8d3035 | |||
| 9742af9a5d | |||
| 3a9ad582f2 | |||
| 5782c8c824 | |||
| b5fd17ba24 | |||
| 093fe2924b | |||
| e867c2125c | |||
| 17f3b97699 | |||
| 8dcfb46bdb | |||
| 38b197f6c1 | |||
| 8edc448bf9 | |||
| 5857e27cf2 | |||
| 7ddc2c991e | |||
| fe25b48804 | |||
| 18abfcdbc2 | |||
| 7546a9d2ab | |||
| 092474830d | |||
| 7f0395e76b | |||
| 0e11d6e2ae | |||
| cee29fb6a1 | |||
| 9cafe4d9a9 | |||
| 4b77d1c22a | |||
| 1ba271af9d | |||
| 3e5da06e80 | |||
| 7e8662772c | |||
| fbb51f0387 | |||
| a4d4111195 | |||
| cb149a7c79 | |||
| cb4a35016b | |||
| c6d6a86343 | |||
| 521c4e4bbc | |||
| b5b1b4f422 | |||
| 3f98534011 | |||
| 63b5ae9994 | |||
| b45f2ac09f | |||
| 65ee6771ca | |||
| af7d5fa94a | |||
| e75741ccd3 | |||
| 2abee4653b | |||
| 5545a08854 | |||
| 2d04b8d484 | |||
| 38ab0d936a | |||
| 720752149e | |||
| d1943f9f49 | |||
| 403dff946b | |||
| a014698a45 | |||
| 6cc97e3721 | |||
| ba4ff3dc45 | |||
| c60f80aacb | |||
| dba1fa20da | |||
| 26128bf7ea | |||
| c438036ee2 | |||
| f1a4a79003 | |||
| dda46b0ab9 | |||
| 32a8fe497a | |||
| 5accfc9cf0 | |||
| 87118aecee | |||
| 770f948e1a | |||
| 5bd896f80f | |||
| 1433a24052 | |||
| 066b902a5e | |||
| 20b87d155d | |||
| 41b8080683 | |||
| 43cc440e67 | |||
| 191643a36c | |||
| df0d3f90e8 | |||
| 47ab16457a | |||
| 5c6c9e3e26 | |||
| d772674223 | |||
| 4d9661f950 | |||
| 8d5b775851 | |||
| f610c82cb7 | |||
| f419cfecdd | |||
| 9d6f263b86 | |||
| 7d4d007975 | |||
| c982203361 | |||
| 724d893ea6 | |||
| b6ee5ce450 | |||
| 1f60b62626 | |||
| 823854d40e | |||
| e468484d68 | |||
| 037c0d078a | |||
| b643324ddb | |||
| f6bb0aeb1b | |||
| f3218d5819 | |||
| df27411c82 | |||
| 67e623f666 | |||
| 779f720aa0 | |||
| 7ea9b8f537 | |||
| 3050899903 | |||
| abdecf1d5e | |||
| 673527abe5 | |||
| d2742b36de | |||
| 3d4d60c664 | |||
| 1c58bdbb81 | |||
| 4b7a17ba30 | |||
| 830035bcec | |||
| 41853cec7e | |||
| 5b7fcd5c95 | |||
| ee99550a65 | |||
| 40e6486154 | |||
| 38d6c58eba | |||
| c310083d2a | |||
| 261c6f8038 | |||
| ef09b599f6 | |||
| 20d6e35640 | |||
| d97a0cc00b | |||
| c7fd5b3dd9 | |||
| 64b21b83bb | |||
| d24c2c1b08 | |||
| aad80d043f | |||
| ec4334cbb5 | |||
| 27553dc47a | |||
| e8a4cc048c | |||
| f8b716687c | |||
| effd80d42f | |||
| 0786949c1c | |||
| b8faa6e36f | |||
| 7167a7a4ad | |||
| e0a6c0a2bd | |||
| 6eaac4ec8b | |||
| b3aa378f16 | |||
| ad913b5093 | |||
| 9b615de8ed | |||
| ba5f0a2f5f | |||
| 2016be8f9a | |||
| 45a42ea8a3 | |||
| 2ab9cbadee | |||
| d1cc3deec1 | |||
| b7124585c5 | |||
| 69d77071b9 | |||
| ceca94135b | |||
| dbca2fc099 | |||
| 54cf727dee | |||
| 0d41db32cf | |||
| b5f6c96583 | |||
| 34d874d69d | |||
| bf5c6e9d47 | |||
| def1d47344 | |||
| 5dab7eb1ff | |||
| 9849aeb2a7 | |||
| 0a71af5405 | |||
| 96a74e0b81 | |||
| c801926a02 | |||
| ebc365528a | |||
| 317ed16814 | |||
| ecc79e0478 | |||
| 8572633686 | |||
| 67b985bc1d | |||
| 9c443f28e3 | |||
| 46ab06f471 | |||
| 18b86f8d26 | |||
| 82fdde897d | |||
| f49a487c88 | |||
| fb2c555d21 | |||
| 4bd0763d48 | |||
| 4674b9e344 | |||
| 68b30413ba | |||
| ebd9153e40 | |||
| 669f575585 | |||
| d04d4a9a97 | |||
| 17e8de8c99 | |||
| ef7029f306 | |||
| 30b97483bd |
@@ -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
@@ -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>
|
|
||||||
@@ -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>
|
||||||
+19
-15
@@ -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 $
|
* @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 {
|
public abstract class AbstractImageSource implements ImageProducer {
|
||||||
private List<ImageConsumer> mConsumers = new ArrayList<ImageConsumer>();
|
private List<ImageConsumer> consumers = new ArrayList<ImageConsumer>();
|
||||||
protected int mWidth;
|
protected int width;
|
||||||
protected int mHeight;
|
protected int height;
|
||||||
protected int mXOff;
|
protected int xOff;
|
||||||
protected int mYOff;
|
protected int yOff;
|
||||||
|
|
||||||
// ImageProducer interface
|
// ImageProducer interface
|
||||||
public void addConsumer(ImageConsumer pConsumer) {
|
public void addConsumer(final ImageConsumer pConsumer) {
|
||||||
if (mConsumers.contains(pConsumer)) {
|
if (consumers.contains(pConsumer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mConsumers.add(pConsumer);
|
|
||||||
|
consumers.add(pConsumer);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initConsumer(pConsumer);
|
initConsumer(pConsumer);
|
||||||
sendPixels(pConsumer);
|
sendPixels(pConsumer);
|
||||||
|
|
||||||
if (isConsumer(pConsumer)) {
|
if (isConsumer(pConsumer)) {
|
||||||
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
|
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
|
||||||
|
|
||||||
@@ -68,34 +71,35 @@ public abstract class AbstractImageSource implements ImageProducer {
|
|||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
if (isConsumer(pConsumer)) {
|
if (isConsumer(pConsumer)) {
|
||||||
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
|
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeConsumer(ImageConsumer pConsumer) {
|
public void removeConsumer(final ImageConsumer pConsumer) {
|
||||||
mConsumers.remove(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.
|
* not in TDLR order by default, subclasses must override this method.
|
||||||
*
|
*
|
||||||
* @param pConsumer the consumer that requested the resend
|
* @param pConsumer the consumer that requested the resend
|
||||||
*
|
*
|
||||||
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
|
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
|
||||||
*/
|
*/
|
||||||
public void requestTopDownLeftRightResend(ImageConsumer pConsumer) {
|
public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startProduction(ImageConsumer pConsumer) {
|
public void startProduction(final ImageConsumer pConsumer) {
|
||||||
addConsumer(pConsumer);
|
addConsumer(pConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isConsumer(ImageConsumer pConsumer) {
|
public boolean isConsumer(final ImageConsumer pConsumer) {
|
||||||
return mConsumers.contains(pConsumer);
|
return consumers.contains(pConsumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void initConsumer(ImageConsumer pConsumer);
|
protected abstract void initConsumer(ImageConsumer pConsumer);
|
||||||
+34
-33
@@ -47,33 +47,34 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||||
|
|
||||||
final private int mWidth;
|
final private int width;
|
||||||
final private int mHeight;
|
final private int height;
|
||||||
|
|
||||||
private Rectangle mSourceRegion;
|
private Rectangle sourceRegion;
|
||||||
|
|
||||||
public AreaAverageOp(final int pWidth, final int pHeight) {
|
public AreaAverageOp(final int pWidth, final int pHeight) {
|
||||||
mWidth = pWidth;
|
width = pWidth;
|
||||||
mHeight = pHeight;
|
height = pHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle getSourceRegion() {
|
public Rectangle getSourceRegion() {
|
||||||
if (mSourceRegion == null) {
|
if (sourceRegion == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Rectangle(mSourceRegion);
|
|
||||||
|
return new Rectangle(sourceRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||||
if (pSourceRegion == null) {
|
if (pSourceRegion == null) {
|
||||||
mSourceRegion = null;
|
sourceRegion = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (mSourceRegion == null) {
|
if (sourceRegion == null) {
|
||||||
mSourceRegion = new Rectangle(pSourceRegion);
|
sourceRegion = new Rectangle(pSourceRegion);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mSourceRegion.setBounds(pSourceRegion);
|
sourceRegion.setBounds(pSourceRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
// Straight-forward version
|
// 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);
|
//ImageUtil.drawOnto(result, scaled);
|
||||||
//result = new BufferedImageFactory(scaled).getBufferedImage();
|
//result = new BufferedImageFactory(scaled).getBufferedImage();
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
AffineTransform xform = null;
|
AffineTransform xform = null;
|
||||||
int w = src.getWidth();
|
int w = src.getWidth();
|
||||||
int h = src.getHeight();
|
int h = src.getHeight();
|
||||||
while (w / 2 > mWidth && h / 2 > mHeight) {
|
while (w / 2 > width && h / 2 > height) {
|
||||||
w /= 2;
|
w /= 2;
|
||||||
h /= 2;
|
h /= 2;
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
src = temp.getSubimage(0, 0, w, h);
|
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
|
// The real version
|
||||||
@@ -160,11 +161,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||||
//System.out.println("src: " + src);
|
//System.out.println("src: " + src);
|
||||||
//System.out.println("dest: " + dest);
|
//System.out.println("dest: " + dest);
|
||||||
if (mSourceRegion != null) {
|
if (sourceRegion != null) {
|
||||||
int cx = mSourceRegion.x;
|
int cx = sourceRegion.x;
|
||||||
int cy = mSourceRegion.y;
|
int cy = sourceRegion.y;
|
||||||
int cw = mSourceRegion.width;
|
int cw = sourceRegion.width;
|
||||||
int ch = mSourceRegion.height;
|
int ch = sourceRegion.height;
|
||||||
|
|
||||||
boolean same = src == dest;
|
boolean same = src == dest;
|
||||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
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..
|
// TODO: This don't work too well..
|
||||||
// The thing is that the step length and the scan length will vary, for
|
// The thing is that the step length and the scan length will vary, for
|
||||||
// non-even (1/2, 1/4, 1/8 etc) resampling
|
// non-even (1/2, 1/4, 1/8 etc) resampling
|
||||||
int widthSteps = (width + mWidth - 1) / mWidth;
|
int widthSteps = (width + this.width - 1) / this.width;
|
||||||
int heightSteps = (height + mHeight - 1) / mHeight;
|
int heightSteps = (height + this.height - 1) / this.height;
|
||||||
|
|
||||||
final boolean oddX = width % mWidth != 0;
|
final boolean oddX = width % this.width != 0;
|
||||||
final boolean oddY = height % mHeight != 0;
|
final boolean oddY = height % this.height != 0;
|
||||||
|
|
||||||
final int dataElements = src.getNumDataElements();
|
final int dataElements = src.getNumDataElements();
|
||||||
final int bands = src.getNumBands();
|
final int bands = src.getNumBands();
|
||||||
@@ -210,16 +211,16 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < mHeight; y++) {
|
for (int y = 0; y < this.height; y++) {
|
||||||
if (!oddY || y < mHeight) {
|
if (!oddY || y < this.height) {
|
||||||
scanH = heightSteps;
|
scanH = heightSteps;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
scanH = height - (y * heightSteps);
|
scanH = height - (y * heightSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int x = 0; x < mWidth; x++) {
|
for (int x = 0; x < this.width; x++) {
|
||||||
if (!oddX || x < mWidth) {
|
if (!oddX || x < this.width) {
|
||||||
scanW = widthSteps;
|
scanW = widthSteps;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -243,8 +244,8 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
//
|
//
|
||||||
//System.err.println("width: " + width);
|
//System.err.println("width: " + width);
|
||||||
//System.err.println("height: " + height);
|
//System.err.println("height: " + height);
|
||||||
//System.err.println("mWidth: " + mWidth);
|
//System.err.println("width: " + width);
|
||||||
//System.err.println("mHeight: " + mHeight);
|
//System.err.println("height: " + height);
|
||||||
//
|
//
|
||||||
//e.printStackTrace();
|
//e.printStackTrace();
|
||||||
continue;
|
continue;
|
||||||
@@ -382,20 +383,20 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
|||||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||||
ColorModel cm = destCM != null ? destCM : src.getColorModel();
|
ColorModel cm = destCM != null ? destCM : src.getColorModel();
|
||||||
return new BufferedImage(cm,
|
return new BufferedImage(cm,
|
||||||
ImageUtil.createCompatibleWritableRaster(src, cm, mWidth, mHeight),
|
ImageUtil.createCompatibleWritableRaster(src, cm, width, height),
|
||||||
cm.isAlphaPremultiplied(), null);
|
cm.isAlphaPremultiplied(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WritableRaster createCompatibleDestRaster(Raster src) {
|
public WritableRaster createCompatibleDestRaster(Raster src) {
|
||||||
return src.createCompatibleWritableRaster(mWidth, mHeight);
|
return src.createCompatibleWritableRaster(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(Raster src) {
|
public Rectangle2D getBounds2D(Raster src) {
|
||||||
return new Rectangle(mWidth, mHeight);
|
return new Rectangle(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||||
return new Rectangle(mWidth, mHeight);
|
return new Rectangle(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||||
+8
-7
@@ -60,14 +60,16 @@ import java.awt.image.RGBImageFilter;
|
|||||||
|
|
||||||
public class BrightnessContrastFilter extends RGBImageFilter {
|
public class BrightnessContrastFilter extends RGBImageFilter {
|
||||||
|
|
||||||
|
// TODO: Replace with RescaleOp?
|
||||||
|
|
||||||
// This filter can filter IndexColorModel, as it is does not depend on
|
// This filter can filter IndexColorModel, as it is does not depend on
|
||||||
// the pixels' location
|
// the pixels' location
|
||||||
{
|
{
|
||||||
canFilterIndexColorModel = true;
|
canFilterIndexColorModel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a precalculated lookup table for performace
|
// Use a pre-calculated lookup table for performance
|
||||||
private int[] mLUT = null;
|
private final int[] LUT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a BrightnessContrastFilter with default values
|
* Creates a BrightnessContrastFilter with default values
|
||||||
@@ -105,7 +107,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
|||||||
* {@code -1.0,..,0.0,..,1.0}.
|
* {@code -1.0,..,0.0,..,1.0}.
|
||||||
*/
|
*/
|
||||||
public BrightnessContrastFilter(float pBrightness, float pContrast) {
|
public BrightnessContrastFilter(float pBrightness, float pContrast) {
|
||||||
mLUT = createLUT(pBrightness, pContrast);
|
LUT = createLUT(pBrightness, pContrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] createLUT(float pBrightness, float 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
|
* @return the filtered pixel value in the default color space
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public int filterRGB(int pX, int pY, int pARGB) {
|
public int filterRGB(int pX, int pY, int pARGB) {
|
||||||
// Get color components
|
// Get color components
|
||||||
int r = pARGB >> 16 & 0xFF;
|
int r = pARGB >> 16 & 0xFF;
|
||||||
@@ -157,9 +158,9 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
|||||||
int b = pARGB & 0xFF;
|
int b = pARGB & 0xFF;
|
||||||
|
|
||||||
// Scale to new contrast
|
// Scale to new contrast
|
||||||
r = mLUT[r];
|
r = LUT[r];
|
||||||
g = mLUT[g];
|
g = LUT[g];
|
||||||
b = mLUT[b];
|
b = LUT[b];
|
||||||
|
|
||||||
// Return ARGB pixel, leave transparency as is
|
// Return ARGB pixel, leave transparency as is
|
||||||
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
|
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
|
||||||
+609
-542
File diff suppressed because it is too large
Load Diff
+20
-21
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.*;
|
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 $
|
* @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 {
|
public class BufferedImageIcon implements Icon {
|
||||||
private final BufferedImage mImage;
|
private final BufferedImage image;
|
||||||
private int mWidth;
|
private int width;
|
||||||
private int mHeight;
|
private int height;
|
||||||
private final boolean mFast;
|
private final boolean fast;
|
||||||
|
|
||||||
public BufferedImageIcon(BufferedImage pImage) {
|
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) {
|
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
|
||||||
if (pImage == null) {
|
this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight);
|
||||||
throw new IllegalArgumentException("image == null");
|
}
|
||||||
}
|
|
||||||
if (pWidth <= 0 || pHeight <= 0) {
|
|
||||||
throw new IllegalArgumentException("Icon size must be positive");
|
|
||||||
}
|
|
||||||
|
|
||||||
mImage = pImage;
|
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) {
|
||||||
mWidth = pWidth;
|
image = Validate.notNull(pImage, "image");
|
||||||
mHeight = pHeight;
|
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() {
|
public int getIconHeight() {
|
||||||
return mHeight;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIconWidth() {
|
public int getIconWidth() {
|
||||||
return mWidth;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
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");
|
//System.out.println("Scaling fast");
|
||||||
g.drawImage(mImage, x, y, mWidth, mHeight, null);
|
g.drawImage(image, x, y, width, height, null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//System.out.println("Scaling using interpolation");
|
//System.out.println("Scaling using interpolation");
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
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,
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
g2.drawImage(mImage, xform, null);
|
g2.drawImage(image, xform, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+23
-21
@@ -73,14 +73,15 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
|||||||
*/
|
*/
|
||||||
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
|
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
|
||||||
|
|
||||||
private final Kernel mKernel;
|
private final Kernel kernel;
|
||||||
private final int mEdgeCondition;
|
private final int edgeCondition;
|
||||||
|
|
||||||
private final ConvolveOp mConvolve;
|
private final ConvolveOp convolve;
|
||||||
|
|
||||||
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
|
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
|
||||||
// Create convolution operation
|
// Create convolution operation
|
||||||
int edge;
|
int edge;
|
||||||
|
|
||||||
switch (pEdgeCondition) {
|
switch (pEdgeCondition) {
|
||||||
case EDGE_REFLECT:
|
case EDGE_REFLECT:
|
||||||
case EDGE_WRAP:
|
case EDGE_WRAP:
|
||||||
@@ -90,9 +91,10 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
|||||||
edge = pEdgeCondition;
|
edge = pEdgeCondition;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
mKernel = pKernel;
|
|
||||||
mEdgeCondition = pEdgeCondition;
|
kernel = pKernel;
|
||||||
mConvolve = new ConvolveOp(pKernel, edge, pHints);
|
edgeCondition = pEdgeCondition;
|
||||||
|
convolve = new ConvolveOp(pKernel, edge, pHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConvolveWithEdgeOp(final Kernel pKernel) {
|
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");
|
throw new IllegalArgumentException("source image cannot be the same as the destination image");
|
||||||
}
|
}
|
||||||
|
|
||||||
int borderX = mKernel.getWidth() / 2;
|
int borderX = kernel.getWidth() / 2;
|
||||||
int borderY = mKernel.getHeight() / 2;
|
int borderY = kernel.getHeight() / 2;
|
||||||
|
|
||||||
BufferedImage original = addBorder(pSource, borderX, borderY);
|
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)
|
// 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) {
|
if (pSource != original) {
|
||||||
// Remove the border
|
// 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) {
|
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
|
||||||
if ((mEdgeCondition & 2) == 0) {
|
if ((edgeCondition & 2) == 0) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
|||||||
g.drawImage(pOriginal, pBorderX, pBorderY, null);
|
g.drawImage(pOriginal, pBorderX, pBorderY, null);
|
||||||
|
|
||||||
// TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel
|
// 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:
|
case EDGE_REFLECT:
|
||||||
// Top/left (empty)
|
// Top/left (empty)
|
||||||
g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center
|
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
|
g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right
|
||||||
break;
|
break;
|
||||||
default:
|
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
|
* @see #EDGE_WRAP
|
||||||
*/
|
*/
|
||||||
public int getEdgeCondition() {
|
public int getEdgeCondition() {
|
||||||
return mEdgeCondition;
|
return edgeCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
|
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) {
|
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) {
|
public WritableRaster createCompatibleDestRaster(final Raster pSource) {
|
||||||
return mConvolve.createCompatibleDestRaster(pSource);
|
return convolve.createCompatibleDestRaster(pSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(final BufferedImage pSource) {
|
public Rectangle2D getBounds2D(final BufferedImage pSource) {
|
||||||
return mConvolve.getBounds2D(pSource);
|
return convolve.getBounds2D(pSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(final Raster pSource) {
|
public Rectangle2D getBounds2D(final Raster pSource) {
|
||||||
return mConvolve.getBounds2D(pSource);
|
return convolve.getBounds2D(pSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
|
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
|
||||||
return mConvolve.getPoint2D(pSourcePoint, pDestinationPoint);
|
return convolve.getPoint2D(pSourcePoint, pDestinationPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderingHints getRenderingHints() {
|
public RenderingHints getRenderingHints() {
|
||||||
return mConvolve.getRenderingHints();
|
return convolve.getRenderingHints();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Kernel getKernel() {
|
public Kernel getKernel() {
|
||||||
return mConvolve.getKernel();
|
return convolve.getKernel();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+14
-25
@@ -51,7 +51,7 @@ import java.awt.image.WritableRaster;
|
|||||||
*/
|
*/
|
||||||
public class CopyDither implements BufferedImageOp, RasterOp {
|
public class CopyDither implements BufferedImageOp, RasterOp {
|
||||||
|
|
||||||
protected IndexColorModel mIndexColorModel = null;
|
protected IndexColorModel indexColorModel = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code CopyDither}, using the given
|
* Creates a {@code CopyDither}, using the given
|
||||||
@@ -61,7 +61,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
|||||||
*/
|
*/
|
||||||
public CopyDither(IndexColorModel pICM) {
|
public CopyDither(IndexColorModel pICM) {
|
||||||
// Store colormodel
|
// 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
|
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
|
||||||
* an instance of {@code IndexColorModel}.
|
* an instance of {@code IndexColorModel}.
|
||||||
*/
|
*/
|
||||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
|
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
|
||||||
ColorModel pDestCM) {
|
|
||||||
if (pDestCM == null) {
|
if (pDestCM == null) {
|
||||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, indexColorModel);
|
||||||
BufferedImage.TYPE_BYTE_INDEXED,
|
|
||||||
mIndexColorModel);
|
|
||||||
}
|
}
|
||||||
else if (pDestCM instanceof IndexColorModel) {
|
else if (pDestCM instanceof IndexColorModel) {
|
||||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) pDestCM);
|
||||||
BufferedImage.TYPE_BYTE_INDEXED,
|
|
||||||
(IndexColorModel) pDestCM);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new ImageFilterException("Only IndexColorModel allowed.");
|
throw new ImageFilterException("Only IndexColorModel allowed.");
|
||||||
@@ -112,13 +107,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
|||||||
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
||||||
}
|
}
|
||||||
|
|
||||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
|
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
|
||||||
IndexColorModel pIndexColorModel) {
|
|
||||||
/*
|
|
||||||
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
|
|
||||||
BufferedImage.TYPE_BYTE_INDEXED,
|
|
||||||
pIndexColorModel).getRaster();
|
|
||||||
*/
|
|
||||||
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
|
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
|
* @return the destination image, or a new image, if {@code pDest} was
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
*/
|
*/
|
||||||
public final BufferedImage filter(BufferedImage pSource,
|
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
|
||||||
BufferedImage pDest) {
|
|
||||||
// Create destination image, if none provided
|
// Create destination image, if none provided
|
||||||
if (pDest == null) {
|
if (pDest == null) {
|
||||||
pDest = createCompatibleDestImage(pSource, getICM(pSource));
|
pDest = createCompatibleDestImage(pSource, getICM(pSource));
|
||||||
@@ -238,16 +226,17 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IndexColorModel getICM(BufferedImage pSource) {
|
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) {
|
private IndexColorModel getICM(Raster pSource) {
|
||||||
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
|
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexColorModel createIndexColorModel(Raster pSource) {
|
private IndexColorModel createIndexColorModel(Raster pSource) {
|
||||||
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||||
BufferedImage.TYPE_INT_ARGB);
|
|
||||||
image.setData(pSource);
|
image.setData(pSource);
|
||||||
|
|
||||||
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY);
|
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
|
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
*/
|
*/
|
||||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
|
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
|
||||||
IndexColorModel pColorModel) {
|
|
||||||
int width = pSource.getWidth();
|
int width = pSource.getWidth();
|
||||||
int height = pSource.getHeight();
|
int height = pSource.getHeight();
|
||||||
|
|
||||||
@@ -292,6 +280,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
|||||||
pDest.setDataElements(x, y, pixel);
|
pDest.setDataElements(x, y, pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pDest;
|
return pDest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+73
-78
@@ -17,7 +17,7 @@ import java.util.Random;
|
|||||||
* This {@code BufferedImageOp/RasterOp} implements basic
|
* This {@code BufferedImageOp/RasterOp} implements basic
|
||||||
* Floyd-Steinberg error-diffusion algorithm for dithering.
|
* Floyd-Steinberg error-diffusion algorithm for dithering.
|
||||||
* <P/>
|
* <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|
|
* | |x|7|
|
||||||
* - - - -
|
* - - - -
|
||||||
@@ -36,45 +36,47 @@ import java.util.Random;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haku $
|
* @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 {
|
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 int FS_SCALE = 1 << 8;
|
||||||
private static final Random RANDOM = new Random();
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
protected final IndexColorModel indexColorModel;
|
||||||
|
private boolean alternateScans = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code DiffusionDither}, using the given
|
* Creates a {@code DiffusionDither}, using the given
|
||||||
* {@code IndexColorModel} for dithering into.
|
* {@code IndexColorModel} for dithering into.
|
||||||
*
|
*
|
||||||
* @param pICM an IndexColorModel.
|
* @param pICM an IndexColorModel.
|
||||||
*/
|
*/
|
||||||
public DiffusionDither(IndexColorModel pICM) {
|
public DiffusionDither(final IndexColorModel pICM) {
|
||||||
// Store colormodel
|
// Store color model
|
||||||
mIndexColorModel = pICM;
|
indexColorModel = pICM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code DiffusionDither}, with no fixed
|
* Creates a {@code DiffusionDither}, with no fixed
|
||||||
* {@code IndexColorModel}. The colormodel will be generated for each
|
* {@code IndexColorModel}. The color model will be generated for each
|
||||||
* filtering, unless the dest image allready has an
|
* filtering, unless the destination image already has an
|
||||||
* {@code IndexColorModel}.
|
* {@code IndexColorModel}.
|
||||||
*/
|
*/
|
||||||
public DiffusionDither() {
|
public DiffusionDither() {
|
||||||
|
this(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the scan mode. If the parameter is true, error distribution for
|
* 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
|
* every even line will be left-to-right, while odd lines will be
|
||||||
* right-to-left.
|
* right-to-left.
|
||||||
|
* The default is {@code true}.
|
||||||
*
|
*
|
||||||
* @param pUse {@code true} if scan mode should be alternating left/right
|
* @param pUse {@code true} if scan mode should be alternating left/right
|
||||||
*/
|
*/
|
||||||
public void setAlternateScans(boolean pUse) {
|
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
|
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
|
||||||
* an instance of {@code IndexColorModel}.
|
* an instance of {@code IndexColorModel}.
|
||||||
*/
|
*/
|
||||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
|
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
|
||||||
ColorModel pDestCM) {
|
|
||||||
if (pDestCM == null) {
|
if (pDestCM == null) {
|
||||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
||||||
BufferedImage.TYPE_BYTE_INDEXED,
|
BufferedImage.TYPE_BYTE_INDEXED,
|
||||||
@@ -107,7 +108,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
* Creates a compatible {@code Raster} to dither into.
|
* Creates a compatible {@code Raster} to dither into.
|
||||||
* Only {@code IndexColorModel} allowed.
|
* Only {@code IndexColorModel} allowed.
|
||||||
*
|
*
|
||||||
* @param pSrc
|
* @param pSrc the source raster
|
||||||
*
|
*
|
||||||
* @return a {@code WritableRaster}
|
* @return a {@code WritableRaster}
|
||||||
*/
|
*/
|
||||||
@@ -115,14 +116,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
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 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.
|
* Floyd-Steinberg error-diffusion to the image.
|
||||||
*
|
*
|
||||||
* @param pSource the source 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
|
* @return the destination image, or a new image, if {@code pDest} was
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
*/
|
*/
|
||||||
public final BufferedImage filter(BufferedImage pSource,
|
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
|
||||||
BufferedImage pDest) {
|
|
||||||
// Create destination image, if none provided
|
// Create destination image, if none provided
|
||||||
if (pDest == null) {
|
if (pDest == null) {
|
||||||
pDest = createCompatibleDestImage(pSource, getICM(pSource));
|
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
|
* Performs a single-input/single-output dither operation, applying basic
|
||||||
* Floyd-Steinberg error-diffusion to the image.
|
* Floyd-Steinberg error-diffusion to the image.
|
||||||
*
|
*
|
||||||
* @param pSource
|
* @param pSource the source raster, assumed to be in sRGB
|
||||||
* @param pDest
|
* @param pDest the destination raster, may be {@code null}
|
||||||
*
|
*
|
||||||
* @return the destination raster, or a new raster, if {@code pDest} was
|
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
@@ -252,10 +254,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private IndexColorModel getICM(BufferedImage pSource) {
|
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) {
|
private IndexColorModel getICM(Raster pSource) {
|
||||||
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
|
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndexColorModel createIndexColorModel(Raster pSource) {
|
private IndexColorModel createIndexColorModel(Raster pSource) {
|
||||||
@@ -265,21 +267,18 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
|
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a single-input/single-output dither operation, applying basic
|
* Performs a single-input/single-output dither operation, applying basic
|
||||||
* Floyd-Steinberg error-diffusion to the image.
|
* Floyd-Steinberg error-diffusion to the image.
|
||||||
*
|
*
|
||||||
* @param pSource
|
* @param pSource the source raster, assumed to be in sRGB
|
||||||
* @param pDest
|
* @param pDest the destination raster, may be {@code null}
|
||||||
* @param pColorModel
|
* @param pColorModel the indexed color model to use
|
||||||
*
|
*
|
||||||
* @return the destination raster, or a new raster, if {@code pDest} was
|
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||||
* {@code null}.
|
* {@code null}.
|
||||||
*/
|
*/
|
||||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
|
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
|
||||||
IndexColorModel pColorModel) {
|
|
||||||
int width = pSource.getWidth();
|
int width = pSource.getWidth();
|
||||||
int height = pSource.getHeight();
|
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
|
// 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
|
// offset from actual column position by one to allow FS to not check
|
||||||
// left/right edge conditions
|
// left/right edge conditions
|
||||||
int[][] mCurrErr = new int[width + 2][3];
|
int[][] currErr = new int[width + 2][3];
|
||||||
int[][] mNextErr = new int[width + 2][3];
|
int[][] nextErr = new int[width + 2][3];
|
||||||
|
|
||||||
// Random errors in [-1 .. 1] - for first row
|
// Random errors in [-1 .. 1] - for first row
|
||||||
for (int i = 0; i < width + 2; i++) {
|
for (int i = 0; i < width + 2; i++) {
|
||||||
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
|
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
|
||||||
/*
|
currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||||
mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||||
mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
currErr[i][2] = 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temp buffers
|
// Temp buffers
|
||||||
@@ -319,10 +313,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
// Loop through image data
|
// Loop through image data
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
// Clear out next error rows for colour errors
|
// Clear out next error rows for colour errors
|
||||||
for (int i = mNextErr.length; --i >= 0;) {
|
for (int i = nextErr.length; --i >= 0;) {
|
||||||
mNextErr[i][0] = 0;
|
nextErr[i][0] = 0;
|
||||||
mNextErr[i][1] = 0;
|
nextErr[i][1] = 0;
|
||||||
mNextErr[i][2] = 0;
|
nextErr[i][2] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up start column and limit
|
// Set up start column and limit
|
||||||
@@ -349,7 +343,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Make a 28.4 FP number, add Error (with fraction),
|
// Make a 28.4 FP number, add Error (with fraction),
|
||||||
// rounding and truncate to int
|
// 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
|
// Clamp
|
||||||
if (inRGB[i] > 255) {
|
if (inRGB[i] > 255) {
|
||||||
@@ -385,26 +379,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
if (forward) {
|
if (forward) {
|
||||||
// Row 1 (y)
|
// Row 1 (y)
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
mCurrErr[x + 2][0] += diff[0] * 7;
|
currErr[x + 2][0] += diff[0] * 7;
|
||||||
mCurrErr[x + 2][1] += diff[1] * 7;
|
currErr[x + 2][1] += diff[1] * 7;
|
||||||
mCurrErr[x + 2][2] += diff[2] * 7;
|
currErr[x + 2][2] += diff[2] * 7;
|
||||||
|
|
||||||
// Row 2 (y + 1)
|
// Row 2 (y + 1)
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
mNextErr[x][0] += diff[0] * 3;
|
nextErr[x][0] += diff[0] * 3;
|
||||||
mNextErr[x][1] += diff[1] * 3;
|
nextErr[x][1] += diff[1] * 3;
|
||||||
mNextErr[x][2] += diff[2] * 3;
|
nextErr[x][2] += diff[2] * 3;
|
||||||
// Update error in this pixel (x)
|
// Update error in this pixel (x)
|
||||||
mNextErr[x + 1][0] += diff[0] * 5;
|
nextErr[x + 1][0] += diff[0] * 5;
|
||||||
mNextErr[x + 1][1] += diff[1] * 5;
|
nextErr[x + 1][1] += diff[1] * 5;
|
||||||
mNextErr[x + 1][2] += diff[2] * 5;
|
nextErr[x + 1][2] += diff[2] * 5;
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
// TODO: Consider calculating this using
|
// TODO: Consider calculating this using
|
||||||
// error term = error - sum(error terms 1, 2 and 3)
|
// error term = error - sum(error terms 1, 2 and 3)
|
||||||
// See Computer Graphics (Foley et al.), p. 573
|
// See Computer Graphics (Foley et al.), p. 573
|
||||||
mNextErr[x + 2][0] += diff[0]; // * 1;
|
nextErr[x + 2][0] += diff[0]; // * 1;
|
||||||
mNextErr[x + 2][1] += diff[1]; // * 1;
|
nextErr[x + 2][1] += diff[1]; // * 1;
|
||||||
mNextErr[x + 2][2] += diff[2]; // * 1;
|
nextErr[x + 2][2] += diff[2]; // * 1;
|
||||||
|
|
||||||
// Next
|
// Next
|
||||||
x++;
|
x++;
|
||||||
@@ -418,26 +412,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
else {
|
else {
|
||||||
// Row 1 (y)
|
// Row 1 (y)
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
mCurrErr[x][0] += diff[0] * 7;
|
currErr[x][0] += diff[0] * 7;
|
||||||
mCurrErr[x][1] += diff[1] * 7;
|
currErr[x][1] += diff[1] * 7;
|
||||||
mCurrErr[x][2] += diff[2] * 7;
|
currErr[x][2] += diff[2] * 7;
|
||||||
|
|
||||||
// Row 2 (y + 1)
|
// Row 2 (y + 1)
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
mNextErr[x + 2][0] += diff[0] * 3;
|
nextErr[x + 2][0] += diff[0] * 3;
|
||||||
mNextErr[x + 2][1] += diff[1] * 3;
|
nextErr[x + 2][1] += diff[1] * 3;
|
||||||
mNextErr[x + 2][2] += diff[2] * 3;
|
nextErr[x + 2][2] += diff[2] * 3;
|
||||||
// Update error in this pixel (x)
|
// Update error in this pixel (x)
|
||||||
mNextErr[x + 1][0] += diff[0] * 5;
|
nextErr[x + 1][0] += diff[0] * 5;
|
||||||
mNextErr[x + 1][1] += diff[1] * 5;
|
nextErr[x + 1][1] += diff[1] * 5;
|
||||||
mNextErr[x + 1][2] += diff[2] * 5;
|
nextErr[x + 1][2] += diff[2] * 5;
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
// TODO: Consider calculating this using
|
// TODO: Consider calculating this using
|
||||||
// error term = error - sum(error terms 1, 2 and 3)
|
// error term = error - sum(error terms 1, 2 and 3)
|
||||||
// See Computer Graphics (Foley et al.), p. 573
|
// See Computer Graphics (Foley et al.), p. 573
|
||||||
mNextErr[x][0] += diff[0]; // * 1;
|
nextErr[x][0] += diff[0]; // * 1;
|
||||||
mNextErr[x][1] += diff[1]; // * 1;
|
nextErr[x][1] += diff[1]; // * 1;
|
||||||
mNextErr[x][2] += diff[2]; // * 1;
|
nextErr[x][2] += diff[2]; // * 1;
|
||||||
|
|
||||||
// Previous
|
// Previous
|
||||||
x--;
|
x--;
|
||||||
@@ -451,15 +445,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
|
|
||||||
// Make next error info current for next iteration
|
// Make next error info current for next iteration
|
||||||
int[][] temperr;
|
int[][] temperr;
|
||||||
temperr = mCurrErr;
|
temperr = currErr;
|
||||||
mCurrErr = mNextErr;
|
currErr = nextErr;
|
||||||
mNextErr = temperr;
|
nextErr = temperr;
|
||||||
|
|
||||||
// Toggle direction
|
// Toggle direction
|
||||||
if (mAlternateScans) {
|
if (alternateScans) {
|
||||||
forward = !forward;
|
forward = !forward;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pDest;
|
return pDest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-6
@@ -48,8 +48,8 @@ public class GrayFilter extends RGBImageFilter {
|
|||||||
canFilterIndexColorModel = true;
|
canFilterIndexColorModel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int mLow = 0;
|
private int low = 0;
|
||||||
private float mRange = 1.0f;
|
private float range = 1.0f;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a GrayFilter using ITU color-conversion.
|
* Constructs a GrayFilter using ITU color-conversion.
|
||||||
@@ -82,8 +82,8 @@ public class GrayFilter extends RGBImageFilter {
|
|||||||
pHigh = 1f;
|
pHigh = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
mLow = (int) (pLow * 255f);
|
low = (int) (pLow * 255f);
|
||||||
mRange = pHigh - pLow;
|
range = pHigh - pLow;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +118,9 @@ public class GrayFilter extends RGBImageFilter {
|
|||||||
|
|
||||||
//int gray = (int) ((float) (r + g + b) / 3.0f);
|
//int gray = (int) ((float) (r + g + b) / 3.0f);
|
||||||
|
|
||||||
if (mRange != 1.0f) {
|
if (range != 1.0f) {
|
||||||
// Apply range
|
// Apply range
|
||||||
gray = mLow + (int) (gray * mRange);
|
gray = low + (int) (gray * range);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return ARGB pixel
|
// Return ARGB pixel
|
||||||
+7
-29
@@ -32,42 +32,20 @@ package com.twelvemonkeys.image;
|
|||||||
* This class wraps IllegalArgumentException as thrown by the
|
* This class wraps IllegalArgumentException as thrown by the
|
||||||
* BufferedImageOp interface for more fine-grained control.
|
* 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 $
|
* @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 {
|
public class ImageFilterException extends IllegalArgumentException {
|
||||||
private Throwable mCause = null;
|
public ImageFilterException(String message) {
|
||||||
|
super(message);
|
||||||
public ImageFilterException(String pStr) {
|
|
||||||
super(pStr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageFilterException(Throwable pT) {
|
public ImageFilterException(Throwable cause) {
|
||||||
initCause(pT);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageFilterException(String pStr, Throwable pT) {
|
public ImageFilterException(String message, Throwable cause) {
|
||||||
super(pStr);
|
super(message, cause);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+81
-163
@@ -32,19 +32,17 @@ import java.awt.*;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains methods for basic image manipulation and conversion.
|
* 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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haku $
|
* @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 {
|
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_CCW = -90;
|
||||||
public final static int ROTATE_90_CW = 90;
|
public final static int ROTATE_90_CW = 90;
|
||||||
@@ -59,12 +57,14 @@ public final class ImageUtil {
|
|||||||
* @see #EDGE_REFLECT
|
* @see #EDGE_REFLECT
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
|
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
|
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
|
||||||
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
|
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
|
||||||
* @see #EDGE_REFLECT
|
* @see #EDGE_REFLECT
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
|
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a border to the image while convolving. The border will reflect the
|
* Adds a border to the image while convolving. The border will reflect the
|
||||||
* edges of the original image. This is usually a good default.
|
* 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)
|
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
|
||||||
*/
|
*/
|
||||||
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
|
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a border to the image while convolving. The border will wrap the
|
* 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.
|
* 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 */
|
/** Our static image tracker */
|
||||||
private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT);
|
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 AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
|
||||||
/** */
|
/** */
|
||||||
protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
|
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();
|
private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration();
|
||||||
|
|
||||||
@@ -208,28 +202,12 @@ public final class ImageUtil {
|
|||||||
private 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}.
|
* Converts the {@code RenderedImage} to a {@code BufferedImage}.
|
||||||
* The new image will have the <em>same</em> {@code ColorModel},
|
* The new image will have the <em>same</em> {@code ColorModel},
|
||||||
* {@code Raster} and properties as the original image, if possible.
|
* {@code Raster} and properties as the original image, if possible.
|
||||||
* <p/>
|
* <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.
|
* and no conversion takes place.
|
||||||
*
|
*
|
||||||
* @param pOriginal the image to convert.
|
* @param pOriginal the image to convert.
|
||||||
@@ -237,7 +215,7 @@ public final class ImageUtil {
|
|||||||
* @return a {@code BufferedImage}
|
* @return a {@code BufferedImage}
|
||||||
*/
|
*/
|
||||||
public static BufferedImage toBuffered(RenderedImage pOriginal) {
|
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) {
|
if (pOriginal instanceof BufferedImage) {
|
||||||
return (BufferedImage) pOriginal;
|
return (BufferedImage) pOriginal;
|
||||||
}
|
}
|
||||||
@@ -283,7 +261,7 @@ public final class ImageUtil {
|
|||||||
* Converts the {@code RenderedImage} to a {@code BufferedImage} of the
|
* Converts the {@code RenderedImage} to a {@code BufferedImage} of the
|
||||||
* given type.
|
* given type.
|
||||||
* <p/>
|
* <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.
|
* is simply returned and no conversion takes place.
|
||||||
*
|
*
|
||||||
* @param pOriginal the image to convert.
|
* @param pOriginal the image to convert.
|
||||||
@@ -297,7 +275,7 @@ public final class ImageUtil {
|
|||||||
* @see java.awt.image.BufferedImage#getType()
|
* @see java.awt.image.BufferedImage#getType()
|
||||||
*/
|
*/
|
||||||
public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) {
|
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) {
|
if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) {
|
||||||
return (BufferedImage) pOriginal;
|
return (BufferedImage) pOriginal;
|
||||||
}
|
}
|
||||||
@@ -329,7 +307,7 @@ public final class ImageUtil {
|
|||||||
* given type. The new image will have the same {@code ColorModel},
|
* given type. The new image will have the same {@code ColorModel},
|
||||||
* {@code Raster} and properties as the original image, if possible.
|
* {@code Raster} and properties as the original image, if possible.
|
||||||
* <p/>
|
* <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.
|
* is simply returned and no conversion takes place.
|
||||||
* <p/>
|
* <p/>
|
||||||
* This method simply invokes
|
* This method simply invokes
|
||||||
@@ -354,7 +332,7 @@ public final class ImageUtil {
|
|||||||
* The new image will have the same {@code ColorModel}, {@code Raster} and
|
* The new image will have the same {@code ColorModel}, {@code Raster} and
|
||||||
* properties as the original image, if possible.
|
* properties as the original image, if possible.
|
||||||
* <p/>
|
* <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.
|
* and no conversion takes place.
|
||||||
*
|
*
|
||||||
* @param pOriginal the image to convert.
|
* @param pOriginal the image to convert.
|
||||||
@@ -365,7 +343,7 @@ public final class ImageUtil {
|
|||||||
* @throws ImageConversionException if the image cannot be converted
|
* @throws ImageConversionException if the image cannot be converted
|
||||||
*/
|
*/
|
||||||
public static BufferedImage toBuffered(Image pOriginal) {
|
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) {
|
if (pOriginal instanceof BufferedImage) {
|
||||||
return (BufferedImage) pOriginal;
|
return (BufferedImage) pOriginal;
|
||||||
}
|
}
|
||||||
@@ -381,7 +359,7 @@ public final class ImageUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the given image. The image will have the same
|
* 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.
|
* @param pImage the image to clone.
|
||||||
*
|
*
|
||||||
@@ -411,11 +389,11 @@ public final class ImageUtil {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method is optimized for the most common cases of {@code ColorModel}
|
* This method is optimized for the most common cases of {@code ColorModel}
|
||||||
* and pixel data combinations. The raster's backing {@code DataBuffer} is
|
* 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
|
* resource-friendly than using
|
||||||
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
|
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
|
||||||
* <p/>
|
* <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 ColorModel.createCompatibleWritableRaster(w, h)} and
|
||||||
* {@code WritableRaster.setDataElements(w, h, pixels)}
|
* {@code WritableRaster.setDataElements(w, h, pixels)}
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -441,8 +419,8 @@ public final class ImageUtil {
|
|||||||
*/
|
*/
|
||||||
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
|
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
|
||||||
// NOTE: This is optimized code for most common cases.
|
// NOTE: This is optimized code for most common cases.
|
||||||
// We create a DataBuffer with the array from grabber.getPixels()
|
// We create a DataBuffer from the pixel array directly,
|
||||||
// directly, and creating a raster based on the ColorModel.
|
// and creating a raster based on the DataBuffer and ColorModel.
|
||||||
// Creating rasters this way is faster and more resource-friendly, as
|
// Creating rasters this way is faster and more resource-friendly, as
|
||||||
// cm.createCompatibleWritableRaster allocates an
|
// cm.createCompatibleWritableRaster allocates an
|
||||||
// "empty" DataBuffer with a storage array of w*h. This array is
|
// "empty" DataBuffer with a storage array of w*h. This array is
|
||||||
@@ -456,14 +434,12 @@ public final class ImageUtil {
|
|||||||
if (pPixels instanceof int[]) {
|
if (pPixels instanceof int[]) {
|
||||||
int[] data = (int[]) pPixels;
|
int[] data = (int[]) pPixels;
|
||||||
buffer = new DataBufferInt(data, data.length);
|
buffer = new DataBufferInt(data, data.length);
|
||||||
//bands = data.length / (w * h);
|
|
||||||
bands = pColorModel.getNumComponents();
|
bands = pColorModel.getNumComponents();
|
||||||
}
|
}
|
||||||
else if (pPixels instanceof short[]) {
|
else if (pPixels instanceof short[]) {
|
||||||
short[] data = (short[]) pPixels;
|
short[] data = (short[]) pPixels;
|
||||||
buffer = new DataBufferUShort(data, data.length);
|
buffer = new DataBufferUShort(data, data.length);
|
||||||
bands = data.length / (pWidth * pHeight);
|
bands = data.length / (pWidth * pHeight);
|
||||||
//bands = cm.getNumComponents();
|
|
||||||
}
|
}
|
||||||
else if (pPixels instanceof byte[]) {
|
else if (pPixels instanceof byte[]) {
|
||||||
byte[] data = (byte[]) pPixels;
|
byte[] data = (byte[]) pPixels;
|
||||||
@@ -476,47 +452,30 @@ public final class ImageUtil {
|
|||||||
else {
|
else {
|
||||||
bands = data.length / (pWidth * pHeight);
|
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 {
|
else {
|
||||||
//System.out.println("Fallback!");
|
|
||||||
// Fallback mode, slower & requires more memory, but compatible
|
// Fallback mode, slower & requires more memory, but compatible
|
||||||
bands = -1;
|
bands = -1;
|
||||||
|
|
||||||
// Create raster from colormodel, w and h
|
// Create raster from color model, w and h
|
||||||
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
||||||
raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
|
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) {
|
if (raster == null) {
|
||||||
//int bits = cm.getPixelSize();
|
|
||||||
//if (bits > 4) {
|
|
||||||
if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
|
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);
|
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
|
||||||
}
|
}
|
||||||
else if (pColorModel instanceof PackedColorModel) {
|
else if (pColorModel instanceof PackedColorModel) {
|
||||||
//System.out.println("Creating packed model");
|
|
||||||
PackedColorModel pcm = (PackedColorModel) pColorModel;
|
PackedColorModel pcm = (PackedColorModel) pColorModel;
|
||||||
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
|
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//System.out.println("Creating interleaved model");
|
|
||||||
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
|
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
|
||||||
int[] bandsOffsets = new int[bands];
|
int[] bandsOffsets = new int[bands];
|
||||||
for (int i = 0; i < bands;) {
|
for (int i = 0; i < bands;) {
|
||||||
bandsOffsets[i] = bands - (++i);
|
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);
|
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 pOriginal the orignal image
|
||||||
* @param pModel the original color model
|
* @param pModel the original color model
|
||||||
* @param mWidth the requested width of the raster
|
* @param width the requested width of the raster
|
||||||
* @param mHeight the requested height of the raster
|
* @param height the requested height of the raster
|
||||||
*
|
*
|
||||||
* @return a new WritableRaster
|
* @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)) {
|
if (pModel == null || equals(pOriginal.getColorModel(), pModel)) {
|
||||||
|
int[] bOffs;
|
||||||
switch (pOriginal.getType()) {
|
switch (pOriginal.getType()) {
|
||||||
case BufferedImage.TYPE_3BYTE_BGR:
|
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,
|
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
||||||
mWidth, mHeight,
|
width, height,
|
||||||
mWidth * 3, 3,
|
width * 3, 3,
|
||||||
bOffs, null);
|
bOffs, null);
|
||||||
case BufferedImage.TYPE_4BYTE_ABGR:
|
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||||
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||||
bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
|
bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
|
||||||
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
||||||
mWidth, mHeight,
|
width, height,
|
||||||
mWidth * 4, 4,
|
width * 4, 4,
|
||||||
bOffs, null);
|
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:
|
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
|
* The new image will have the same {@code ColorModel}, {@code Raster} and
|
||||||
* properties as the original image, if possible.
|
* properties as the original image, if possible.
|
||||||
* <p/>
|
* <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.
|
* is simply returned and no conversion takes place.
|
||||||
*
|
*
|
||||||
* @param pOriginal the image to convert.
|
* @param pOriginal the image to convert.
|
||||||
@@ -597,7 +569,7 @@ public final class ImageUtil {
|
|||||||
* the color model
|
* the color model
|
||||||
*/
|
*/
|
||||||
private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) {
|
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)
|
if ((pOriginal instanceof BufferedImage)
|
||||||
&& ((BufferedImage) pOriginal).getType() == pType
|
&& ((BufferedImage) pOriginal).getType() == pType
|
||||||
&& (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) {
|
&& (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
|
* Creates a scaled instance of the given {@code Image}, and converts it to
|
||||||
* a {@code BufferedImage} if needed.
|
* a {@code BufferedImage} if needed.
|
||||||
* If the original image is a {@code BufferedImage} the result will have
|
* 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.
|
* probably not useful for anything but {@code IndexColorModel} images.
|
||||||
*
|
*
|
||||||
* @param pImage the {@code Image} to scale
|
* @param pImage the {@code Image} to scale
|
||||||
@@ -820,7 +792,7 @@ public final class ImageUtil {
|
|||||||
|
|
||||||
BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints);
|
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)) {
|
if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) {
|
||||||
//System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
|
//System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
|
||||||
//long start = System.currentTimeMillis();
|
//long start = System.currentTimeMillis();
|
||||||
@@ -835,11 +807,13 @@ public final class ImageUtil {
|
|||||||
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
|
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);
|
new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
drawOnto(temp, scaled);
|
drawOnto(temp, scaled);
|
||||||
}
|
}
|
||||||
|
|
||||||
scaled = temp;
|
scaled = temp;
|
||||||
//long end = System.currentTimeMillis();
|
//long end = System.currentTimeMillis();
|
||||||
//System.out.println("Time: " + (end - start) + " ms");
|
//System.out.println("Time: " + (end - start) + " ms");
|
||||||
@@ -965,9 +939,6 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int convertAWTHints(int pHints) {
|
private static int convertAWTHints(int pHints) {
|
||||||
// TODO: These conversions are broken!
|
|
||||||
// box == area average
|
|
||||||
// point == replicate (or..?)
|
|
||||||
switch (pHints) {
|
switch (pHints) {
|
||||||
case Image.SCALE_FAST:
|
case Image.SCALE_FAST:
|
||||||
case Image.SCALE_REPLICATE:
|
case Image.SCALE_REPLICATE:
|
||||||
@@ -1129,26 +1100,26 @@ public final class ImageUtil {
|
|||||||
* Sharpens an image using a convolution matrix.
|
* Sharpens an image using a convolution matrix.
|
||||||
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
|
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
|
||||||
* <TABLE border="1" cellspacing="0">
|
* <TABLE border="1" cellspacing="0">
|
||||||
* <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 pAmmount}</TD>
|
* <TR><TD>-{@code pAmount}</TD>
|
||||||
* <TD>4.0 * {@code pAmmount} + 1.0</TD>
|
* <TD>4.0 * {@code pAmount} + 1.0</TD>
|
||||||
* <TD>-{@code pAmmount}</TD></TR>
|
* <TD>-{@code pAmount}</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>
|
||||||
* </TABLE>
|
* </TABLE>
|
||||||
*
|
*
|
||||||
* @param pOriginal the BufferedImage to sharpen
|
* @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.
|
* @return a BufferedImage, containing the sharpened image.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmmount) {
|
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) {
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the convolution matrix
|
// Create the convolution matrix
|
||||||
float[] data = new float[] {
|
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
|
// Do the filtering
|
||||||
@@ -1174,7 +1145,7 @@ public final class ImageUtil {
|
|||||||
* Creates a blurred version of the given image.
|
* Creates a blurred version of the given image.
|
||||||
*
|
*
|
||||||
* @param pOriginal the original 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
|
* @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
|
// See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation
|
||||||
// Also see http://www.jhlabs.com/ip/blurring.html
|
// Also see http://www.jhlabs.com/ip/blurring.html
|
||||||
|
|
||||||
// TODO: Rethink... Fixed ammount and scale matrix instead?
|
// TODO: Rethink... Fixed amount and scale matrix instead?
|
||||||
// pAmmount = 1f - pAmmount;
|
// pAmount = 1f - pAmount;
|
||||||
// float pAmmount = 1f - pRadius;
|
// float pAmount = 1f - pRadius;
|
||||||
//
|
//
|
||||||
// // Normalize ammount
|
// // Normalize amount
|
||||||
// float normAmt = (1f - pAmmount) / 24;
|
// float normAmt = (1f - pAmount) / 24;
|
||||||
//
|
//
|
||||||
// // Create the convolution matrix
|
// // Create the convolution matrix
|
||||||
// float[] data = new float[] {
|
// float[] data = new float[] {
|
||||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
|
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
|
||||||
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
// 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, normAmt, normAmt * 2, normAmt, normAmt,
|
||||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
|
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
|
||||||
// };
|
// };
|
||||||
@@ -1380,18 +1351,18 @@ public final class ImageUtil {
|
|||||||
* Changes the contrast of the image
|
* Changes the contrast of the image
|
||||||
*
|
*
|
||||||
* @param pOriginal the {@code Image} to change
|
* @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.
|
* @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
|
// No change, return original
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create filter
|
// Create filter
|
||||||
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmmount);
|
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount);
|
||||||
|
|
||||||
// Return contrast adjusted image
|
// Return contrast adjusted image
|
||||||
return filter(pOriginal, filter);
|
return filter(pOriginal, filter);
|
||||||
@@ -1402,18 +1373,18 @@ public final class ImageUtil {
|
|||||||
* Changes the brightness of the original image.
|
* Changes the brightness of the original image.
|
||||||
*
|
*
|
||||||
* @param pOriginal the {@code Image} to change
|
* @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}
|
* @return an {@code Image}
|
||||||
*/
|
*/
|
||||||
public static Image brightness(Image pOriginal, float pAmmount) {
|
public static Image brightness(Image pOriginal, float pAmount) {
|
||||||
// No change, return original
|
// No change, return original
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create filter
|
// Create filter
|
||||||
RGBImageFilter filter = new BrightnessContrastFilter(pAmmount, 0f);
|
RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f);
|
||||||
|
|
||||||
// Return brightness adjusted image
|
// Return brightness adjusted image
|
||||||
return filter(pOriginal, filter);
|
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
|
* Note that transparent parts of the image might be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* 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
|
* Note that transparent parts of the image might be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* 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
|
* Note that transparent parts of the image will be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* 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.
|
* @param pTimeOut the time to wait, in milliseconds.
|
||||||
*
|
*
|
||||||
* @return true if the image was loaded successfully, false if an error
|
* @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)
|
* @see #waitForImages(Image[],long)
|
||||||
*/
|
*/
|
||||||
@@ -1788,7 +1759,7 @@ public final class ImageUtil {
|
|||||||
* @param pImages an array of Image objects to wait for.
|
* @param pImages an array of Image objects to wait for.
|
||||||
*
|
*
|
||||||
* @return true if the images was loaded successfully, false if an error
|
* @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)
|
* @see #waitForImages(Image[],long)
|
||||||
*/
|
*/
|
||||||
@@ -1804,7 +1775,7 @@ public final class ImageUtil {
|
|||||||
* @param pTimeOut the time to wait, in milliseconds
|
* @param pTimeOut the time to wait, in milliseconds
|
||||||
*
|
*
|
||||||
* @return true if the images was loaded successfully, false if an error
|
* @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) {
|
public static boolean waitForImages(Image[] pImages, long pTimeOut) {
|
||||||
// TODO: Need to make sure that we don't wait for the same image many times
|
// 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
|
// Create a local id for use with the mediatracker
|
||||||
int imageId;
|
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...
|
// NOTE: This is very experimental...
|
||||||
imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages);
|
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 pImage the image
|
||||||
* @param pFast if {@code true}, the method tests maximum 10 x 10 pixels,
|
* @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 pRGB1 color 1
|
||||||
* @param pRGB2 color 2
|
* @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 pColor color 1
|
||||||
* @param pOther color 2
|
* @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 0.0} will return the first color,
|
||||||
* a factor of {@code 1.0} will return the second.
|
* a factor of {@code 1.0} will return the second.
|
||||||
*
|
*
|
||||||
@@ -1987,50 +1951,4 @@ public final class ImageUtil {
|
|||||||
private static int clamp(float f) {
|
private static int clamp(float f) {
|
||||||
return (int) 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+90
-105
@@ -89,12 +89,14 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.awt.image.ColorModel;
|
import java.awt.image.ColorModel;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
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.
|
* to a variable number of colors.
|
||||||
* It can also render images into fixed color pallettes.
|
* It can also render images into fixed color pallettes.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -127,7 +129,7 @@ import java.util.List;
|
|||||||
* @author <A href="mailto:deweese@apache.org">Thomas DeWeese</A>
|
* @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:jun@oop-reserch.com">Jun Inamori</A>
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</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
|
* @see DiffusionDither
|
||||||
*/
|
*/
|
||||||
class IndexImage {
|
class IndexImage {
|
||||||
@@ -237,19 +239,22 @@ class IndexImage {
|
|||||||
if (this.val != val) {
|
if (this.val != val) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to define a cube of the colorspace. The cube can be split
|
* Used to define a cube of the color space. The cube can be split
|
||||||
* approximagely in half to generate two cubes.
|
* approximately in half to generate two cubes.
|
||||||
*/
|
*/
|
||||||
private static class Cube {
|
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;
|
boolean done = false;
|
||||||
List[] colors = null;
|
List<Counter>[] colors = null;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
static final int RED = 0;
|
static final int RED = 0;
|
||||||
static final int GRN = 1;
|
static final int GRN = 1;
|
||||||
@@ -261,7 +266,7 @@ class IndexImage {
|
|||||||
* @param colors contains the 3D color histogram to be subdivided
|
* @param colors contains the 3D color histogram to be subdivided
|
||||||
* @param count the total number of pixels in the 3D histogram.
|
* @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.colors = colors;
|
||||||
this.count = count;
|
this.count = count;
|
||||||
}
|
}
|
||||||
@@ -312,20 +317,27 @@ class IndexImage {
|
|||||||
c0 = RED;
|
c0 = RED;
|
||||||
c1 = GRN;
|
c1 = GRN;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cube ret;
|
Cube ret;
|
||||||
|
|
||||||
ret = splitChannel(splitChannel, c0, c1);
|
ret = splitChannel(splitChannel, c0, c1);
|
||||||
|
|
||||||
if (ret != null) {
|
if (ret != null) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = splitChannel(c0, splitChannel, c1);
|
ret = splitChannel(c0, splitChannel, c1);
|
||||||
|
|
||||||
if (ret != null) {
|
if (ret != null) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = splitChannel(c1, splitChannel, c0);
|
ret = splitChannel(c1, splitChannel, c0);
|
||||||
|
|
||||||
if (ret != null) {
|
if (ret != null) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
done = true;
|
done = true;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -381,16 +393,13 @@ class IndexImage {
|
|||||||
|
|
||||||
for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) {
|
for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) {
|
||||||
int idx = idx2 | (k << c1Sh4);
|
int idx = idx2 | (k << c1Sh4);
|
||||||
List v = colors[idx];
|
List<Counter> v = colors[idx];
|
||||||
|
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Iterator itr = v.iterator();
|
|
||||||
Counter c;
|
for (Counter c : v) {
|
||||||
|
|
||||||
while (itr.hasNext()) {
|
|
||||||
c = (Counter) itr.next();
|
|
||||||
val = c.val;
|
val = c.val;
|
||||||
vals[0] = (val & 0xFF0000) >> 16;
|
vals[0] = (val & 0xFF0000) >> 16;
|
||||||
vals[1] = (val & 0xFF00) >> 8;
|
vals[1] = (val & 0xFF00) >> 8;
|
||||||
@@ -425,7 +434,6 @@ class IndexImage {
|
|||||||
int c = counts[i];
|
int c = counts[i];
|
||||||
|
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
|
|
||||||
// No counts below this so move up bottom of cube.
|
// No counts below this so move up bottom of cube.
|
||||||
if ((tcount == 0) && (i < max[splitChannel])) {
|
if ((tcount == 0) && (i < max[splitChannel])) {
|
||||||
this.min[splitChannel] = i + 1;
|
this.min[splitChannel] = i + 1;
|
||||||
@@ -438,10 +446,8 @@ class IndexImage {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((half - tcount) <= ((tcount + c) - half)) {
|
if ((half - tcount) <= ((tcount + c) - half)) {
|
||||||
|
|
||||||
// Then lastAdd is a better top idx for this then i.
|
// Then lastAdd is a better top idx for this then i.
|
||||||
if (lastAdd == -1) {
|
if (lastAdd == -1) {
|
||||||
|
|
||||||
// No lower place to break.
|
// No lower place to break.
|
||||||
if (c == this.count) {
|
if (c == this.count) {
|
||||||
|
|
||||||
@@ -465,13 +471,11 @@ class IndexImage {
|
|||||||
else {
|
else {
|
||||||
if (i == this.max[splitChannel]) {
|
if (i == this.max[splitChannel]) {
|
||||||
if (c == this.count) {
|
if (c == this.count) {
|
||||||
|
|
||||||
// would move min up but that should
|
// would move min up but that should
|
||||||
// have happened already.
|
// have happened already.
|
||||||
return null;// no split to make.
|
return null;// no split to make.
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// Would like to break between i and i+1
|
// Would like to break between i and i+1
|
||||||
// but no i+1 so use lastAdd and i;
|
// but no i+1 so use lastAdd and i;
|
||||||
splitLo = lastAdd;
|
splitLo = lastAdd;
|
||||||
@@ -503,6 +507,7 @@ class IndexImage {
|
|||||||
ret.max[c0] = this.max[c0];
|
ret.max[c0] = this.max[c0];
|
||||||
ret.min[c1] = this.min[c1];
|
ret.min[c1] = this.min[c1];
|
||||||
ret.max[c1] = this.max[c1];
|
ret.max[c1] = this.max[c1];
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,6 +520,7 @@ class IndexImage {
|
|||||||
if (this.count == 0) {
|
if (this.count == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
float red = 0, grn = 0, blu = 0;
|
float red = 0, grn = 0, blu = 0;
|
||||||
int minR = min[0], minG = min[1], minB = min[2];
|
int minR = min[0], minG = min[1], minB = min[2];
|
||||||
int maxR = max[0], maxG = max[1], maxB = max[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++) {
|
for (int k = minIdx[2]; k <= maxIdx[2]; k++) {
|
||||||
int idx = idx2 | k;
|
int idx = idx2 | k;
|
||||||
List v = colors[idx];
|
List<Counter> v = colors[idx];
|
||||||
|
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Iterator itr = v.iterator();
|
|
||||||
Counter c;
|
|
||||||
|
|
||||||
while (itr.hasNext()) {
|
for (Counter c : v) {
|
||||||
c = (Counter) itr.next();
|
|
||||||
val = c.val;
|
val = c.val;
|
||||||
ired = (val & 0xFF0000) >> 16;
|
ired = (val & 0xFF0000) >> 16;
|
||||||
igrn = (val & 0x00FF00) >> 8;
|
igrn = (val & 0x00FF00) >> 8;
|
||||||
iblu = (val & 0x0000FF);
|
iblu = (val & 0x0000FF);
|
||||||
|
|
||||||
if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) {
|
if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) {
|
||||||
weight = (c.count / (float) this.count);
|
weight = (c.count / (float) this.count);
|
||||||
red += ((float) ired) * weight;
|
red += ((float) ired) * weight;
|
||||||
@@ -579,16 +583,13 @@ class IndexImage {
|
|||||||
* This version will be removed in a later version of the API.
|
* This version will be removed in a later version of the API.
|
||||||
*/
|
*/
|
||||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
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
|
* Gets an {@code IndexColorModel} from the given image. If the image has an
|
||||||
* {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel}
|
* {@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 pImage the image to get {@code IndexColorModel} from
|
||||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
* @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
|
// We now have at least a buffered image, create model from it
|
||||||
if (icm == null) {
|
if (icm == null) {
|
||||||
icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints);
|
icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints);
|
||||||
|
|
||||||
//System.out.println("IndexColorModel created from colors.");
|
|
||||||
}
|
}
|
||||||
else if (!(icm instanceof InverseColorMapIndexColorModel)) {
|
else if (!(icm instanceof InverseColorMapIndexColorModel)) {
|
||||||
// If possible, use faster code
|
// If possible, use faster code
|
||||||
//System.out.println("Wrappimg IndexColorModel in InverseColorMapIndexColorModel");
|
|
||||||
icm = new InverseColorMapIndexColorModel(icm);
|
icm = new InverseColorMapIndexColorModel(icm);
|
||||||
}
|
}
|
||||||
//else {
|
|
||||||
//System.out.println("Allredy InverseColorMapIndexColorModel");
|
|
||||||
//}
|
|
||||||
return icm;
|
return icm;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@code IndexColorModel} from the given image, using an adaptive
|
* Creates an {@code IndexColorModel} from the given image, using an adaptive
|
||||||
* pallete.
|
* palette.
|
||||||
*
|
*
|
||||||
* @param pImage the image to get {@code IndexColorModel} from
|
* @param pImage the image to get {@code IndexColorModel} from
|
||||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
||||||
@@ -674,7 +670,8 @@ class IndexImage {
|
|||||||
int height = pImage.getHeight();
|
int height = pImage.getHeight();
|
||||||
|
|
||||||
// Using 4 bits from R, G & B.
|
// 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
|
// Speedup, doesn't decrease image quality much
|
||||||
int step = 1;
|
int step = 1;
|
||||||
@@ -739,13 +736,16 @@ class IndexImage {
|
|||||||
while (numberOfCubes < pNumberOfColors) {
|
while (numberOfCubes < pNumberOfColors) {
|
||||||
while (cubes[fCube].isDone()) {
|
while (cubes[fCube].isDone()) {
|
||||||
fCube++;
|
fCube++;
|
||||||
|
|
||||||
if (fCube == numberOfCubes) {
|
if (fCube == numberOfCubes) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fCube == numberOfCubes) {
|
if (fCube == numberOfCubes) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cube cube = cubes[fCube];
|
Cube cube = cubes[fCube];
|
||||||
Cube newCube = cube.split();
|
Cube newCube = cube.split();
|
||||||
|
|
||||||
@@ -756,6 +756,7 @@ class IndexImage {
|
|||||||
cube = newCube;
|
cube = newCube;
|
||||||
newCube = tmp;
|
newCube = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
int j = fCube;
|
int j = fCube;
|
||||||
int count = cube.count;
|
int count = cube.count;
|
||||||
|
|
||||||
@@ -765,17 +766,19 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
cubes[j++] = cubes[i];
|
cubes[j++] = cubes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
cubes[j++] = cube;
|
cubes[j++] = cube;
|
||||||
count = newCube.count;
|
count = newCube.count;
|
||||||
|
|
||||||
while (j < numberOfCubes) {
|
while (j < numberOfCubes) {
|
||||||
if (cubes[j].count < count) {
|
if (cubes[j].count < count) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
j++;
|
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;
|
cubes[j/*++*/] = newCube;
|
||||||
numberOfCubes++;
|
numberOfCubes++;
|
||||||
}
|
}
|
||||||
@@ -803,15 +806,13 @@ class IndexImage {
|
|||||||
// - transparency added to all totally black colors?
|
// - transparency added to all totally black colors?
|
||||||
int numOfBits = 8;
|
int numOfBits = 8;
|
||||||
|
|
||||||
// -- haraldK, 20021024, as suggested by Thomas E Deweese
|
// -- haraldK, 20021024, as suggested by Thomas E. Deweese
|
||||||
// plus adding a transparent pixel
|
// plus adding a transparent pixel
|
||||||
IndexColorModel icm;
|
IndexColorModel icm;
|
||||||
if (useTransparency) {
|
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);
|
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//icm = new IndexColorModel(numOfBits, r.length, r, g, b);
|
|
||||||
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b);
|
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b);
|
||||||
}
|
}
|
||||||
return icm;
|
return icm;
|
||||||
@@ -820,7 +821,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@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.
|
* dither.
|
||||||
* <p/>
|
* <p/>
|
||||||
* The image returned is a new image, the input image is not modified.
|
* 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
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
* 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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -874,7 +875,7 @@ class IndexImage {
|
|||||||
* @param pPalette the Image to read color information from
|
* @param pPalette the Image to read color information from
|
||||||
* @param pMatte the background color, used where the original image was
|
* @param pMatte the background color, used where the original image was
|
||||||
* transparent
|
* 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
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@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
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -909,7 +910,7 @@ class IndexImage {
|
|||||||
* @param pNumberOfColors the number of colors for the image
|
* @param pNumberOfColors the number of colors for the image
|
||||||
* @param pMatte the background color, used where the original image was
|
* @param pMatte the background color, used where the original image was
|
||||||
* transparent
|
* 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
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -925,7 +926,7 @@ class IndexImage {
|
|||||||
* @see IndexColorModel
|
* @see IndexColorModel
|
||||||
*/
|
*/
|
||||||
public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) {
|
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
|
// won't have colors for potential faded transitions
|
||||||
IndexColorModel icm;
|
IndexColorModel icm;
|
||||||
|
|
||||||
@@ -946,7 +947,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
* {@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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints} parameter.
|
* {@code pHints} parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -985,15 +986,16 @@ class IndexImage {
|
|||||||
final int width = pImage.getWidth();
|
final int width = pImage.getWidth();
|
||||||
final int height = pImage.getHeight();
|
final int height = pImage.getHeight();
|
||||||
|
|
||||||
// Support transparancy?
|
// Support transparency?
|
||||||
boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE);
|
boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE);
|
||||||
|
|
||||||
// Create image with solid background
|
// Create image with solid background
|
||||||
BufferedImage solid = pImage;
|
BufferedImage solid = pImage;
|
||||||
|
|
||||||
if (pMatte != null) {// transparency doesn't really matter
|
if (pMatte != null) { // transparency doesn't really matter
|
||||||
solid = createSolid(pImage, pMatte);
|
solid = createSolid(pImage, pMatte);
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage indexed;
|
BufferedImage indexed;
|
||||||
|
|
||||||
// Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default
|
// Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default
|
||||||
@@ -1044,12 +1046,12 @@ class IndexImage {
|
|||||||
finally {
|
finally {
|
||||||
g2d.dispose();
|
g2d.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transparency support, this approach seems lame, but it's the only
|
// Transparency support, this approach seems lame, but it's the only
|
||||||
// solution I've found until now (that actually works).
|
// solution I've found until now (that actually works).
|
||||||
// Got anything to do with isPremultiplied? Hmm...
|
|
||||||
if (transparency) {
|
if (transparency) {
|
||||||
// Re-apply the alpha-channel of the original image
|
// Re-apply the alpha-channel of the original image
|
||||||
applyAlpha(indexed, pImage);
|
applyAlpha(indexed, pImage);
|
||||||
@@ -1062,7 +1064,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1070,7 +1072,7 @@ class IndexImage {
|
|||||||
*
|
*
|
||||||
* @param pImage the BufferedImage to index
|
* @param pImage the BufferedImage to index
|
||||||
* @param pNumberOfColors the number of colors for the image
|
* @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
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@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
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
* {@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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1123,7 +1125,7 @@ class IndexImage {
|
|||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
* 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
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1131,7 +1133,7 @@ class IndexImage {
|
|||||||
*
|
*
|
||||||
* @param pImage the BufferedImage to index
|
* @param pImage the BufferedImage to index
|
||||||
* @param pPalette the Image to read color information from
|
* @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
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -1183,14 +1185,14 @@ class IndexImage {
|
|||||||
* @param pAlpha the image containing the alpha
|
* @param pAlpha the image containing the alpha
|
||||||
*/
|
*/
|
||||||
private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) {
|
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 y = 0; y < pAlpha.getHeight(); y++) {
|
||||||
for (int x = 0; x < pAlpha.getWidth(); x++) {
|
for (int x = 0; x < pAlpha.getWidth(); x++) {
|
||||||
|
|
||||||
// Get alpha component of pixel, if less than 25% opaque
|
// Get alpha component of pixel, if less than 25% opaque
|
||||||
// (0x40 = 64 => 25% of 256), the pixel will be transparent
|
// (0x40 = 64 => 25% of 256), the pixel will be transparent
|
||||||
if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) {
|
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.
|
* This class is also a command-line utility.
|
||||||
*/
|
*/
|
||||||
public static void main(String pArgs[]) {
|
public static void main(String pArgs[]) {
|
||||||
|
|
||||||
// Defaults
|
// Defaults
|
||||||
int argIdx = 0;
|
int argIdx = 0;
|
||||||
int speedTest = -1;
|
int speedTest = -1;
|
||||||
@@ -1237,14 +1238,13 @@ class IndexImage {
|
|||||||
speedTest = 10;
|
speedTest = 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
|
|
||||||
overWrite = true;
|
overWrite = true;
|
||||||
argIdx++;
|
argIdx++;
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
numColors = Integer.parseInt(pArgs[argIdx++]);
|
numColors = Integer.parseInt(pArgs[argIdx++]);
|
||||||
}
|
}
|
||||||
@@ -1253,34 +1253,28 @@ class IndexImage {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
gray = true;
|
gray = true;
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
numColors = 2;
|
numColors = 2;
|
||||||
monochrome = true;
|
monochrome = true;
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
dither = pArgs[argIdx++];
|
dither = pArgs[argIdx++];
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
paletteFileName = pArgs[argIdx++];
|
paletteFileName = pArgs[argIdx++];
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
quality = pArgs[argIdx++];
|
quality = pArgs[argIdx++];
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
try {
|
try {
|
||||||
background = StringUtil.toColor(pArgs[argIdx++]);
|
background = StringUtil.toColor(pArgs[argIdx++]);
|
||||||
@@ -1290,18 +1284,15 @@ class IndexImage {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
transparency = true;
|
transparency = true;
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
format = StringUtil.toLowerCase(pArgs[argIdx++]);
|
format = StringUtil.toLowerCase(pArgs[argIdx++]);
|
||||||
}
|
}
|
||||||
else
|
else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
|
||||||
if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
|
|
||||||
argIdx++;
|
argIdx++;
|
||||||
|
|
||||||
// Setting errArgs to true, to print usage
|
// Setting errArgs to true, to print usage
|
||||||
@@ -1321,6 +1312,7 @@ class IndexImage {
|
|||||||
? ", "
|
? ", "
|
||||||
: "\n"));
|
: "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
System.err.print("Output format names: ");
|
System.err.print("Output format names: ");
|
||||||
String[] writers = ImageIO.getWriterFormatNames();
|
String[] writers = ImageIO.getWriterFormatNames();
|
||||||
|
|
||||||
@@ -1333,7 +1325,7 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read in image
|
// Read in image
|
||||||
java.io.File in = new java.io.File(pArgs[argIdx++]);
|
File in = new File(pArgs[argIdx++]);
|
||||||
|
|
||||||
if (!in.exists()) {
|
if (!in.exists()) {
|
||||||
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
||||||
@@ -1341,10 +1333,10 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read palette if needed
|
// Read palette if needed
|
||||||
java.io.File paletteFile = null;
|
File paletteFile = null;
|
||||||
|
|
||||||
if (paletteFileName != null) {
|
if (paletteFileName != null) {
|
||||||
paletteFile = new java.io.File(paletteFileName);
|
paletteFile = new File(paletteFileName);
|
||||||
if (!paletteFile.exists()) {
|
if (!paletteFile.exists()) {
|
||||||
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
||||||
System.exit(5);
|
System.exit(5);
|
||||||
@@ -1352,10 +1344,10 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we can write
|
// Make sure we can write
|
||||||
java.io.File out;
|
File out;
|
||||||
|
|
||||||
if (argIdx < pArgs.length) {
|
if (argIdx < pArgs.length) {
|
||||||
out = new java.io.File(pArgs[argIdx/*++*/]);
|
out = new File(pArgs[argIdx/*++*/]);
|
||||||
|
|
||||||
// Get format from file extension
|
// Get format from file extension
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
@@ -1363,7 +1355,6 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// Create new file in current dir, same name + format extension
|
// Create new file in current dir, same name + format extension
|
||||||
String baseName = FileUtil.getBasename(in);
|
String baseName = FileUtil.getBasename(in);
|
||||||
|
|
||||||
@@ -1371,8 +1362,9 @@ class IndexImage {
|
|||||||
if (format == null) {
|
if (format == null) {
|
||||||
format = "png";
|
format = "png";
|
||||||
}
|
}
|
||||||
out = new java.io.File(baseName + '.' + format);
|
out = new File(baseName + '.' + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!overWrite && out.exists()) {
|
if (!overWrite && out.exists()) {
|
||||||
System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!");
|
System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!");
|
||||||
System.exit(5);
|
System.exit(5);
|
||||||
@@ -1396,12 +1388,12 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (java.io.IOException ioe) {
|
catch (IOException ioe) {
|
||||||
ioe.printStackTrace(System.err);
|
ioe.printStackTrace(System.err);
|
||||||
System.exit(5);
|
System.exit(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mHints
|
// Create hints
|
||||||
int hints = DITHER_DEFAULT;
|
int hints = DITHER_DEFAULT;
|
||||||
|
|
||||||
if ("DIFFUSION".equalsIgnoreCase(dither)) {
|
if ("DIFFUSION".equalsIgnoreCase(dither)) {
|
||||||
@@ -1441,16 +1433,14 @@ class IndexImage {
|
|||||||
///////////////////////////////
|
///////////////////////////////
|
||||||
// Index
|
// Index
|
||||||
long start = 0;
|
long start = 0;
|
||||||
long end;
|
|
||||||
|
|
||||||
if (speedTest > 0) {
|
if (speedTest > 0) {
|
||||||
|
|
||||||
// SPEED TESTING
|
// SPEED TESTING
|
||||||
System.out.println("Measuring speed!");
|
System.out.println("Measuring speed!");
|
||||||
start = System.currentTimeMillis();
|
start = System.currentTimeMillis();
|
||||||
|
|
||||||
// END SPEED TESTING
|
// END SPEED TESTING
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage indexed;
|
BufferedImage indexed;
|
||||||
IndexColorModel colors;
|
IndexColorModel colors;
|
||||||
|
|
||||||
@@ -1459,7 +1449,6 @@ class IndexImage {
|
|||||||
colors = MonochromeColorModel.getInstance();
|
colors = MonochromeColorModel.getInstance();
|
||||||
}
|
}
|
||||||
else if (gray) {
|
else if (gray) {
|
||||||
|
|
||||||
//indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY);
|
//indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY);
|
||||||
image = ImageUtil.toBuffered(ImageUtil.grayscale(image));
|
image = ImageUtil.toBuffered(ImageUtil.grayscale(image));
|
||||||
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
||||||
@@ -1470,7 +1459,6 @@ class IndexImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (paletteImg != null) {
|
else if (paletteImg != null) {
|
||||||
|
|
||||||
// Get palette from image
|
// Get palette from image
|
||||||
indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB),
|
indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB),
|
||||||
colors = getIndexColorModel(paletteImg, numColors, hints), background, hints);
|
colors = getIndexColorModel(paletteImg, numColors, hints), background, hints);
|
||||||
@@ -1479,12 +1467,10 @@ class IndexImage {
|
|||||||
image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
|
image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
|
||||||
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speedTest > 0) {
|
if (speedTest > 0) {
|
||||||
|
|
||||||
// SPEED TESTING
|
// SPEED TESTING
|
||||||
end = System.currentTimeMillis();
|
System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
System.out.println("Color selection + dither: " + (end - start) + " ms");
|
|
||||||
|
|
||||||
// END SPEED TESTING
|
// END SPEED TESTING
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1494,11 +1480,11 @@ class IndexImage {
|
|||||||
System.err.println("No writer for format: \"" + format + "\"!");
|
System.err.println("No writer for format: \"" + format + "\"!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (java.io.IOException ioe) {
|
catch (IOException ioe) {
|
||||||
ioe.printStackTrace(System.err);
|
ioe.printStackTrace(System.err);
|
||||||
}
|
}
|
||||||
if (speedTest > 0) {
|
|
||||||
|
|
||||||
|
if (speedTest > 0) {
|
||||||
// SPEED TESTING
|
// SPEED TESTING
|
||||||
System.out.println("Measuring speed!");
|
System.out.println("Measuring speed!");
|
||||||
|
|
||||||
@@ -1513,17 +1499,16 @@ class IndexImage {
|
|||||||
for (int i = 0; i < speedTest; i++) {
|
for (int i = 0; i < speedTest; i++) {
|
||||||
start = System.currentTimeMillis();
|
start = System.currentTimeMillis();
|
||||||
getIndexedImage(image, colors, background, hints);
|
getIndexedImage(image, colors, background, hints);
|
||||||
end = System.currentTimeMillis();
|
time += (System.currentTimeMillis() - start);
|
||||||
time += (end - start);
|
|
||||||
System.out.print('.');
|
System.out.print('.');
|
||||||
if ((i + 1) % 10 == 0) {
|
if ((i + 1) % 10 == 0) {
|
||||||
System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms");
|
System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("\nDither only:");
|
System.out.println("\nDither only:");
|
||||||
System.out.println("Total time (" + speedTest + " invocations): " + time + "ms");
|
System.out.println("Total time (" + speedTest + " invocations): " + time + "ms");
|
||||||
System.out.println("Average: " + time / speedTest + "ms");
|
System.out.println("Average: " + time / speedTest + "ms");
|
||||||
|
|
||||||
// END SPEED TESTING
|
// END SPEED TESTING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+27
-27
@@ -72,12 +72,12 @@ class InverseColorMap {
|
|||||||
*/
|
*/
|
||||||
final static int MAXQUANTVAL = 1 << 5;
|
final static int MAXQUANTVAL = 1 << 5;
|
||||||
|
|
||||||
byte[] mRGBMapByte;
|
byte[] rgbMapByte;
|
||||||
int[] mRGBMapInt;
|
int[] rgbMapInt;
|
||||||
int mNumColors;
|
int numColors;
|
||||||
int mMaxColor;
|
int maxColor;
|
||||||
byte[] mInverseRGB; // inverse rgb color map
|
byte[] inverseRGB; // inverse rgb color map
|
||||||
int mTransparentIndex = -1;
|
int transparentIndex = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pRGBColorMap the rgb color map to create inverse color map for.
|
* @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
|
* @param pTransparent the index of the transparent pixel in the map
|
||||||
*/
|
*/
|
||||||
InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
|
InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
|
||||||
mRGBMapByte = pRGBColorMap;
|
rgbMapByte = pRGBColorMap;
|
||||||
mNumColors = mRGBMapByte.length / 4;
|
numColors = rgbMapByte.length / 4;
|
||||||
mTransparentIndex = pTransparent;
|
transparentIndex = pTransparent;
|
||||||
|
|
||||||
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||||
initIRGB(new int[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
|
* @param pTransparent the index of the transparent pixel in the map
|
||||||
*/
|
*/
|
||||||
InverseColorMap(int[] pRGBColorMap, int pTransparent) {
|
InverseColorMap(int[] pRGBColorMap, int pTransparent) {
|
||||||
mRGBMapInt = pRGBColorMap;
|
rgbMapInt = pRGBColorMap;
|
||||||
mNumColors = mRGBMapInt.length;
|
numColors = rgbMapInt.length;
|
||||||
mTransparentIndex = pTransparent;
|
transparentIndex = pTransparent;
|
||||||
|
|
||||||
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||||
initIRGB(new int[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 xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors
|
||||||
final int xsqr2 = xsqr + xsqr;
|
final int xsqr2 = xsqr + xsqr;
|
||||||
|
|
||||||
for (int i = 0; i < mNumColors; ++i) {
|
for (int i = 0; i < numColors; ++i) {
|
||||||
if (i == mTransparentIndex) {
|
if (i == transparentIndex) {
|
||||||
// Skip the transparent pixel
|
// Skip the transparent pixel
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -141,15 +141,15 @@ class InverseColorMap {
|
|||||||
int blue, b, bdist, binc, bxx;
|
int blue, b, bdist, binc, bxx;
|
||||||
|
|
||||||
// HaraldK 20040801: Added support for int[]
|
// HaraldK 20040801: Added support for int[]
|
||||||
if (mRGBMapByte != null) {
|
if (rgbMapByte != null) {
|
||||||
red = mRGBMapByte[i * 4] & 0xFF;
|
red = rgbMapByte[i * 4] & 0xFF;
|
||||||
green = mRGBMapByte[i * 4 + 1] & 0xFF;
|
green = rgbMapByte[i * 4 + 1] & 0xFF;
|
||||||
blue = mRGBMapByte[i * 4 + 2] & 0xFF;
|
blue = rgbMapByte[i * 4 + 2] & 0xFF;
|
||||||
}
|
}
|
||||||
else if (mRGBMapInt != null) {
|
else if (rgbMapInt != null) {
|
||||||
red = (mRGBMapInt[i] >> 16) & 0xFF;
|
red = (rgbMapInt[i] >> 16) & 0xFF;
|
||||||
green = (mRGBMapInt[i] >> 8) & 0xFF;
|
green = (rgbMapInt[i] >> 8) & 0xFF;
|
||||||
blue = mRGBMapInt[i] & 0xFF;
|
blue = rgbMapInt[i] & 0xFF;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalStateException("colormap == null");
|
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) {
|
for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) {
|
||||||
if (i == 0 || pTemp[rgbI] > bdist) {
|
if (i == 0 || pTemp[rgbI] > bdist) {
|
||||||
pTemp[rgbI] = bdist;
|
pTemp[rgbI] = bdist;
|
||||||
mInverseRGB[rgbI] = (byte) i;
|
inverseRGB[rgbI] = (byte) i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ class InverseColorMap {
|
|||||||
* created inverse color map.
|
* created inverse color map.
|
||||||
*/
|
*/
|
||||||
public final int getIndexNearest(int pColor) {
|
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 >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) +
|
||||||
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ class InverseColorMap {
|
|||||||
*/
|
*/
|
||||||
public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
|
public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
|
||||||
// NOTE: the third line in expression for blue is shifting DOWN not UP.
|
// 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) +
|
((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) +
|
||||||
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
||||||
}
|
}
|
||||||
+40
-47
@@ -30,6 +30,7 @@
|
|||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.DataBuffer;
|
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
|
* 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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author $Author: haku $
|
* @author $Author: haku $
|
||||||
@@ -46,13 +47,13 @@ import java.awt.image.IndexColorModel;
|
|||||||
*/
|
*/
|
||||||
public class InverseColorMapIndexColorModel extends IndexColorModel {
|
public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||||
|
|
||||||
protected int mRGBs[];
|
protected int rgbs[];
|
||||||
protected int mMapSize;
|
protected int mapSize;
|
||||||
|
|
||||||
protected InverseColorMap mInverseMap = null;
|
protected InverseColorMap inverseMap = null;
|
||||||
private final static int ALPHA_THRESHOLD = 0x80;
|
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 WHITE = 0x00FFFFFF;
|
||||||
private final static int RGB_MASK = 0x00FFFFFF;
|
private final static int RGB_MASK = 0x00FFFFFF;
|
||||||
|
|
||||||
@@ -60,37 +61,36 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
||||||
* {@code IndexColorModel}.
|
* {@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) {
|
public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) {
|
||||||
this(pColorModel, getRGBs(pColorModel));
|
this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
||||||
// times. What is wrong with protected?!
|
// times. What is wrong with protected?!
|
||||||
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
||||||
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(),
|
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels());
|
||||||
pRGBs, 0,
|
|
||||||
ImageUtil.getTransferType(pColorModel),
|
|
||||||
pColorModel.getValidPixels());
|
|
||||||
|
|
||||||
mRGBs = pRGBs;
|
rgbs = pRGBs;
|
||||||
mMapSize = mRGBs.length;
|
mapSize = rgbs.length;
|
||||||
|
|
||||||
mInverseMap = new InverseColorMap(mRGBs);
|
inverseMap = new InverseColorMap(rgbs);
|
||||||
mWhiteIndex = getWhiteIndex();
|
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}.
|
* {@code IndexColorModel}.
|
||||||
*
|
*
|
||||||
* @param pColorModel the indec colormodel to get RGB values from
|
* @param pColorModel the indexed color model to get RGB values from
|
||||||
* @return the RGB colormap
|
* @return the RGB color map
|
||||||
*/
|
*/
|
||||||
private static int[] getRGBs(IndexColorModel pColorModel) {
|
private static int[] getRGBs(IndexColorModel pColorModel) {
|
||||||
int[] rgb = new int[pColorModel.getMapSize()];
|
int[] rgb = new int[pColorModel.getMapSize()];
|
||||||
pColorModel.getRGBs(rgb);
|
pColorModel.getRGBs(rgb);
|
||||||
|
|
||||||
return rgb;
|
return rgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,15 +111,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
*
|
*
|
||||||
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
|
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
|
||||||
*/
|
*/
|
||||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs,
|
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, int pStart, boolean pAlpha, int pTransparentIndex, int pTransferType) {
|
||||||
int pStart, boolean pAlpha, int pTransparentIndex,
|
|
||||||
int pTransferType) {
|
|
||||||
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
|
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
|
||||||
mRGBs = getRGBs(this);
|
rgbs = getRGBs(this);
|
||||||
mMapSize = mRGBs.length;
|
mapSize = rgbs.length;
|
||||||
|
|
||||||
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
|
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||||
mWhiteIndex = getWhiteIndex();
|
whiteIndex = getWhiteIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,15 +136,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
*
|
*
|
||||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
|
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
|
||||||
*/
|
*/
|
||||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
|
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues, int pTransparentIndex) {
|
||||||
byte[] pReds, byte[] pGreens, byte[] pBlues,
|
|
||||||
int pTransparentIndex) {
|
|
||||||
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
|
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
|
||||||
mRGBs = getRGBs(this);
|
rgbs = getRGBs(this);
|
||||||
mMapSize = mRGBs.length;
|
mapSize = rgbs.length;
|
||||||
|
|
||||||
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
|
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||||
mWhiteIndex = getWhiteIndex();
|
whiteIndex = getWhiteIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,19 +160,18 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
*
|
*
|
||||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
|
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
|
||||||
*/
|
*/
|
||||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
|
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues) {
|
||||||
byte[] pReds, byte[] pGreens, byte[] pBlues) {
|
|
||||||
super(pNumBits, pSize, pReds, pGreens, pBlues);
|
super(pNumBits, pSize, pReds, pGreens, pBlues);
|
||||||
mRGBs = getRGBs(this);
|
rgbs = getRGBs(this);
|
||||||
mMapSize = mRGBs.length;
|
mapSize = rgbs.length;
|
||||||
|
|
||||||
mInverseMap = new InverseColorMap(mRGBs);
|
inverseMap = new InverseColorMap(rgbs);
|
||||||
mWhiteIndex = getWhiteIndex();
|
whiteIndex = getWhiteIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getWhiteIndex() {
|
private int getWhiteIndex() {
|
||||||
for (int i = 0; i < mRGBs.length; i++) {
|
for (int i = 0; i < rgbs.length; i++) {
|
||||||
int color = mRGBs[i];
|
int color = rgbs[i];
|
||||||
if ((color & RGB_MASK) == WHITE) {
|
if ((color & RGB_MASK) == WHITE) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -244,7 +239,6 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Object getDataElements(int rgb, Object pixel) {
|
public Object getDataElements(int rgb, Object pixel) {
|
||||||
|
|
||||||
int alpha = (rgb>>>24);
|
int alpha = (rgb>>>24);
|
||||||
|
|
||||||
int pix;
|
int pix;
|
||||||
@@ -253,11 +247,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int color = rgb & RGB_MASK;
|
int color = rgb & RGB_MASK;
|
||||||
if (color == WHITE && mWhiteIndex != -1) {
|
if (color == WHITE && whiteIndex != -1) {
|
||||||
pix = mWhiteIndex;
|
pix = whiteIndex;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pix = mInverseMap.getIndexNearest(color);
|
pix = inverseMap.getIndexNearest(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,8 +291,7 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
shortObj[0] = (short) pix;
|
shortObj[0] = (short) pix;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new UnsupportedOperationException("This method has not been " +
|
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
|
||||||
"implemented for transferType " + transferType);
|
|
||||||
}
|
}
|
||||||
return pixel;
|
return pixel;
|
||||||
}
|
}
|
||||||
+5
-5
@@ -54,11 +54,11 @@ final class MagickAccelerator {
|
|||||||
|
|
||||||
private static final int RESAMPLE_OP = 0;
|
private static final int RESAMPLE_OP = 0;
|
||||||
|
|
||||||
private static Class[] sNativeOp = new Class[1];
|
private static Class[] nativeOp = new Class[1];
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
sNativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
|
nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException e) {
|
catch (ClassNotFoundException e) {
|
||||||
System.err.println("Could not find class: " + e);
|
System.err.println("Could not find class: " + e);
|
||||||
@@ -94,8 +94,8 @@ final class MagickAccelerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int getNativeOpIndex(Class pOpClass) {
|
private static int getNativeOpIndex(Class pOpClass) {
|
||||||
for (int i = 0; i < sNativeOp.length; i++) {
|
for (int i = 0; i < nativeOp.length; i++) {
|
||||||
if (pOpClass == sNativeOp[i]) {
|
if (pOpClass == nativeOp[i]) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ final class MagickAccelerator {
|
|||||||
switch (getNativeOpIndex(pOperation.getClass())) {
|
switch (getNativeOpIndex(pOperation.getClass())) {
|
||||||
case RESAMPLE_OP:
|
case RESAMPLE_OP:
|
||||||
ResampleOp resample = (ResampleOp) pOperation;
|
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
|
// NOTE: If output parameter is non-null, we have to return that
|
||||||
// image, instead of result
|
// image, instead of result
|
||||||
+62
@@ -32,6 +32,8 @@ import magick.*;
|
|||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,7 +162,11 @@ public final class MagickUtil {
|
|||||||
image = rgbToBuffered(pImage, true);
|
image = rgbToBuffered(pImage, true);
|
||||||
break;
|
break;
|
||||||
case ImageType.ColorSeparationType:
|
case ImageType.ColorSeparationType:
|
||||||
|
image = cmykToBuffered(pImage, false);
|
||||||
|
break;
|
||||||
case ImageType.ColorSeparationMatteType:
|
case ImageType.ColorSeparationMatteType:
|
||||||
|
image = cmykToBuffered(pImage, true);
|
||||||
|
break;
|
||||||
case ImageType.OptimizeType:
|
case ImageType.OptimizeType:
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
|
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);
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -33,7 +33,7 @@ import java.awt.image.*;
|
|||||||
/**
|
/**
|
||||||
* Monochrome B/W color model.
|
* Monochrome B/W color model.
|
||||||
*
|
*
|
||||||
* @author Harald Kuhr
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
*/
|
*/
|
||||||
public class MonochromeColorModel extends IndexColorModel {
|
public class MonochromeColorModel extends IndexColorModel {
|
||||||
|
|
||||||
+24
-24
@@ -48,37 +48,37 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
|||||||
// TODO: support more raster types/color models
|
// TODO: support more raster types/color models
|
||||||
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
|
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
|
||||||
|
|
||||||
final private int mPixelSizeX;
|
final private int pixelSizeX;
|
||||||
final private int mPixelSizeY;
|
final private int pixelSizeY;
|
||||||
|
|
||||||
private Rectangle mSourceRegion;
|
private Rectangle sourceRegion;
|
||||||
|
|
||||||
public PixelizeOp(final int pPixelSize) {
|
public PixelizeOp(final int pPixelSize) {
|
||||||
this(pPixelSize, pPixelSize);
|
this(pPixelSize, pPixelSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
|
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
|
||||||
mPixelSizeX = pPixelSizeX;
|
pixelSizeX = pPixelSizeX;
|
||||||
mPixelSizeY = pPixelSizeY;
|
pixelSizeY = pPixelSizeY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle getSourceRegion() {
|
public Rectangle getSourceRegion() {
|
||||||
if (mSourceRegion == null) {
|
if (sourceRegion == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return new Rectangle(mSourceRegion);
|
return new Rectangle(sourceRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||||
if (pSourceRegion == null) {
|
if (pSourceRegion == null) {
|
||||||
mSourceRegion = null;
|
sourceRegion = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (mSourceRegion == null) {
|
if (sourceRegion == null) {
|
||||||
mSourceRegion = new Rectangle(pSourceRegion);
|
sourceRegion = new Rectangle(pSourceRegion);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mSourceRegion.setBounds(pSourceRegion);
|
sourceRegion.setBounds(pSourceRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,11 +107,11 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
|||||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||||
//System.out.println("src: " + src);
|
//System.out.println("src: " + src);
|
||||||
//System.out.println("dest: " + dest);
|
//System.out.println("dest: " + dest);
|
||||||
if (mSourceRegion != null) {
|
if (sourceRegion != null) {
|
||||||
int cx = mSourceRegion.x;
|
int cx = sourceRegion.x;
|
||||||
int cy = mSourceRegion.y;
|
int cy = sourceRegion.y;
|
||||||
int cw = mSourceRegion.width;
|
int cw = sourceRegion.width;
|
||||||
int ch = mSourceRegion.height;
|
int ch = sourceRegion.height;
|
||||||
|
|
||||||
boolean same = src == dest;
|
boolean same = src == dest;
|
||||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
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 width = src.getWidth();
|
||||||
final int height = src.getHeight();
|
final int height = src.getHeight();
|
||||||
int w = (width + mPixelSizeX - 1) / mPixelSizeX;
|
int w = (width + pixelSizeX - 1) / pixelSizeX;
|
||||||
int h = (height + mPixelSizeY - 1) / mPixelSizeY;
|
int h = (height + pixelSizeY - 1) / pixelSizeY;
|
||||||
|
|
||||||
final boolean oddX = width % w != 0;
|
final boolean oddX = width % w != 0;
|
||||||
final boolean oddY = height % h != 0;
|
final boolean oddY = height % h != 0;
|
||||||
@@ -156,23 +156,23 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
|||||||
|
|
||||||
for (int y = 0; y < h; y++) {
|
for (int y = 0; y < h; y++) {
|
||||||
if (!oddY || y + 1 < h) {
|
if (!oddY || y + 1 < h) {
|
||||||
scanH = mPixelSizeY;
|
scanH = pixelSizeY;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
scanH = height - (y * mPixelSizeY);
|
scanH = height - (y * pixelSizeY);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int x = 0; x < w; x++) {
|
for (int x = 0; x < w; x++) {
|
||||||
if (!oddX || x + 1 < w) {
|
if (!oddX || x + 1 < w) {
|
||||||
scanW = mPixelSizeX;
|
scanW = pixelSizeX;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
scanW = width - (x * mPixelSizeX);
|
scanW = width - (x * pixelSizeX);
|
||||||
}
|
}
|
||||||
final int pixelCount = scanW * scanH;
|
final int pixelCount = scanW * scanH;
|
||||||
final int pixelLength = pixelCount * dataElements;
|
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..
|
// NOTE: These are not neccessarily ARGB..
|
||||||
double valueA = 0.0;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*/
|
/*/
|
||||||
+112
-122
@@ -52,15 +52,12 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.SystemUtil;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resamples (scales) a {@code BufferedImage} to a new width and height, using
|
* Resamples (scales) a {@code BufferedImage} to a new width and height, using
|
||||||
* high performance and high quality algorithms.
|
* high performance and high quality algorithms.
|
||||||
@@ -138,7 +135,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
// MagickAccelerator to work consistently (see magick.FilterType).
|
// 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;
|
public final static int FILTER_UNDEFINED = 0;
|
||||||
/**
|
/**
|
||||||
@@ -194,11 +191,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
/**
|
/**
|
||||||
* Mitchell interpolation. High quality.
|
* 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.
|
* 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.
|
* Blackman-Bessel interpolation. High quality.
|
||||||
*/
|
*/
|
||||||
@@ -291,25 +288,23 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
|
|
||||||
// Member variables
|
// Member variables
|
||||||
// Package access, to allow access from MagickAccelerator
|
// Package access, to allow access from MagickAccelerator
|
||||||
int mWidth;
|
int width;
|
||||||
int mHeight;
|
int height;
|
||||||
|
|
||||||
int mFilterType;
|
int filterType;
|
||||||
private static final boolean TRANSFORM_OP_BICUBIC_SUPPORT = SystemUtil.isFieldAvailable(AffineTransformOp.class.getName(), "TYPE_BICUBIC");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RendereingHints.Key implementation, works only with Value values.
|
* RendereingHints.Key implementation, works only with Value values.
|
||||||
*/
|
*/
|
||||||
// TODO: Move to abstract class AbstractBufferedImageOp?
|
// TODO: Move to abstract class AbstractBufferedImageOp?
|
||||||
static class Key extends RenderingHints.Key {
|
static class Key extends RenderingHints.Key {
|
||||||
|
|
||||||
static int sIndex = 10000;
|
static int sIndex = 10000;
|
||||||
|
|
||||||
private final String mName;
|
private final String name;
|
||||||
|
|
||||||
public Key(final String pName) {
|
public Key(final String pName) {
|
||||||
super(sIndex++);
|
super(sIndex++);
|
||||||
mName = pName;
|
name = pName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCompatibleValue(Object pValue) {
|
public boolean isCompatibleValue(Object pValue) {
|
||||||
@@ -317,36 +312,35 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
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
|
// TODO: Extract abstract Value class, and move to AbstractBufferedImageOp
|
||||||
static final class Value {
|
static final class Value {
|
||||||
final private RenderingHints.Key mKey;
|
final private RenderingHints.Key key;
|
||||||
final private String mName;
|
final private String name;
|
||||||
final private int mType;
|
final private int type;
|
||||||
|
|
||||||
public Value(final RenderingHints.Key pKey, final String pName, final int pType) {
|
public Value(final RenderingHints.Key pKey, final String pName, final int pType) {
|
||||||
mKey = pKey;
|
key = pKey;
|
||||||
mName = pName;
|
name = pName;
|
||||||
validateFilterType(pType);
|
type = validateFilterType(pType);
|
||||||
mType = pType;// TODO: test for duplicates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCompatibleKey(Key pKey) {
|
public boolean isCompatibleKey(Key pKey) {
|
||||||
return pKey == mKey;
|
return pKey == key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFilterType() {
|
public int getFilterType() {
|
||||||
return mType;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
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
|
* Creates a {@code ResampleOp} that will resample input images to the
|
||||||
* given width and height, using the default interpolation filter.
|
* given width and height, using the default interpolation filter.
|
||||||
*
|
*
|
||||||
* @param pWidth width of the resampled image
|
* @param width width of the re-sampled image
|
||||||
* @param pHeight height of the resampled image
|
* @param height height of the re-sampled image
|
||||||
*/
|
*/
|
||||||
public ResampleOp(int pWidth, int pHeight) {
|
public ResampleOp(int width, int height) {
|
||||||
this(pWidth, pHeight, FILTER_UNDEFINED);
|
this(width, height, FILTER_UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,41 +388,40 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
* </ul>
|
* </ul>
|
||||||
* Other hints have no effect on this filter.
|
* Other hints have no effect on this filter.
|
||||||
*
|
*
|
||||||
* @param pWidth width of the resampled image
|
* @param width width of the re-sampled image
|
||||||
* @param pHeight height of the resampled image
|
* @param height height of the re-sampled image
|
||||||
* @param pHints rendering hints, affecting interpolation algorithm
|
* @param hints rendering hints, affecting interpolation algorithm
|
||||||
* @see #KEY_RESAMPLE_INTERPOLATION
|
* @see #KEY_RESAMPLE_INTERPOLATION
|
||||||
* @see RenderingHints#KEY_INTERPOLATION
|
* @see RenderingHints#KEY_INTERPOLATION
|
||||||
* @see RenderingHints#KEY_RENDERING
|
* @see RenderingHints#KEY_RENDERING
|
||||||
* @see RenderingHints#KEY_COLOR_RENDERING
|
* @see RenderingHints#KEY_COLOR_RENDERING
|
||||||
*/
|
*/
|
||||||
public ResampleOp(int pWidth, int pHeight, RenderingHints pHints) {
|
public ResampleOp(int width, int height, RenderingHints hints) {
|
||||||
this(pWidth, pHeight, getFilterType(pHints));
|
this(width, height, getFilterType(hints));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ResampleOp} that will resample input images to the
|
* Creates a {@code ResampleOp} that will resample input images to the
|
||||||
* given width and height, using the given interpolation filter.
|
* given width and height, using the given interpolation filter.
|
||||||
*
|
*
|
||||||
* @param pWidth width of the resampled image
|
* @param width width of the re-sampled image
|
||||||
* @param pHeight height of the resampled image
|
* @param height height of the re-sampled image
|
||||||
* @param pFilterType interpolation filter algorithm
|
* @param filterType interpolation filter algorithm
|
||||||
* @see <a href="#field_summary">filter type constants</a>
|
* @see <a href="#field_summary">filter type constants</a>
|
||||||
*/
|
*/
|
||||||
public ResampleOp(int pWidth, int pHeight, int pFilterType) {
|
public ResampleOp(int width, int height, int filterType) {
|
||||||
if (pWidth <= 0 || pHeight <= 0) {
|
if (width <= 0 || height <= 0) {
|
||||||
// NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
|
// NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
|
||||||
throw new IllegalArgumentException("width and height must be positive");
|
throw new IllegalArgumentException("width and height must be positive");
|
||||||
}
|
}
|
||||||
|
|
||||||
mWidth = pWidth;
|
this.width = width;
|
||||||
mHeight = pHeight;
|
this.height = height;
|
||||||
|
|
||||||
validateFilterType(pFilterType);
|
this.filterType = validateFilterType(filterType);
|
||||||
mFilterType = pFilterType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateFilterType(int pFilterType) {
|
private static int validateFilterType(int pFilterType) {
|
||||||
switch (pFilterType) {
|
switch (pFilterType) {
|
||||||
case FILTER_UNDEFINED:
|
case FILTER_UNDEFINED:
|
||||||
case FILTER_POINT:
|
case FILTER_POINT:
|
||||||
@@ -446,7 +439,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
case FILTER_LANCZOS:
|
case FILTER_LANCZOS:
|
||||||
case FILTER_BLACKMAN_BESSEL:
|
case FILTER_BLACKMAN_BESSEL:
|
||||||
case FILTER_BLACKMAN_SINC:
|
case FILTER_BLACKMAN_SINC:
|
||||||
break;
|
return pFilterType;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unknown filter type: " + pFilterType);
|
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;
|
return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED;
|
||||||
}
|
}
|
||||||
else
|
else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|
||||||
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|
|
||||||
|| (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION)
|
|| (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION)
|
||||||
&& (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
&& (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
||||||
|| RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_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;
|
return FILTER_POINT;
|
||||||
}
|
}
|
||||||
else
|
else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||||
if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
|
||||||
// Triangle equals bi-linear interpolation
|
// Triangle equals bi-linear interpolation
|
||||||
return FILTER_TRIANGLE;
|
return FILTER_TRIANGLE;
|
||||||
}
|
}
|
||||||
else
|
else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||||
if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
|
||||||
return FILTER_QUADRATIC;// No idea if this is correct..?
|
return FILTER_QUADRATIC;// No idea if this is correct..?
|
||||||
}
|
}
|
||||||
else
|
else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
||||||
if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
|
||||||
|| RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
|
|| RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
|
||||||
// Prioritize quality
|
// Prioritize quality
|
||||||
return FILTER_MITCHELL;
|
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.
|
* specified in the constructor.
|
||||||
*
|
*
|
||||||
* @param pInput The {@code BufferedImage} to be filtered
|
* @param input The {@code BufferedImage} to be filtered
|
||||||
* @param pOutput The {@code BufferedImage} in which to store the resampled
|
* @param output The {@code BufferedImage} in which to store the resampled
|
||||||
* image
|
* image
|
||||||
* @return The resampled {@code BufferedImage}.
|
* @return The re-sampled {@code BufferedImage}.
|
||||||
* @throws NullPointerException if {@code pInput} is {@code null}
|
* @throws NullPointerException if {@code input} is {@code null}
|
||||||
* @throws IllegalArgumentException if {@code pInput == pOutput}.
|
* @throws IllegalArgumentException if {@code input == output}.
|
||||||
* @see #ResampleOp(int,int,int)
|
* @see #ResampleOp(int,int,int)
|
||||||
*/
|
*/
|
||||||
public final BufferedImage filter(final BufferedImage pInput, final BufferedImage pOutput) {
|
public final BufferedImage filter(final BufferedImage input, final BufferedImage output) {
|
||||||
if (pInput == null) {
|
if (input == null) {
|
||||||
throw new NullPointerException("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");
|
throw new IllegalArgumentException("Output image cannot be the same as the input image");
|
||||||
}
|
}
|
||||||
|
|
||||||
InterpolationFilter filter;
|
InterpolationFilter filter;
|
||||||
|
|
||||||
|
|
||||||
// Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
|
// Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
|
||||||
// Java implementation is very fast (possibly H/W accellerated)
|
// Java implementation is very fast (possibly H/W accelerated)
|
||||||
switch (mFilterType) {
|
switch (filterType) {
|
||||||
case FILTER_POINT:
|
case FILTER_POINT:
|
||||||
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
|
||||||
case FILTER_TRIANGLE:
|
return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
// 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:
|
default:
|
||||||
filter = createFilter(mFilterType);
|
filter = createFilter(filterType);
|
||||||
// NOTE: Workaround for filter throwing exceptions when input or output is less than support...
|
// 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()) {
|
if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) {
|
||||||
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
|
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
|
||||||
}
|
}
|
||||||
// Fall through
|
// Fall through
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Try to use native ImageMagick code
|
// Try to use native ImageMagick code
|
||||||
BufferedImage result = MagickAccelerator.filter(this, pInput, pOutput);
|
BufferedImage result = MagickAccelerator.filter(this, input, output);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, continue in pure Java mode
|
// 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
|
// If filter type != POINT or BOX an input has IndexColorModel, convert
|
||||||
// to true color, with alpha reflecting that of the original colormodel.
|
// to true color, with alpha reflecting that of the original color model.
|
||||||
BufferedImage input;
|
BufferedImage temp;
|
||||||
ColorModel cm;
|
ColorModel cm;
|
||||||
if (mFilterType != FILTER_BOX && (cm = pInput.getColorModel()) instanceof IndexColorModel) {
|
if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) {
|
||||||
// TODO: OPTIMIZE: If colormodel has only b/w or gray, we could skip color info
|
// TODO: OPTIMIZE: If color model 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);
|
temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
input = pInput;
|
temp = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or convert output to a suitable image
|
// Create or convert output to a suitable image
|
||||||
// TODO: OPTIMIZE: Don't really need to convert all types to same as input
|
// 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 output != null and needed to be converted, draw it back
|
||||||
if (pOutput != null && pOutput != result) {
|
if (output != null && output != result) {
|
||||||
//pOutput.setData(output.getRaster());
|
//output.setData(output.getRaster());
|
||||||
ImageUtil.drawOnto(pOutput, result);
|
ImageUtil.drawOnto(output, result);
|
||||||
result = pOutput;
|
result = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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) {
|
private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) {
|
||||||
BufferedImage temp = pInput;
|
BufferedImage temp = input;
|
||||||
|
|
||||||
double xScale;
|
double xScale;
|
||||||
double yScale;
|
double yScale;
|
||||||
@@ -681,20 +674,20 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
AffineTransform transform;
|
AffineTransform transform;
|
||||||
AffineTransformOp scale;
|
AffineTransformOp scale;
|
||||||
|
|
||||||
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
||||||
// Initially scale so all remaining operations will halve the image
|
// Initially scale so all remaining operations will halve the image
|
||||||
if (pWidth < pInput.getWidth() || pHeight < pInput.getHeight()) {
|
if (width < input.getWidth() || height < input.getHeight()) {
|
||||||
int w = pWidth;
|
int w = width;
|
||||||
int h = pHeight;
|
int h = height;
|
||||||
while (w < pInput.getWidth() / 2) {
|
while (w < input.getWidth() / 2) {
|
||||||
w *= 2;
|
w *= 2;
|
||||||
}
|
}
|
||||||
while (h < pInput.getHeight() / 2) {
|
while (h < input.getHeight() / 2) {
|
||||||
h *= 2;
|
h *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
xScale = w / (double) pInput.getWidth();
|
xScale = w / (double) input.getWidth();
|
||||||
yScale = h / (double) pInput.getHeight();
|
yScale = h / (double) input.getHeight();
|
||||||
|
|
||||||
//System.out.println("First scale by x=" + xScale + ", y=" + yScale);
|
//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();
|
xScale = width / (double) temp.getWidth();
|
||||||
yScale = pHeight / (double) temp.getHeight();
|
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
|
// TODO: Test skipping first scale (above), and instead scale once
|
||||||
// more here, and a little less than .5 each time...
|
// more here, and a little less than .5 each time...
|
||||||
// That would probably make the scaling smoother...
|
// That would probably make the scaling smoother...
|
||||||
@@ -740,17 +733,15 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
temp = scale.filter(temp, null);
|
temp = scale.filter(temp, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);
|
//System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);
|
||||||
|
|
||||||
transform = AffineTransform.getScaleInstance(xScale, yScale);
|
transform = AffineTransform.getScaleInstance(xScale, yScale);
|
||||||
scale = new AffineTransformOp(transform, pType);
|
scale = new AffineTransformOp(transform, type);
|
||||||
|
|
||||||
return scale.filter(temp, pOutput);
|
|
||||||
|
|
||||||
|
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>
|
* @see <a href="#field_summary">filter type constants</a>
|
||||||
*/
|
*/
|
||||||
public int getFilterType() {
|
public int getFilterType() {
|
||||||
return mFilterType;
|
return filterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InterpolationFilter createFilter(int pFilterType) {
|
private static InterpolationFilter createFilter(int pFilterType) {
|
||||||
@@ -770,7 +761,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (pFilterType) {
|
switch (pFilterType) {
|
||||||
//case FILTER_POINT: // Should never happen
|
case FILTER_POINT:
|
||||||
|
return new PointFilter();
|
||||||
case FILTER_BOX:
|
case FILTER_BOX:
|
||||||
return new BoxFilter();
|
return new BoxFilter();
|
||||||
case FILTER_TRIANGLE:
|
case FILTER_TRIANGLE:
|
||||||
@@ -815,14 +807,13 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
// If indexcolormodel, we probably don't want to use that...
|
// If indexcolormodel, we probably don't want to use that...
|
||||||
// NOTE: Either BOTH or NONE of the images must have ALPHA
|
// 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);
|
cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderingHints getRenderingHints() {
|
public RenderingHints getRenderingHints() {
|
||||||
Object value;
|
Object value;
|
||||||
switch (mFilterType) {
|
switch (filterType) {
|
||||||
case FILTER_UNDEFINED:
|
case FILTER_UNDEFINED:
|
||||||
return null;
|
return null;
|
||||||
case FILTER_POINT:
|
case FILTER_POINT:
|
||||||
@@ -871,14 +862,14 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
value = VALUE_INTERPOLATION_BLACKMAN_SINC;
|
value = VALUE_INTERPOLATION_BLACKMAN_SINC;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unknown filter type: " + mFilterType);
|
throw new IllegalStateException("Unknown filter type: " + filterType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
|
return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||||
return new Rectangle(mWidth, mHeight);
|
return new Rectangle(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||||
@@ -1439,10 +1430,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
resample()
|
resample()
|
||||||
|
|
||||||
Resizes bitmaps while resampling them.
|
Resizes bitmaps while resampling them.
|
||||||
Returns -1 if error, 0 if success.
|
|
||||||
*/
|
*/
|
||||||
private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) {
|
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 dstWidth = pDest.getWidth();
|
||||||
final int dstHeight = pDest.getHeight();
|
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 */
|
/* create intermediate column to hold horizontal dst column zoom */
|
||||||
final ColorModel cm = pSource.getColorModel();
|
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 xscale = (double) dstWidth / (double) srcWidth;
|
||||||
double yscale = (double) dstHeight / (double) srcHeight;
|
double yscale = (double) dstHeight / (double) srcHeight;
|
||||||
@@ -1566,7 +1556,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
|||||||
final WritableRaster out = pDest.getRaster();
|
final WritableRaster out = pDest.getRaster();
|
||||||
|
|
||||||
// TODO: This is not optimal for non-byte-packed rasters...
|
// 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 numChannels = raster.getNumBands();
|
||||||
final int[] channelMax = new int[numChannels];
|
final int[] channelMax = new int[numChannels];
|
||||||
for (int k = 0; k < numChannels; k++) {
|
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++) {
|
for (int xx = 0; xx < dstWidth; xx++) {
|
||||||
ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, 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 k = 0; k < srcHeight; k++) {
|
||||||
for (int channel = 0; channel < numChannels; channel++) {
|
for (int channel = 0; channel < numChannels; channel++) {
|
||||||
|
|
||||||
+7
-7
@@ -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 $
|
* @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 {
|
public class SubsamplingFilter extends ReplicateScaleFilter {
|
||||||
private int mXSub;
|
private int xSub;
|
||||||
private int mYSub;
|
private int ySub;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code SubsamplingFilter}.
|
* Creates a {@code SubsamplingFilter}.
|
||||||
@@ -62,16 +62,16 @@ public class SubsamplingFilter extends ReplicateScaleFilter {
|
|||||||
throw new IllegalArgumentException("Subsampling factors must be positive.");
|
throw new IllegalArgumentException("Subsampling factors must be positive.");
|
||||||
}
|
}
|
||||||
|
|
||||||
mXSub = pXSub;
|
xSub = pXSub;
|
||||||
mYSub = pYSub;
|
ySub = pYSub;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@code ImageFilter} implementation, do not invoke. */
|
/** {@code ImageFilter} implementation, do not invoke. */
|
||||||
public void setDimensions(int pWidth, int pHeight) {
|
public void setDimensions(int pWidth, int pHeight) {
|
||||||
destWidth = (pWidth + mXSub - 1) / mXSub;
|
destWidth = (pWidth + xSub - 1) / xSub;
|
||||||
destHeight = (pHeight + mYSub - 1) / mYSub;
|
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);
|
super.setDimensions(pWidth, pHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -4,6 +4,6 @@
|
|||||||
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
|
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
|
||||||
*
|
*
|
||||||
* @version 1.0
|
* @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;
|
package com.twelvemonkeys.image;
|
||||||
+366
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
-131
@@ -1,61 +1,53 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import org.junit.Test;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.swing.*;
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.WindowAdapter;
|
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
|
|
||||||
/**
|
import static org.junit.Assert.*;
|
||||||
* Created by IntelliJ IDEA.
|
|
||||||
*
|
public class ImageUtilTestCase {
|
||||||
* @author $author wmhakur$
|
|
||||||
* @version $id: $
|
|
||||||
* To change this template use Options | File Templates.
|
|
||||||
*/
|
|
||||||
public class ImageUtilTestCase extends TestCase {
|
|
||||||
|
|
||||||
private final static String IMAGE_NAME = "/sunflower.jpg";
|
private final static String IMAGE_NAME = "/sunflower.jpg";
|
||||||
private BufferedImage mOriginal;
|
private BufferedImage original;
|
||||||
private BufferedImage mImage;
|
private BufferedImage image;
|
||||||
private Image mScaled;
|
private Image scaled;
|
||||||
|
|
||||||
public ImageUtilTestCase() throws Exception {
|
public ImageUtilTestCase() throws Exception {
|
||||||
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||||
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
|
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||||
|
|
||||||
// Read image from class path
|
// Read image from class path
|
||||||
InputStream is = getClass().getResourceAsStream(IMAGE_NAME);
|
InputStream is = getClass().getResourceAsStream(IMAGE_NAME);
|
||||||
mOriginal = ImageIO.read(is);
|
original = ImageIO.read(is);
|
||||||
|
|
||||||
assertNotNull(mOriginal);
|
assertNotNull(original);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||||
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
|
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||||
|
|
||||||
// Read image from class path
|
// Read image from class path
|
||||||
InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME);
|
InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME);
|
||||||
mOriginal = ImageIO.read(is);
|
original = ImageIO.read(is);
|
||||||
|
|
||||||
assertNotNull(mOriginal);
|
assertNotNull(original);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void tearDown() throws Exception {
|
protected void tearDown() throws Exception {
|
||||||
mOriginal = null;
|
original = null;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testToBufferedImageNull() {
|
public void testToBufferedImageNull() {
|
||||||
BufferedImage img = null;
|
BufferedImage img = null;
|
||||||
boolean threwRuntimeException = false;
|
boolean threwRuntimeException = false;
|
||||||
@@ -73,6 +65,7 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
assertTrue(threwRuntimeException);
|
assertTrue(threwRuntimeException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testToBufferedImageTypeNull() {
|
public void testToBufferedImageTypeNull() {
|
||||||
BufferedImage img = null;
|
BufferedImage img = null;
|
||||||
boolean threwRuntimeException = false;
|
boolean threwRuntimeException = false;
|
||||||
@@ -90,54 +83,58 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
assertTrue(threwRuntimeException);
|
assertTrue(threwRuntimeException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testImageIsNotBufferedImage() {
|
public void testImageIsNotBufferedImage() {
|
||||||
// Should not be a buffered image
|
// Should not be a buffered image
|
||||||
assertFalse(
|
assertFalse(
|
||||||
"FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.",
|
"FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.",
|
||||||
mScaled instanceof BufferedImage
|
scaled instanceof BufferedImage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testToBufferedImage() {
|
public void testToBufferedImage() {
|
||||||
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) mImage);
|
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image);
|
||||||
BufferedImage bufferedScaled = ImageUtil.toBuffered(mScaled);
|
BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled);
|
||||||
|
|
||||||
// Should be no need to convert
|
// Should be no need to convert
|
||||||
assertSame(mImage, sameAsImage);
|
assertSame(image, sameAsImage);
|
||||||
|
|
||||||
// Should have same dimensions
|
// Should have same dimensions
|
||||||
assertEquals(mScaled.getWidth(null), bufferedScaled.getWidth());
|
assertEquals(scaled.getWidth(null), bufferedScaled.getWidth());
|
||||||
assertEquals(mScaled.getHeight(null), bufferedScaled.getHeight());
|
assertEquals(scaled.getHeight(null), bufferedScaled.getHeight());
|
||||||
|
|
||||||
// Hmmm...
|
// Hmmm...
|
||||||
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|
||||||
|| bufferedScaled.getPropertyNames() == null
|
|| bufferedScaled.getPropertyNames() == null
|
||||||
|| bufferedScaled.getPropertyNames().length == 0);
|
|| bufferedScaled.getPropertyNames().length == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testToBufferedImageType() {
|
public void testToBufferedImageType() {
|
||||||
// Assumes mImage is TYPE_INT_ARGB
|
// Assumes image is TYPE_INT_ARGB
|
||||||
BufferedImage converted = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_INDEXED);
|
BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED);
|
||||||
BufferedImage convertedToo = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_BINARY);
|
BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY);
|
||||||
|
|
||||||
// Should not be the same
|
// Should not be the same
|
||||||
assertNotSame(mImage, converted);
|
assertNotSame(image, converted);
|
||||||
assertNotSame(mImage, convertedToo);
|
assertNotSame(image, convertedToo);
|
||||||
|
|
||||||
// Correct type
|
// Correct type
|
||||||
assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED);
|
assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED);
|
||||||
assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY);
|
assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY);
|
||||||
|
|
||||||
// Should have same dimensions
|
// Should have same dimensions
|
||||||
assertEquals(mImage.getWidth(), converted.getWidth());
|
assertEquals(image.getWidth(), converted.getWidth());
|
||||||
assertEquals(mImage.getHeight(), converted.getHeight());
|
assertEquals(image.getHeight(), converted.getHeight());
|
||||||
|
|
||||||
assertEquals(mImage.getWidth(), convertedToo.getWidth());
|
assertEquals(image.getWidth(), convertedToo.getWidth());
|
||||||
assertEquals(mImage.getHeight(), convertedToo.getHeight());
|
assertEquals(image.getHeight(), convertedToo.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testBrightness() {
|
public void testBrightness() {
|
||||||
final BufferedImage original = mOriginal;
|
final BufferedImage original = this.original;
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
|
|
||||||
final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f));
|
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));
|
final BufferedImage brightenedMaxNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -2f));
|
||||||
for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) {
|
for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) {
|
||||||
for (int x = 0; x < brightenedMaxNegative.getWidth(); x++) {
|
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() {
|
public void testContrast() {
|
||||||
final BufferedImage original = mOriginal;
|
final BufferedImage original = this.original;
|
||||||
|
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
|
|
||||||
@@ -273,7 +270,6 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
else {
|
else {
|
||||||
assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB);
|
assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Assumed: Only primary colors (w/b/r/g/b/c/y/m)
|
// 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 r = rgb >> 16 & 0xFF;
|
||||||
int g = rgb >> 8 & 0xFF;
|
int g = rgb >> 8 & 0xFF;
|
||||||
int b = rgb & 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() {
|
public void testSharpen() {
|
||||||
final BufferedImage original = mOriginal;
|
final BufferedImage original = this.original;
|
||||||
|
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
|
|
||||||
@@ -390,10 +387,10 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f);
|
final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f);
|
||||||
final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f);
|
final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f);
|
||||||
|
|
||||||
long diffOriginal = 0;
|
// long diffOriginal = 0;
|
||||||
long diffSharpened = 0;
|
// long diffSharpened = 0;
|
||||||
long diffDefault = 0;
|
// long diffDefault = 0;
|
||||||
long diffMore = 0;
|
// long diffMore = 0;
|
||||||
|
|
||||||
long absDiffOriginal = 0;
|
long absDiffOriginal = 0;
|
||||||
long absDiffSharpened = 0;
|
long absDiffSharpened = 0;
|
||||||
@@ -412,10 +409,10 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y);
|
int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y);
|
||||||
int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y);
|
int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y);
|
||||||
|
|
||||||
diffOriginal += poRGB - oRGB;
|
// diffOriginal += poRGB - oRGB;
|
||||||
diffSharpened += psRGB - sRGB;
|
// diffSharpened += psRGB - sRGB;
|
||||||
diffDefault += pdRGB - dRGB;
|
// diffDefault += pdRGB - dRGB;
|
||||||
diffMore += pmRGB - mRGB;
|
// diffMore += pmRGB - mRGB;
|
||||||
|
|
||||||
absDiffOriginal += Math.abs(poRGB - oRGB);
|
absDiffOriginal += Math.abs(poRGB - oRGB);
|
||||||
absDiffSharpened += Math.abs(psRGB - sRGB);
|
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);
|
// assertEquals("Difference should not change", diffOriginal, diffSharpened);
|
||||||
assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened);
|
assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened);
|
||||||
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
||||||
@@ -438,64 +431,9 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
assertTrue("Abs difference should increase", absDiffSharpened < absDiffMore);
|
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) {
|
@Test
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBlur() {
|
public void testBlur() {
|
||||||
final BufferedImage original = mOriginal;
|
final BufferedImage original = this.original;
|
||||||
|
|
||||||
assertNotNull(original);
|
assertNotNull(original);
|
||||||
|
|
||||||
@@ -515,17 +453,16 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f);
|
final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f);
|
||||||
final BufferedImage blurredMore = ImageUtil.blur(original, 3f);
|
final BufferedImage blurredMore = ImageUtil.blur(original, 3f);
|
||||||
|
|
||||||
long diffOriginal = 0;
|
// long diffOriginal = 0;
|
||||||
long diffBlurred = 0;
|
// long diffBlurred = 0;
|
||||||
long diffDefault = 0;
|
// long diffDefault = 0;
|
||||||
long diffMore = 0;
|
// long diffMore = 0;
|
||||||
|
|
||||||
long absDiffOriginal = 0;
|
long absDiffOriginal = 0;
|
||||||
long absDiffBlurred = 0;
|
long absDiffBlurred = 0;
|
||||||
long absDiffDefault = 0;
|
long absDiffDefault = 0;
|
||||||
long absDiffMore = 0;
|
long absDiffMore = 0;
|
||||||
|
|
||||||
|
|
||||||
for (int y = 0; y < original.getHeight(); y++) {
|
for (int y = 0; y < original.getHeight(); y++) {
|
||||||
for (int x = 1; x < original.getWidth(); x++) {
|
for (int x = 1; x < original.getWidth(); x++) {
|
||||||
int oRGB = 0x00FFFFFF & original.getRGB(x, y);
|
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 pdRGB = 0x00FFFFFF & blurredDefault.getRGB(x - 1, y);
|
||||||
int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y);
|
int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y);
|
||||||
|
|
||||||
diffOriginal += poRGB - oRGB;
|
// diffOriginal += poRGB - oRGB;
|
||||||
diffBlurred += pbRGB - bRGB;
|
// diffBlurred += pbRGB - bRGB;
|
||||||
diffDefault += pdRGB - dRGB;
|
// diffDefault += pdRGB - dRGB;
|
||||||
diffMore += pmRGB - mRGB;
|
// diffMore += pmRGB - mRGB;
|
||||||
|
|
||||||
absDiffOriginal += Math.abs(poRGB - oRGB);
|
absDiffOriginal += Math.abs(poRGB - oRGB);
|
||||||
absDiffBlurred += Math.abs(pbRGB - bRGB);
|
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);
|
// assertEquals("Difference should not change", diffOriginal, diffBlurred);
|
||||||
assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred);
|
assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred);
|
||||||
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
||||||
@@ -562,8 +497,9 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore);
|
assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testIndexImage() {
|
public void testIndexImage() {
|
||||||
BufferedImage sunflower = mOriginal;
|
BufferedImage sunflower = original;
|
||||||
|
|
||||||
assertNotNull(sunflower);
|
assertNotNull(sunflower);
|
||||||
|
|
||||||
@@ -571,4 +507,4 @@ public class ImageUtilTestCase extends TestCase {
|
|||||||
assertNotNull("Image was null", image);
|
assertNotNull("Image was null", image);
|
||||||
assertTrue(image.getColorModel() instanceof IndexColorModel);
|
assertTrue(image.getColorModel() instanceof IndexColorModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+61
-2
@@ -1,6 +1,7 @@
|
|||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@@ -9,6 +10,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResampleOpTestCase
|
* ResampleOpTestCase
|
||||||
*
|
*
|
||||||
@@ -16,7 +19,7 @@ import java.util.List;
|
|||||||
* @author last modified by $Author: haku $
|
* @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 $
|
* @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) {
|
protected BufferedImage createImage(final int pWidth, final int pHeigth) {
|
||||||
return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB);
|
return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB);
|
||||||
@@ -36,6 +39,7 @@ public class ResampleOpTestCase extends TestCase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testCreateImage() {
|
public void testCreateImage() {
|
||||||
// Sanity test the create method
|
// Sanity test the create method
|
||||||
BufferedImage image = createImage(79, 84);
|
BufferedImage image = createImage(79, 84);
|
||||||
@@ -94,170 +98,225 @@ public class ResampleOpTestCase extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1x1
|
// 1x1
|
||||||
|
@Test
|
||||||
public void testResample1x1Point() {
|
public void testResample1x1Point() {
|
||||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample1x1Box() {
|
public void testResample1x1Box() {
|
||||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample1x1Triangle() {
|
public void testResample1x1Triangle() {
|
||||||
assertResample(createImage(1, 1), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(1, 1), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample1x1Lanczos() {
|
public void testResample1x1Lanczos() {
|
||||||
assertResample(createImage(1, 1), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(1, 1), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample1x1Gaussian() {
|
public void testResample1x1Gaussian() {
|
||||||
assertResample(createImage(1, 1), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(1, 1), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample1x1Sinc() {
|
public void testResample1x1Sinc() {
|
||||||
assertResample(createImage(1, 1), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(1, 1), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2x2
|
// 2x2
|
||||||
|
@Test
|
||||||
public void testResample2x2Point() {
|
public void testResample2x2Point() {
|
||||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample2x2Box() {
|
public void testResample2x2Box() {
|
||||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample2x2Triangle() {
|
public void testResample2x2Triangle() {
|
||||||
assertResample(createImage(2, 2), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(2, 2), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample2x2Lanczos() {
|
public void testResample2x2Lanczos() {
|
||||||
assertResample(createImage(2, 2), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(2, 2), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample2x2Gaussian() {
|
public void testResample2x2Gaussian() {
|
||||||
assertResample(createImage(2, 2), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(2, 2), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample2x2Sinc() {
|
public void testResample2x2Sinc() {
|
||||||
assertResample(createImage(2, 2), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(2, 2), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3x3
|
// 3x3
|
||||||
|
@Test
|
||||||
public void testResample3x3Point() {
|
public void testResample3x3Point() {
|
||||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample3x3Box() {
|
public void testResample3x3Box() {
|
||||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample3x3Triangle() {
|
public void testResample3x3Triangle() {
|
||||||
assertResample(createImage(3, 3), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(3, 3), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample3x3Lanczos() {
|
public void testResample3x3Lanczos() {
|
||||||
assertResample(createImage(3, 3), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(3, 3), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample3x3Gaussian() {
|
public void testResample3x3Gaussian() {
|
||||||
assertResample(createImage(3, 3), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(3, 3), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample3x3Sinc() {
|
public void testResample3x3Sinc() {
|
||||||
assertResample(createImage(3, 3), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(3, 3), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4x4
|
// 4x4
|
||||||
|
@Test
|
||||||
public void testResample4x4Point() {
|
public void testResample4x4Point() {
|
||||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample4x4Box() {
|
public void testResample4x4Box() {
|
||||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample4x4Triangle() {
|
public void testResample4x4Triangle() {
|
||||||
assertResample(createImage(4, 4), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(4, 4), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample4x4Lanczos() {
|
public void testResample4x4Lanczos() {
|
||||||
assertResample(createImage(4, 4), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(4, 4), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample4x4Gaussian() {
|
public void testResample4x4Gaussian() {
|
||||||
assertResample(createImage(4, 4), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(4, 4), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample4x4Sinc() {
|
public void testResample4x4Sinc() {
|
||||||
assertResample(createImage(4, 4), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(4, 4), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 20x20
|
// 20x20
|
||||||
|
@Test
|
||||||
public void testResample20x20Point() {
|
public void testResample20x20Point() {
|
||||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample20x20Box() {
|
public void testResample20x20Box() {
|
||||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample20x20Triangle() {
|
public void testResample20x20Triangle() {
|
||||||
assertResample(createImage(20, 20), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(20, 20), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample20x20Lanczos() {
|
public void testResample20x20Lanczos() {
|
||||||
assertResample(createImage(20, 20), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(20, 20), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample20x20Gaussian() {
|
public void testResample20x20Gaussian() {
|
||||||
assertResample(createImage(20, 20), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(20, 20), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample20x20Sinc() {
|
public void testResample20x20Sinc() {
|
||||||
assertResample(createImage(20, 20), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(20, 20), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 200x160
|
// 200x160
|
||||||
|
@Test
|
||||||
public void testResample200x160Point() {
|
public void testResample200x160Point() {
|
||||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_POINT);
|
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample200x160Box() {
|
public void testResample200x160Box() {
|
||||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_BOX);
|
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample200x160Triangle() {
|
public void testResample200x160Triangle() {
|
||||||
assertResample(createImage(200, 160), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
assertResample(createImage(200, 160), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample200x160Lanczos() {
|
public void testResample200x160Lanczos() {
|
||||||
assertResample(createImage(200, 160), 7, 49, ResampleOp.FILTER_LANCZOS);
|
assertResample(createImage(200, 160), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample200x160Gaussian() {
|
public void testResample200x160Gaussian() {
|
||||||
assertResample(createImage(200, 160), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
assertResample(createImage(200, 160), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResample200x160Sinc() {
|
public void testResample200x160Sinc() {
|
||||||
assertResample(createImage(200, 160), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
assertResample(createImage(200, 160), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 10x10 -> 15x5 with different algorithms and types
|
// Test 10x10 -> 15x5 with different algorithms and types
|
||||||
|
@Test
|
||||||
public void testResamplePoint() {
|
public void testResamplePoint() {
|
||||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_POINT);
|
assertResampleBufferedImageTypes(ResampleOp.FILTER_POINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResampleBox() {
|
public void testResampleBox() {
|
||||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_BOX);
|
assertResampleBufferedImageTypes(ResampleOp.FILTER_BOX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResampleTriangle() {
|
public void testResampleTriangle() {
|
||||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_TRIANGLE);
|
assertResampleBufferedImageTypes(ResampleOp.FILTER_TRIANGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResampleLanczos() {
|
public void testResampleLanczos() {
|
||||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
|
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 |
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -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:
|
||||||
@@ -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>
|
||||||
+39
-39
@@ -14,28 +14,28 @@ import java.io.InputStream;
|
|||||||
*/
|
*/
|
||||||
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||||
/** The backing stream */
|
/** The backing stream */
|
||||||
protected final InputStream mStream;
|
protected final InputStream stream;
|
||||||
|
|
||||||
/** The stream positon in the backing stream (mStream) */
|
/** The stream positon in the backing stream (stream) */
|
||||||
protected long mStreamPosition;
|
protected long streamPosition;
|
||||||
|
|
||||||
private StreamCache mCache;
|
private StreamCache cache;
|
||||||
|
|
||||||
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
|
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
|
||||||
Validate.notNull(pStream, "stream");
|
Validate.notNull(pStream, "stream");
|
||||||
Validate.notNull(pCache, "cache");
|
Validate.notNull(pCache, "cache");
|
||||||
|
|
||||||
mStream = pStream;
|
stream = pStream;
|
||||||
mCache = pCache;
|
cache = pCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final StreamCache getCache() {
|
protected final StreamCache getCache() {
|
||||||
return mCache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
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;
|
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,26 +43,26 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
|||||||
checkOpen();
|
checkOpen();
|
||||||
int read;
|
int read;
|
||||||
|
|
||||||
if (mPosition == mStreamPosition) {
|
if (position == streamPosition) {
|
||||||
// TODO: Read more bytes here!
|
// TODO: Read more bytes here!
|
||||||
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
|
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
|
||||||
// Read a byte from the stream
|
// Read a byte from the stream
|
||||||
read = mStream.read();
|
read = stream.read();
|
||||||
|
|
||||||
if (read >= 0) {
|
if (read >= 0) {
|
||||||
mStreamPosition++;
|
streamPosition++;
|
||||||
mCache.write(read);
|
cache.write(read);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// ..or read byte from the cache
|
// ..or read byte from the cache
|
||||||
syncPosition();
|
syncPosition();
|
||||||
read = mCache.read();
|
read = cache.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This field is not REALLY considered accessible.. :-P
|
// TODO: This field is not REALLY considered accessible.. :-P
|
||||||
if (read != -1) {
|
if (read != -1) {
|
||||||
mPosition++;
|
position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
@@ -73,32 +73,32 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
|||||||
checkOpen();
|
checkOpen();
|
||||||
int length;
|
int length;
|
||||||
|
|
||||||
if (mPosition == mStreamPosition) {
|
if (position == streamPosition) {
|
||||||
// Read bytes from the stream
|
// Read bytes from the stream
|
||||||
length = mStream.read(pBytes, pOffset, pLength);
|
length = stream.read(pBytes, pOffset, pLength);
|
||||||
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
mStreamPosition += length;
|
streamPosition += length;
|
||||||
mCache.write(pBytes, pOffset, length);
|
cache.write(pBytes, pOffset, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// ...or read bytes from the cache
|
// ...or read bytes from the cache
|
||||||
syncPosition();
|
syncPosition();
|
||||||
length = mCache.read(pBytes, pOffset, pLength);
|
length = cache.read(pBytes, pOffset, pLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This field is not REALLY considered accessible.. :-P
|
// TODO: This field is not REALLY considered accessible.. :-P
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
mPosition += length;
|
position += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void syncPosition() throws IOException {
|
protected final void syncPosition() throws IOException {
|
||||||
if (mCache.getPosition() != mPosition) {
|
if (cache.getPosition() != position) {
|
||||||
mCache.seek(mPosition); // Assure EOF is correctly thrown
|
cache.seek(position); // Assure EOF is correctly thrown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,14 +111,14 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
|||||||
public abstract boolean isCachedFile();
|
public abstract boolean isCachedFile();
|
||||||
|
|
||||||
protected void seekImpl(long pPosition) throws IOException {
|
protected void seekImpl(long pPosition) throws IOException {
|
||||||
if (mStreamPosition < pPosition) {
|
if (streamPosition < pPosition) {
|
||||||
// Make sure we append at end of cache
|
// Make sure we append at end of cache
|
||||||
if (mCache.getPosition() != mStreamPosition) {
|
if (cache.getPosition() != streamPosition) {
|
||||||
mCache.seek(mStreamPosition);
|
cache.seek(streamPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read diff from stream into cache
|
// Read diff from stream into cache
|
||||||
long left = pPosition - mStreamPosition;
|
long left = pPosition - streamPosition;
|
||||||
|
|
||||||
// TODO: Use fixed buffer, instead of allocating here...
|
// TODO: Use fixed buffer, instead of allocating here...
|
||||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||||
@@ -126,11 +126,11 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
|||||||
|
|
||||||
while (left > 0) {
|
while (left > 0) {
|
||||||
int length = buffer.length < left ? buffer.length : (int) left;
|
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) {
|
if (read > 0) {
|
||||||
mCache.write(buffer, 0, read);
|
cache.write(buffer, 0, read);
|
||||||
mStreamPosition += read;
|
streamPosition += read;
|
||||||
left -= read;
|
left -= read;
|
||||||
}
|
}
|
||||||
else if (read < 0) {
|
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
|
// Seek backwards into the cache
|
||||||
mCache.seek(pPosition);
|
cache.seek(pPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// System.out.println("pPosition: " + pPosition);
|
// System.out.println("pPosition: " + pPosition);
|
||||||
// System.out.println("mPosition: " + mPosition);
|
// System.out.println("position: " + position);
|
||||||
// System.out.println("mStreamPosition: " + mStreamPosition);
|
// System.out.println("streamPosition: " + streamPosition);
|
||||||
// System.out.println("mCache.mPosition: " + mCache.getPosition());
|
// 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) {
|
protected void flushBeforeImpl(long pPosition) {
|
||||||
mCache.flush(pPosition);
|
cache.flush(pPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void closeImpl() throws IOException {
|
protected void closeImpl() throws IOException {
|
||||||
mCache.flush(mPosition);
|
cache.flush(position);
|
||||||
mCache = null;
|
cache = null;
|
||||||
mStream.close();
|
stream.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+43
-41
@@ -46,15 +46,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class CompoundReader extends Reader {
|
public class CompoundReader extends Reader {
|
||||||
|
|
||||||
private Reader mCurrent;
|
private Reader current;
|
||||||
private List<Reader> mReaders;
|
private List<Reader> readers;
|
||||||
protected final Object mLock;
|
|
||||||
|
|
||||||
protected final boolean mMarkSupported;
|
protected final Object finalLock;
|
||||||
|
|
||||||
private int mCurrentReader;
|
protected final boolean markSupported;
|
||||||
private int mMarkedReader;
|
|
||||||
private int mMark;
|
private int currentReader;
|
||||||
|
private int markedReader;
|
||||||
|
private int mark;
|
||||||
private int mNext;
|
private int mNext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,10 +72,10 @@ public class CompoundReader extends Reader {
|
|||||||
public CompoundReader(final Iterator<Reader> pReaders) {
|
public CompoundReader(final Iterator<Reader> pReaders) {
|
||||||
super(Validate.notNull(pReaders, "readers"));
|
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
|
// reference can't change, only it's elements
|
||||||
|
|
||||||
mReaders = new ArrayList<Reader>();
|
readers = new ArrayList<Reader>();
|
||||||
|
|
||||||
boolean markSupported = true;
|
boolean markSupported = true;
|
||||||
while (pReaders.hasNext()) {
|
while (pReaders.hasNext()) {
|
||||||
@@ -82,25 +83,25 @@ public class CompoundReader extends Reader {
|
|||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
throw new NullPointerException("readers cannot contain null-elements");
|
throw new NullPointerException("readers cannot contain null-elements");
|
||||||
}
|
}
|
||||||
mReaders.add(reader);
|
readers.add(reader);
|
||||||
markSupported = markSupported && reader.markSupported();
|
markSupported = markSupported && reader.markSupported();
|
||||||
}
|
}
|
||||||
mMarkSupported = markSupported;
|
this.markSupported = markSupported;
|
||||||
|
|
||||||
mCurrent = nextReader();
|
current = nextReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Reader nextReader() {
|
protected final Reader nextReader() {
|
||||||
if (mCurrentReader >= mReaders.size()) {
|
if (currentReader >= readers.size()) {
|
||||||
mCurrent = new EmptyReader();
|
current = new EmptyReader();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mCurrent = mReaders.get(mCurrentReader++);
|
current = readers.get(currentReader++);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||||
mNext = 0;
|
mNext = 0;
|
||||||
return mCurrent;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,17 +110,18 @@ public class CompoundReader extends Reader {
|
|||||||
* @throws IOException if the stream is closed
|
* @throws IOException if the stream is closed
|
||||||
*/
|
*/
|
||||||
protected final void ensureOpen() throws IOException {
|
protected final void ensureOpen() throws IOException {
|
||||||
if (mReaders == null) {
|
if (readers == null) {
|
||||||
throw new IOException("Stream closed");
|
throw new IOException("Stream closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// Close all readers
|
// Close all readers
|
||||||
for (Reader reader : mReaders) {
|
for (Reader reader : readers) {
|
||||||
reader.close();
|
reader.close();
|
||||||
}
|
}
|
||||||
mReaders = null;
|
|
||||||
|
readers = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,46 +132,46 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
// TODO: It would be nice if we could actually close some readers now
|
// TODO: It would be nice if we could actually close some readers now
|
||||||
|
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
mMark = mNext;
|
mark = mNext;
|
||||||
mMarkedReader = mCurrentReader;
|
markedReader = currentReader;
|
||||||
|
|
||||||
mCurrent.mark(pReadLimit);
|
current.mark(pReadLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() throws IOException {
|
public void reset() throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
|
|
||||||
if (mCurrentReader != mMarkedReader) {
|
if (currentReader != markedReader) {
|
||||||
// Reset any reader before this
|
// Reset any reader before this
|
||||||
for (int i = mCurrentReader; i >= mMarkedReader; i--) {
|
for (int i = currentReader; i >= markedReader; i--) {
|
||||||
mReaders.get(i).reset();
|
readers.get(i).reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentReader = mMarkedReader - 1;
|
currentReader = markedReader - 1;
|
||||||
nextReader();
|
nextReader();
|
||||||
}
|
}
|
||||||
mCurrent.reset();
|
current.reset();
|
||||||
|
|
||||||
mNext = mMark;
|
mNext = mark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean markSupported() {
|
public boolean markSupported() {
|
||||||
return mMarkSupported;
|
return markSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
int read = mCurrent.read();
|
int read = current.read();
|
||||||
|
|
||||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
if (read < 0 && currentReader < readers.size()) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return read(); // In case of 0-length readers
|
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 {
|
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
if (read < 0 && currentReader < readers.size()) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
@@ -197,15 +199,15 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean ready() throws IOException {
|
public boolean ready() throws IOException {
|
||||||
return mCurrent.ready();
|
return current.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long skip(long pChars) throws IOException {
|
public long skip(long pChars) throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
long skipped = mCurrent.skip(pChars);
|
long skipped = current.skip(pChars);
|
||||||
|
|
||||||
if (skipped == 0 && mCurrentReader < mReaders.size()) {
|
if (skipped == 0 && currentReader < readers.size()) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return skip(pChars); // In case of 0-length readers
|
return skip(pChars); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
+21
-18
@@ -39,11 +39,12 @@ import java.io.ByteArrayInputStream;
|
|||||||
* <p/>
|
* <p/>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</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/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 {
|
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||||
/** Max grow size (unless if writing more than this ammount of bytes) */
|
/** Max grow size (unless if writing more than this amount of bytes) */
|
||||||
protected int mMaxGrowSize = 1024 * 1024; // 1 MB
|
protected int maxGrowSize = 1024 * 1024; // 1 MB
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||||
@@ -69,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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) ||
|
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
@@ -77,23 +78,24 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
else if (pLength == 0) {
|
else if (pLength == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int newcount = count + pLength;
|
|
||||||
growIfNeeded(newcount);
|
int newCount = count + pLength;
|
||||||
|
growIfNeeded(newCount);
|
||||||
System.arraycopy(pBytes, pOffset, buf, count, pLength);
|
System.arraycopy(pBytes, pOffset, buf, count, pLength);
|
||||||
count = newcount;
|
count = newCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void write(int pByte) {
|
public void write(int pByte) {
|
||||||
int newcount = count + 1;
|
int newCount = count + 1;
|
||||||
growIfNeeded(newcount);
|
growIfNeeded(newCount);
|
||||||
buf[count] = (byte) pByte;
|
buf[count] = (byte) pByte;
|
||||||
count = newcount;
|
count = newCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void growIfNeeded(int pNewcount) {
|
private void growIfNeeded(int pNewCount) {
|
||||||
if (pNewcount > buf.length) {
|
if (pNewCount > buf.length) {
|
||||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount);
|
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
|
||||||
byte newBuf[] = new byte[newSize];
|
byte newBuf[] = new byte[newSize];
|
||||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||||
buf = newBuf;
|
buf = newBuf;
|
||||||
@@ -109,9 +111,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
// Non-synchronized version of toByteArray
|
// Non-synchronized version of toByteArray
|
||||||
@Override
|
@Override
|
||||||
public byte[] toByteArray() {
|
public byte[] toByteArray() {
|
||||||
byte newbuf[] = new byte[count];
|
byte newBuf[] = new byte[count];
|
||||||
System.arraycopy(buf, 0, newbuf, 0, count);
|
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||||
return newbuf;
|
|
||||||
|
return newBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +124,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Note that care needs to be taken to avoid writes to
|
* Note that care needs to be taken to avoid writes to
|
||||||
* this output stream after the input stream is created.
|
* 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.
|
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
|
||||||
*/
|
*/
|
||||||
+12
-95
@@ -48,16 +48,7 @@ import java.io.*;
|
|||||||
*/
|
*/
|
||||||
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
|
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
|
||||||
|
|
||||||
// private final InputStream mStream;
|
private byte[] buffer;
|
||||||
// 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?
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code FileCacheSeekableStream} reading from the given
|
* Creates a {@code FileCacheSeekableStream} reading from the given
|
||||||
@@ -118,7 +109,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
|||||||
super(pStream, new FileCache(pFile));
|
super(pStream, new FileCache(pFile));
|
||||||
|
|
||||||
// TODO: Allow for custom buffer sizes?
|
// TODO: Allow for custom buffer sizes?
|
||||||
mBuffer = new byte[1024];
|
buffer = new byte[1024];
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isCachedMemory() {
|
public final boolean isCachedMemory() {
|
||||||
@@ -132,39 +123,19 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
|||||||
@Override
|
@Override
|
||||||
protected void closeImpl() throws IOException {
|
protected void closeImpl() throws IOException {
|
||||||
super.closeImpl();
|
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
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
|
|
||||||
int read;
|
int read;
|
||||||
if (mPosition == mStreamPosition) {
|
if (position == streamPosition) {
|
||||||
// Read ahead into buffer, for performance
|
// Read ahead into buffer, for performance
|
||||||
read = readAhead(mBuffer, 0, mBuffer.length);
|
read = readAhead(buffer, 0, buffer.length);
|
||||||
if (read >= 0) {
|
if (read >= 0) {
|
||||||
read = mBuffer[0] & 0xff;
|
read = buffer[0] & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 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
|
// TODO: This field is not REALLY considered accessible.. :-P
|
||||||
if (read != -1) {
|
if (read != -1) {
|
||||||
mPosition++;
|
position++;
|
||||||
}
|
}
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
@@ -189,7 +160,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
|||||||
checkOpen();
|
checkOpen();
|
||||||
|
|
||||||
int length;
|
int length;
|
||||||
if (mPosition == mStreamPosition) {
|
if (position == streamPosition) {
|
||||||
// Read bytes from the stream
|
// Read bytes from the stream
|
||||||
length = readAhead(pBytes, pOffset, pLength);
|
length = readAhead(pBytes, pOffset, pLength);
|
||||||
|
|
||||||
@@ -198,83 +169,29 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
|||||||
else {
|
else {
|
||||||
// ...or read bytes from the cache
|
// ...or read bytes from the cache
|
||||||
syncPosition();
|
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");
|
//System.out.println("Read " + length + " byte from cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This field is not REALLY considered accessible.. :-P
|
// TODO: This field is not REALLY considered accessible.. :-P
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
mPosition += length;
|
position += length;
|
||||||
}
|
}
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||||
int length;
|
int length;
|
||||||
length = mStream.read(pBytes, pOffset, pLength);
|
length = stream.read(pBytes, pOffset, pLength);
|
||||||
|
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
mStreamPosition += length;
|
streamPosition += length;
|
||||||
getCache().write(pBytes, pOffset, length);
|
getCache().write(pBytes, pOffset, length);
|
||||||
}
|
}
|
||||||
return 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 {
|
final static class FileCache extends StreamCache {
|
||||||
private RandomAccessFile mCacheFile;
|
private RandomAccessFile mCacheFile;
|
||||||
|
|
||||||
+3
-3
@@ -87,7 +87,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
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;
|
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
|||||||
|
|
||||||
int read = mRandomAccess.read();
|
int read = mRandomAccess.read();
|
||||||
if (read >= 0) {
|
if (read >= 0) {
|
||||||
mPosition++;
|
position++;
|
||||||
}
|
}
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
|||||||
|
|
||||||
int read = mRandomAccess.read(pBytes, pOffset, pLength);
|
int read = mRandomAccess.read(pBytes, pOffset, pLength);
|
||||||
if (read > 0) {
|
if (read > 0) {
|
||||||
mPosition += read;
|
position += read;
|
||||||
}
|
}
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
+17
-17
@@ -38,7 +38,7 @@ import java.io.InputStreamReader;
|
|||||||
* <p/>
|
* <p/>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</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/io/FileSystem.java#1 $
|
* @version $Id: FileSystem.java#1 $
|
||||||
*/
|
*/
|
||||||
abstract class FileSystem {
|
abstract class FileSystem {
|
||||||
abstract long getFreeSpace(File pPath);
|
abstract long getFreeSpace(File pPath);
|
||||||
@@ -57,21 +57,21 @@ abstract class FileSystem {
|
|||||||
//System.out.println("os = " + os);
|
//System.out.println("os = " + os);
|
||||||
|
|
||||||
os = os.toLowerCase();
|
os = os.toLowerCase();
|
||||||
if (os.indexOf("windows") != -1) {
|
if (os.contains("windows")) {
|
||||||
return new Win32FileSystem();
|
return new Win32FileSystem();
|
||||||
}
|
}
|
||||||
else if (os.indexOf("linux") != -1 ||
|
else if (os.contains("linux") ||
|
||||||
os.indexOf("sun os") != -1 ||
|
os.contains("sun os") ||
|
||||||
os.indexOf("sunos") != -1 ||
|
os.contains("sunos") ||
|
||||||
os.indexOf("solaris") != -1 ||
|
os.contains("solaris") ||
|
||||||
os.indexOf("mpe/ix") != -1 ||
|
os.contains("mpe/ix") ||
|
||||||
os.indexOf("hp-ux") != -1 ||
|
os.contains("hp-ux") ||
|
||||||
os.indexOf("aix") != -1 ||
|
os.contains("aix") ||
|
||||||
os.indexOf("freebsd") != -1 ||
|
os.contains("freebsd") ||
|
||||||
os.indexOf("irix") != -1 ||
|
os.contains("irix") ||
|
||||||
os.indexOf("digital unix") != -1 ||
|
os.contains("digital unix") ||
|
||||||
os.indexOf("unix") != -1 ||
|
os.contains("unix") ||
|
||||||
os.indexOf("mac os x") != -1) {
|
os.contains("mac os x")) {
|
||||||
return new UnixFileSystem();
|
return new UnixFileSystem();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -80,10 +80,10 @@ abstract class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class UnknownFileSystem extends FileSystem {
|
private static class UnknownFileSystem extends FileSystem {
|
||||||
private final String mOSName;
|
private final String osName;
|
||||||
|
|
||||||
UnknownFileSystem(String pOSName) {
|
UnknownFileSystem(String pOSName) {
|
||||||
mOSName = pOSName;
|
osName = pOSName;
|
||||||
}
|
}
|
||||||
|
|
||||||
long getFreeSpace(File pPath) {
|
long getFreeSpace(File pPath) {
|
||||||
@@ -95,7 +95,7 @@ abstract class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getName() {
|
String getName() {
|
||||||
return "Unknown (" + mOSName + ")";
|
return "Unknown (" + osName + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+25
-36
@@ -79,7 +79,7 @@ public final class FileUtil {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Method main for test only.
|
* Method main for test only.
|
||||||
*/
|
*
|
||||||
public static void main0(String[] pArgs) {
|
public static void main0(String[] pArgs) {
|
||||||
if (pArgs.length != 2) {
|
if (pArgs.length != 2) {
|
||||||
System.out.println("usage: java Copy in out");
|
System.out.println("usage: java Copy in out");
|
||||||
@@ -94,6 +94,7 @@ public final class FileUtil {
|
|||||||
System.out.println(e.getMessage());
|
System.out.println(e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//*/
|
||||||
|
|
||||||
// Avoid instances/constructor showing up in API doc
|
// Avoid instances/constructor showing up in API doc
|
||||||
private FileUtil() {}
|
private FileUtil() {}
|
||||||
@@ -186,6 +187,7 @@ public final class FileUtil {
|
|||||||
if (!pOverWrite && pToFile.exists()) {
|
if (!pOverWrite && pToFile.exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
OutputStream out = null;
|
OutputStream out = null;
|
||||||
|
|
||||||
@@ -202,7 +204,8 @@ public final class FileUtil {
|
|||||||
close(in);
|
close(in);
|
||||||
close(out);
|
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(pFrom, "from");
|
||||||
Validate.notNull(pTo, "to");
|
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
|
// 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!
|
// TODO: Consider letting the client decide as this is sometimes not a good thing!
|
||||||
InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
|
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
|
// Flush out stream, to write any remaining buffered data
|
||||||
out.flush();
|
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.
|
* Gets the file (type) extension of the given file.
|
||||||
* A file extension is the part of the filename, after the last occurence
|
* A file extension is the part of the filename, after the last occurence
|
||||||
@@ -568,6 +551,7 @@ public final class FileUtil {
|
|||||||
if (!pFile.exists()) {
|
if (!pFile.exists()) {
|
||||||
throw new FileNotFoundException(pFile.toString());
|
throw new FileNotFoundException(pFile.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[(int) pFile.length()];
|
byte[] bytes = new byte[(int) pFile.length()];
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
|
|
||||||
@@ -586,6 +570,7 @@ public final class FileUtil {
|
|||||||
finally {
|
finally {
|
||||||
close(in);
|
close(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +582,7 @@ public final class FileUtil {
|
|||||||
* @throws IOException if an i/o error occurs during read.
|
* @throws IOException if an i/o error occurs during read.
|
||||||
*/
|
*/
|
||||||
public static byte[] read(InputStream pInput) throws IOException {
|
public static byte[] read(InputStream pInput) throws IOException {
|
||||||
// Create bytearray
|
// Create byte array
|
||||||
ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
|
ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
|
||||||
|
|
||||||
// Copy from stream to byte array
|
// Copy from stream to byte array
|
||||||
@@ -685,28 +670,28 @@ public final class FileUtil {
|
|||||||
// a file array, which may throw OutOfMemoryExceptions for
|
// a file array, which may throw OutOfMemoryExceptions for
|
||||||
// large directories/in low memory situations
|
// large directories/in low memory situations
|
||||||
class DeleteFilesVisitor implements Visitor<File> {
|
class DeleteFilesVisitor implements Visitor<File> {
|
||||||
private int mFailedCount = 0;
|
private int failedCount = 0;
|
||||||
private IOException mException = null;
|
private IOException exception = null;
|
||||||
|
|
||||||
public void visit(final File pFile) {
|
public void visit(final File pFile) {
|
||||||
try {
|
try {
|
||||||
if (!delete(pFile, true)) {
|
if (!delete(pFile, true)) {
|
||||||
mFailedCount++;
|
failedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
mFailedCount++;
|
failedCount++;
|
||||||
if (mException == null) {
|
if (exception == null) {
|
||||||
mException = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean succeeded() throws IOException {
|
boolean succeeded() throws IOException {
|
||||||
if (mException != null) {
|
if (exception != null) {
|
||||||
throw mException;
|
throw exception;
|
||||||
}
|
}
|
||||||
return mFailedCount == 0;
|
return failedCount == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
|
DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
|
||||||
@@ -886,6 +871,8 @@ public final class FileUtil {
|
|||||||
return folder.listFiles();
|
return folder.listFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rewrite to use regexp
|
||||||
|
|
||||||
FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
|
FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
|
||||||
return folder.listFiles(filter);
|
return folder.listFiles(filter);
|
||||||
}
|
}
|
||||||
@@ -1029,6 +1016,7 @@ public final class FileUtil {
|
|||||||
* @return a human readable string representation
|
* @return a human readable string representation
|
||||||
*/
|
*/
|
||||||
public static String toHumanReadableSize(final long pSizeInBytes) {
|
public static String toHumanReadableSize(final long pSizeInBytes) {
|
||||||
|
// TODO: Rewrite to use String.format?
|
||||||
if (pSizeInBytes < 1024L) {
|
if (pSizeInBytes < 1024L) {
|
||||||
return pSizeInBytes + " Bytes";
|
return pSizeInBytes + " Bytes";
|
||||||
}
|
}
|
||||||
@@ -1053,7 +1041,7 @@ public final class FileUtil {
|
|||||||
private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() {
|
private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() {
|
||||||
protected NumberFormat initialValue() {
|
protected NumberFormat initialValue() {
|
||||||
NumberFormat format = NumberFormat.getNumberInstance();
|
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(2);
|
||||||
format.setMaximumFractionDigits(0);
|
format.setMaximumFractionDigits(0);
|
||||||
return format;
|
return format;
|
||||||
@@ -1075,6 +1063,7 @@ public final class FileUtil {
|
|||||||
*
|
*
|
||||||
* @see com.twelvemonkeys.util.Visitor
|
* @see com.twelvemonkeys.util.Visitor
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings({"ResultOfMethodCallIgnored"})
|
||||||
public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) {
|
public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) {
|
||||||
Validate.notNull(pDirectory, "directory");
|
Validate.notNull(pDirectory, "directory");
|
||||||
Validate.notNull(pVisitor, "visitor");
|
Validate.notNull(pVisitor, "visitor");
|
||||||
+25
-22
@@ -56,13 +56,16 @@ import java.io.FilenameFilter;
|
|||||||
* @see File#list(java.io.FilenameFilter) java.io.File.list
|
* @see File#list(java.io.FilenameFilter) java.io.File.list
|
||||||
* @see FilenameFilter java.io.FilenameFilter
|
* @see FilenameFilter java.io.FilenameFilter
|
||||||
* @see WildcardStringParser
|
* @see WildcardStringParser
|
||||||
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
public class FilenameMaskFilter implements FilenameFilter {
|
public class FilenameMaskFilter implements FilenameFilter {
|
||||||
|
|
||||||
|
// TODO: Rewrite to use regexp, or create new class
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
private String[] mFilenameMasksForInclusion;
|
private String[] filenameMasksForInclusion;
|
||||||
private String[] mFilenameMasksForExclusion;
|
private String[] filenameMasksForExclusion;
|
||||||
private boolean mInclusion = true;
|
private boolean inclusion = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -127,29 +130,29 @@ public class FilenameMaskFilter implements FilenameFilter {
|
|||||||
* @param pFilenameMasksForInclusion the filename masks to include
|
* @param pFilenameMasksForInclusion the filename masks to include
|
||||||
*/
|
*/
|
||||||
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
|
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
|
||||||
mFilenameMasksForInclusion = pFilenameMasksForInclusion;
|
filenameMasksForInclusion = pFilenameMasksForInclusion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current inclusion masks
|
* @return the current inclusion masks
|
||||||
*/
|
*/
|
||||||
public String[] getFilenameMasksForInclusion() {
|
public String[] getFilenameMasksForInclusion() {
|
||||||
return mFilenameMasksForInclusion.clone();
|
return filenameMasksForInclusion.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pFilenameMasksForExclusion the filename masks to exclude
|
* @param pFilenameMasksForExclusion the filename masks to exclude
|
||||||
*/
|
*/
|
||||||
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
|
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
|
||||||
mFilenameMasksForExclusion = pFilenameMasksForExclusion;
|
filenameMasksForExclusion = pFilenameMasksForExclusion;
|
||||||
mInclusion = false;
|
inclusion = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the current exclusion masks
|
* @return the current exclusion masks
|
||||||
*/
|
*/
|
||||||
public String[] getFilenameMasksForExclusion() {
|
public String[] getFilenameMasksForExclusion() {
|
||||||
return mFilenameMasksForExclusion.clone();
|
return filenameMasksForExclusion.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -164,8 +167,8 @@ public class FilenameMaskFilter implements FilenameFilter {
|
|||||||
WildcardStringParser parser;
|
WildcardStringParser parser;
|
||||||
|
|
||||||
// Check each filename string mask whether the file is to be accepted
|
// Check each filename string mask whether the file is to be accepted
|
||||||
if (mInclusion) { // Inclusion
|
if (inclusion) { // Inclusion
|
||||||
for (String mask : mFilenameMasksForInclusion) {
|
for (String mask : filenameMasksForInclusion) {
|
||||||
parser = new WildcardStringParser(mask);
|
parser = new WildcardStringParser(mask);
|
||||||
if (parser.parseString(pName)) {
|
if (parser.parseString(pName)) {
|
||||||
|
|
||||||
@@ -181,7 +184,7 @@ public class FilenameMaskFilter implements FilenameFilter {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Exclusion
|
// Exclusion
|
||||||
for (String mask : mFilenameMasksForExclusion) {
|
for (String mask : filenameMasksForExclusion) {
|
||||||
parser = new WildcardStringParser(mask);
|
parser = new WildcardStringParser(mask);
|
||||||
if (parser.parseString(pName)) {
|
if (parser.parseString(pName)) {
|
||||||
|
|
||||||
@@ -204,32 +207,32 @@ public class FilenameMaskFilter implements FilenameFilter {
|
|||||||
StringBuilder retVal = new StringBuilder();
|
StringBuilder retVal = new StringBuilder();
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (mInclusion) {
|
if (inclusion) {
|
||||||
// Inclusion
|
// Inclusion
|
||||||
if (mFilenameMasksForInclusion == null) {
|
if (filenameMasksForInclusion == null) {
|
||||||
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!");
|
retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
retVal.append(mFilenameMasksForInclusion.length);
|
retVal.append(filenameMasksForInclusion.length);
|
||||||
retVal.append(" filename mask(s) - ");
|
retVal.append(" filename mask(s) - ");
|
||||||
for (i = 0; i < mFilenameMasksForInclusion.length; i++) {
|
for (i = 0; i < filenameMasksForInclusion.length; i++) {
|
||||||
retVal.append("\"");
|
retVal.append("\"");
|
||||||
retVal.append(mFilenameMasksForInclusion[i]);
|
retVal.append(filenameMasksForInclusion[i]);
|
||||||
retVal.append("\", \"");
|
retVal.append("\", \"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Exclusion
|
// Exclusion
|
||||||
if (mFilenameMasksForExclusion == null) {
|
if (filenameMasksForExclusion == null) {
|
||||||
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!");
|
retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
retVal.append(mFilenameMasksForExclusion.length);
|
retVal.append(filenameMasksForExclusion.length);
|
||||||
retVal.append(" exclusion filename mask(s) - ");
|
retVal.append(" exclusion filename mask(s) - ");
|
||||||
for (i = 0; i < mFilenameMasksForExclusion.length; i++) {
|
for (i = 0; i < filenameMasksForExclusion.length; i++) {
|
||||||
retVal.append("\"");
|
retVal.append("\"");
|
||||||
retVal.append(mFilenameMasksForExclusion[i]);
|
retVal.append(filenameMasksForExclusion[i]);
|
||||||
retVal.append("\", \"");
|
retVal.append("\", \"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+41
-17
@@ -38,6 +38,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +65,8 @@ import java.io.*;
|
|||||||
* @see java.io.DataOutput
|
* @see java.io.DataOutput
|
||||||
*
|
*
|
||||||
* @author Elliotte Rusty Harold
|
* @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 {
|
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
|
||||||
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
|
// 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
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public LittleEndianDataInputStream(final InputStream pStream) {
|
public LittleEndianDataInputStream(final InputStream pStream) {
|
||||||
super(pStream);
|
super(Validate.notNull(pStream, "stream"));
|
||||||
if (pStream == null) {
|
|
||||||
throw new IllegalArgumentException("stream == null");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,9 +93,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
*/
|
*/
|
||||||
public boolean readBoolean() throws IOException {
|
public boolean readBoolean() throws IOException {
|
||||||
int b = in.read();
|
int b = in.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return b != 0;
|
return b != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +112,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
*/
|
*/
|
||||||
public byte readByte() throws IOException {
|
public byte readByte() throws IOException {
|
||||||
int b = in.read();
|
int b = in.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte) b;
|
return (byte) b;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -128,9 +132,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
*/
|
*/
|
||||||
public int readUnsignedByte() throws IOException {
|
public int readUnsignedByte() throws IOException {
|
||||||
int b = in.read();
|
int b = in.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +152,14 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
public short readShort() throws IOException {
|
public short readShort() throws IOException {
|
||||||
int byte1 = in.read();
|
int byte1 = in.read();
|
||||||
int byte2 = in.read();
|
int byte2 = in.read();
|
||||||
|
|
||||||
// only need to test last byte read
|
// only need to test last byte read
|
||||||
// if byte1 is -1 so is byte2
|
// if byte1 is -1 so is byte2
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
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 {
|
public int readUnsignedShort() throws IOException {
|
||||||
int byte1 = in.read();
|
int byte1 = in.read();
|
||||||
int byte2 = in.read();
|
int byte2 = in.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
|
||||||
return (byte2 << 8) + byte1;
|
return (byte2 << 8) + byte1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,10 +194,12 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
public char readChar() throws IOException {
|
public char readChar() throws IOException {
|
||||||
int byte1 = in.read();
|
int byte1 = in.read();
|
||||||
int byte2 = in.read();
|
int byte2 = in.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
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) {
|
if (byte4 < 0) {
|
||||||
throw new EOFException();
|
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) {
|
if (byte8 < 0) {
|
||||||
throw new EOFException();
|
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 {
|
public String readUTF() throws IOException {
|
||||||
int byte1 = in.read();
|
int byte1 = in.read();
|
||||||
int byte2 = in.read();
|
int byte2 = in.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
int numbytes = (byte1 << 8) + byte2;
|
int numbytes = (byte1 << 8) + byte2;
|
||||||
char result[] = new char[numbytes];
|
char result[] = new char[numbytes];
|
||||||
int numread = 0;
|
int numread = 0;
|
||||||
int numchars = 0;
|
int numchars = 0;
|
||||||
|
|
||||||
while (numread < numbytes) {
|
while (numread < numbytes) {
|
||||||
|
|
||||||
int c1 = readUnsignedByte();
|
int c1 = readUnsignedByte();
|
||||||
int c2, c3;
|
int c2, c3;
|
||||||
|
|
||||||
@@ -281,27 +294,34 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
}
|
}
|
||||||
else if (test == 12 || test == 13) { // two bytes
|
else if (test == 12 || test == 13) { // two bytes
|
||||||
numread += 2;
|
numread += 2;
|
||||||
|
|
||||||
if (numread > numbytes) {
|
if (numread > numbytes) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
c2 = readUnsignedByte();
|
c2 = readUnsignedByte();
|
||||||
|
|
||||||
if ((c2 & 0xC0) != 0x80) {
|
if ((c2 & 0xC0) != 0x80) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||||
}
|
}
|
||||||
else if (test == 14) { // three bytes
|
else if (test == 14) { // three bytes
|
||||||
numread += 3;
|
numread += 3;
|
||||||
|
|
||||||
if (numread > numbytes) {
|
if (numread > numbytes) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
c2 = readUnsignedByte();
|
c2 = readUnsignedByte();
|
||||||
c3 = readUnsignedByte();
|
c3 = readUnsignedByte();
|
||||||
|
|
||||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||||
throw new UTFDataFormatException();
|
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
|
else { // malformed
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
@@ -396,12 +416,16 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
if (pLength < 0) {
|
if (pLength < 0) {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
while (count < pLength) {
|
while (count < pLength) {
|
||||||
int read = in.read(pBytes, pOffset + count, pLength - count);
|
int read = in.read(pBytes, pOffset + count, pLength - count);
|
||||||
|
|
||||||
if (read < 0) {
|
if (read < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
count += read;
|
count += read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+26
-21
@@ -38,6 +38,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.io.*;
|
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.
|
* 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
|
* 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
|
* @see java.io.FilterOutputStream#out
|
||||||
*/
|
*/
|
||||||
public LittleEndianDataOutputStream(OutputStream pStream) {
|
public LittleEndianDataOutputStream(OutputStream pStream) {
|
||||||
super(pStream);
|
super(Validate.notNull(pStream, "stream"));
|
||||||
if (pStream == null) {
|
|
||||||
throw new IllegalArgumentException("stream == null");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,7 +92,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
|||||||
*/
|
*/
|
||||||
public synchronized void write(int pByte) throws IOException {
|
public synchronized void write(int pByte) throws IOException {
|
||||||
out.write(pByte);
|
out.write(pByte);
|
||||||
mWritten++;
|
bytesWritten++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,10 +104,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
|||||||
* @param pLength the number of bytes to write.
|
* @param pLength the number of bytes to write.
|
||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public synchronized void write(byte[] pBytes, int pOffset, int pLength)
|
public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||||
throws IOException {
|
|
||||||
out.write(pBytes, pOffset, pLength);
|
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 {
|
public void writeByte(int pByte) throws IOException {
|
||||||
out.write(pByte);
|
out.write(pByte);
|
||||||
mWritten++;
|
bytesWritten++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,7 +148,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
|||||||
public void writeShort(int pShort) throws IOException {
|
public void writeShort(int pShort) throws IOException {
|
||||||
out.write(pShort & 0xFF);
|
out.write(pShort & 0xFF);
|
||||||
out.write((pShort >>> 8) & 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 {
|
public void writeChar(int pChar) throws IOException {
|
||||||
out.write(pChar & 0xFF);
|
out.write(pChar & 0xFF);
|
||||||
out.write((pChar >>> 8) & 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 >>> 8) & 0xFF);
|
||||||
out.write((pInt >>> 16) & 0xFF);
|
out.write((pInt >>> 16) & 0xFF);
|
||||||
out.write((pInt >>> 24) & 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 >>> 40) & 0xFF);
|
||||||
out.write((int) (pLong >>> 48) & 0xFF);
|
out.write((int) (pLong >>> 48) & 0xFF);
|
||||||
out.write((int) (pLong >>> 56) & 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 {
|
public void writeBytes(String pString) throws IOException {
|
||||||
int length = pString.length();
|
int length = pString.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
out.write((byte) pString.charAt(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 {
|
public void writeChars(String pString) throws IOException {
|
||||||
int length = pString.length();
|
int length = pString.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
out.write(c & 0xFF);
|
out.write(c & 0xFF);
|
||||||
out.write((c >>> 8) & 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++) {
|
for (int i = 0; i < numchars; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
|
|
||||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||||
numbytes++;
|
numbytes++;
|
||||||
}
|
}
|
||||||
@@ -299,8 +302,10 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
|||||||
|
|
||||||
out.write((numbytes >>> 8) & 0xFF);
|
out.write((numbytes >>> 8) & 0xFF);
|
||||||
out.write(numbytes & 0xFF);
|
out.write(numbytes & 0xFF);
|
||||||
|
|
||||||
for (int i = 0; i < numchars; i++) {
|
for (int i = 0; i < numchars; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
|
|
||||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||||
out.write(c);
|
out.write(c);
|
||||||
}
|
}
|
||||||
@@ -308,16 +313,16 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
|||||||
out.write(0xE0 | ((c >> 12) & 0x0F));
|
out.write(0xE0 | ((c >> 12) & 0x0F));
|
||||||
out.write(0x80 | ((c >> 6) & 0x3F));
|
out.write(0x80 | ((c >> 6) & 0x3F));
|
||||||
out.write(0x80 | (c & 0x3F));
|
out.write(0x80 | (c & 0x3F));
|
||||||
mWritten += 2;
|
bytesWritten += 2;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
out.write(0xC0 | ((c >> 6) & 0x1F));
|
out.write(0xC0 | ((c >> 6) & 0x1F));
|
||||||
out.write(0x80 | (c & 0x3F));
|
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
|
* possible that this number is temporarily less than the actual
|
||||||
* number of bytes written.)
|
* number of bytes written.)
|
||||||
* @return the value of the {@code written} field.
|
* @return the value of the {@code written} field.
|
||||||
* @see #mWritten
|
* @see #bytesWritten
|
||||||
*/
|
*/
|
||||||
public int size() {
|
public int size() {
|
||||||
return mWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+103
-77
@@ -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 $
|
* @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 {
|
public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||||
private RandomAccessFile mFile;
|
private RandomAccessFile file;
|
||||||
|
|
||||||
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
|
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
|
||||||
this(FileUtil.resolve(pName), pMode);
|
this(FileUtil.resolve(pName), pMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
|
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 {
|
public void close() throws IOException {
|
||||||
mFile.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileChannel getChannel() {
|
public FileChannel getChannel() {
|
||||||
return mFile.getChannel();
|
return file.getChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileDescriptor getFD() throws IOException {
|
public FileDescriptor getFD() throws IOException {
|
||||||
return mFile.getFD();
|
return file.getFD();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getFilePointer() throws IOException {
|
public long getFilePointer() throws IOException {
|
||||||
return mFile.getFilePointer();
|
return file.getFilePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long length() throws IOException {
|
public long length() throws IOException {
|
||||||
return mFile.length();
|
return file.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
return mFile.read();
|
return file.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(final byte[] b) throws IOException {
|
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 {
|
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 {
|
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 {
|
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 {
|
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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public boolean readBoolean() throws IOException {
|
public boolean readBoolean() throws IOException {
|
||||||
int b = mFile.read();
|
int b = file.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return b != 0;
|
return b != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,10 +140,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public byte readByte() throws IOException {
|
public byte readByte() throws IOException {
|
||||||
int b = mFile.read();
|
int b = file.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte) b;
|
return (byte) b;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -156,10 +160,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public int readUnsignedByte() throws IOException {
|
public int readUnsignedByte() throws IOException {
|
||||||
int b = mFile.read();
|
int b = file.read();
|
||||||
|
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +179,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public short readShort() throws IOException {
|
public short readShort() throws IOException {
|
||||||
int byte1 = mFile.read();
|
int byte1 = file.read();
|
||||||
int byte2 = mFile.read();
|
int byte2 = file.read();
|
||||||
|
|
||||||
// only need to test last byte read
|
// only need to test last byte read
|
||||||
// if byte1 is -1 so is byte2
|
// if byte1 is -1 so is byte2
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public int readUnsignedShort() throws IOException {
|
public int readUnsignedShort() throws IOException {
|
||||||
int byte1 = mFile.read();
|
int byte1 = file.read();
|
||||||
int byte2 = mFile.read();
|
int byte2 = file.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
||||||
return (byte2 << 8) + byte1;
|
return (byte2 << 8) + byte1;
|
||||||
}
|
}
|
||||||
@@ -212,11 +222,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public char readChar() throws IOException {
|
public char readChar() throws IOException {
|
||||||
int byte1 = mFile.read();
|
int byte1 = file.read();
|
||||||
int byte2 = mFile.read();
|
int byte2 = file.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public int readInt() throws IOException {
|
public int readInt() throws IOException {
|
||||||
int byte1 = mFile.read();
|
int byte1 = file.read();
|
||||||
int byte2 = mFile.read();
|
int byte2 = file.read();
|
||||||
int byte3 = mFile.read();
|
int byte3 = file.read();
|
||||||
int byte4 = mFile.read();
|
int byte4 = file.read();
|
||||||
|
|
||||||
if (byte4 < 0) {
|
if (byte4 < 0) {
|
||||||
throw new EOFException();
|
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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public long readLong() throws IOException {
|
public long readLong() throws IOException {
|
||||||
long byte1 = mFile.read();
|
long byte1 = file.read();
|
||||||
long byte2 = mFile.read();
|
long byte2 = file.read();
|
||||||
long byte3 = mFile.read();
|
long byte3 = file.read();
|
||||||
long byte4 = mFile.read();
|
long byte4 = file.read();
|
||||||
long byte5 = mFile.read();
|
long byte5 = file.read();
|
||||||
long byte6 = mFile.read();
|
long byte6 = file.read();
|
||||||
long byte7 = mFile.read();
|
long byte7 = file.read();
|
||||||
long byte8 = mFile.read();
|
long byte8 = file.read();
|
||||||
|
|
||||||
if (byte8 < 0) {
|
if (byte8 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
||||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
||||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
+ ((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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public String readUTF() throws IOException {
|
public String readUTF() throws IOException {
|
||||||
int byte1 = mFile.read();
|
int byte1 = file.read();
|
||||||
int byte2 = mFile.read();
|
int byte2 = file.read();
|
||||||
|
|
||||||
if (byte2 < 0) {
|
if (byte2 < 0) {
|
||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
int numbytes = (byte1 << 8) + byte2;
|
int numbytes = (byte1 << 8) + byte2;
|
||||||
char result[] = new char[numbytes];
|
char result[] = new char[numbytes];
|
||||||
int numread = 0;
|
int numread = 0;
|
||||||
@@ -310,27 +325,34 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
}
|
}
|
||||||
else if (test == 12 || test == 13) { // two bytes
|
else if (test == 12 || test == 13) { // two bytes
|
||||||
numread += 2;
|
numread += 2;
|
||||||
|
|
||||||
if (numread > numbytes) {
|
if (numread > numbytes) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
c2 = readUnsignedByte();
|
c2 = readUnsignedByte();
|
||||||
|
|
||||||
if ((c2 & 0xC0) != 0x80) {
|
if ((c2 & 0xC0) != 0x80) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||||
}
|
}
|
||||||
else if (test == 14) { // three bytes
|
else if (test == 14) { // three bytes
|
||||||
numread += 3;
|
numread += 3;
|
||||||
|
|
||||||
if (numread > numbytes) {
|
if (numread > numbytes) {
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
c2 = readUnsignedByte();
|
c2 = readUnsignedByte();
|
||||||
c3 = readUnsignedByte();
|
c3 = readUnsignedByte();
|
||||||
|
|
||||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||||
throw new UTFDataFormatException();
|
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
|
else { // malformed
|
||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
@@ -378,27 +400,27 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
* {@code 0} or if an I/O error occurs.
|
* {@code 0} or if an I/O error occurs.
|
||||||
*/
|
*/
|
||||||
public void seek(final long pos) throws IOException {
|
public void seek(final long pos) throws IOException {
|
||||||
mFile.seek(pos);
|
file.seek(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLength(final long newLength) throws IOException {
|
public void setLength(final long newLength) throws IOException {
|
||||||
mFile.setLength(newLength);
|
file.setLength(newLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int skipBytes(final int n) throws IOException {
|
public int skipBytes(final int n) throws IOException {
|
||||||
return mFile.skipBytes(n);
|
return file.skipBytes(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(final byte[] b) throws IOException {
|
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 {
|
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 {
|
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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public void writeByte(int pByte) throws 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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public void writeShort(int pShort) throws IOException {
|
public void writeShort(int pShort) throws IOException {
|
||||||
mFile.write(pShort & 0xFF);
|
file.write(pShort & 0xFF);
|
||||||
mFile.write((pShort >>> 8) & 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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public void writeChar(int pChar) throws IOException {
|
public void writeChar(int pChar) throws IOException {
|
||||||
mFile.write(pChar & 0xFF);
|
file.write(pChar & 0xFF);
|
||||||
mFile.write((pChar >>> 8) & 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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public void writeInt(int pInt) throws IOException {
|
public void writeInt(int pInt) throws IOException {
|
||||||
mFile.write(pInt & 0xFF);
|
file.write(pInt & 0xFF);
|
||||||
mFile.write((pInt >>> 8) & 0xFF);
|
file.write((pInt >>> 8) & 0xFF);
|
||||||
mFile.write((pInt >>> 16) & 0xFF);
|
file.write((pInt >>> 16) & 0xFF);
|
||||||
mFile.write((pInt >>> 24) & 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.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
*/
|
*/
|
||||||
public void writeLong(long pLong) throws IOException {
|
public void writeLong(long pLong) throws IOException {
|
||||||
mFile.write((int) pLong & 0xFF);
|
file.write((int) pLong & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 8) & 0xFF);
|
file.write((int) (pLong >>> 8) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 16) & 0xFF);
|
file.write((int) (pLong >>> 16) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 24) & 0xFF);
|
file.write((int) (pLong >>> 24) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 32) & 0xFF);
|
file.write((int) (pLong >>> 32) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 40) & 0xFF);
|
file.write((int) (pLong >>> 40) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 48) & 0xFF);
|
file.write((int) (pLong >>> 48) & 0xFF);
|
||||||
mFile.write((int) (pLong >>> 56) & 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.
|
* @param pString the {@code String} value to be written.
|
||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
* @see #writeByte(int)
|
* @see #writeByte(int)
|
||||||
* @see #mFile
|
* @see #file
|
||||||
*/
|
*/
|
||||||
public void writeBytes(String pString) throws IOException {
|
public void writeBytes(String pString) throws IOException {
|
||||||
int length = pString.length();
|
int length = pString.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
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.
|
* @param pString a {@code String} value to be written.
|
||||||
* @throws IOException if the underlying stream throws an IOException.
|
* @throws IOException if the underlying stream throws an IOException.
|
||||||
* @see #writeChar(int)
|
* @see #writeChar(int)
|
||||||
* @see #mFile
|
* @see #file
|
||||||
*/
|
*/
|
||||||
public void writeChars(String pString) throws IOException {
|
public void writeChars(String pString) throws IOException {
|
||||||
int length = pString.length();
|
int length = pString.length();
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
mFile.write(c & 0xFF);
|
file.write(c & 0xFF);
|
||||||
mFile.write((c >>> 8) & 0xFF);
|
file.write((c >>> 8) & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,6 +587,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
|
|
||||||
for (int i = 0; i < numchars; i++) {
|
for (int i = 0; i < numchars; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
|
|
||||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||||
numbytes++;
|
numbytes++;
|
||||||
}
|
}
|
||||||
@@ -579,21 +603,23 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
|||||||
throw new UTFDataFormatException();
|
throw new UTFDataFormatException();
|
||||||
}
|
}
|
||||||
|
|
||||||
mFile.write((numbytes >>> 8) & 0xFF);
|
file.write((numbytes >>> 8) & 0xFF);
|
||||||
mFile.write(numbytes & 0xFF);
|
file.write(numbytes & 0xFF);
|
||||||
|
|
||||||
for (int i = 0; i < numchars; i++) {
|
for (int i = 0; i < numchars; i++) {
|
||||||
int c = pString.charAt(i);
|
int c = pString.charAt(i);
|
||||||
|
|
||||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||||
mFile.write(c);
|
file.write(c);
|
||||||
}
|
}
|
||||||
else if (c > 0x07FF) {
|
else if (c > 0x07FF) {
|
||||||
mFile.write(0xE0 | ((c >> 12) & 0x0F));
|
file.write(0xE0 | ((c >> 12) & 0x0F));
|
||||||
mFile.write(0x80 | ((c >> 6) & 0x3F));
|
file.write(0x80 | ((c >> 6) & 0x3F));
|
||||||
mFile.write(0x80 | (c & 0x3F));
|
file.write(0x80 | (c & 0x3F));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mFile.write(0xC0 | ((c >> 6) & 0x1F));
|
file.write(0xC0 | ((c >> 6) & 0x1F));
|
||||||
mFile.write(0x80 | (c & 0x3F));
|
file.write(0x80 | (c & 0x3F));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+31
-31
@@ -65,13 +65,13 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
|||||||
final static class MemoryCache extends StreamCache {
|
final static class MemoryCache extends StreamCache {
|
||||||
final static int BLOCK_SIZE = 1 << 13;
|
final static int BLOCK_SIZE = 1 << 13;
|
||||||
|
|
||||||
private final List<byte[]> mCache = new ArrayList<byte[]>();
|
private final List<byte[]> cache = new ArrayList<byte[]>();
|
||||||
private long mLength;
|
private long length;
|
||||||
private long mPosition;
|
private long position;
|
||||||
private long mStart;
|
private long start;
|
||||||
|
|
||||||
private byte[] getBlock() throws IOException {
|
private byte[] getBlock() throws IOException {
|
||||||
final long currPos = mPosition - mStart;
|
final long currPos = position - start;
|
||||||
if (currPos < 0) {
|
if (currPos < 0) {
|
||||||
throw new IOException("StreamCache flushed before read position");
|
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");
|
throw new IOException("Memory cache max size exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index >= mCache.size()) {
|
if (index >= cache.size()) {
|
||||||
try {
|
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("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) {
|
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);
|
//System.out.println("index: " + index);
|
||||||
|
|
||||||
return mCache.get((int) index);
|
return cache.get((int) index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(final int pByte) throws IOException {
|
public void write(final int pByte) throws IOException {
|
||||||
byte[] buffer = getBlock();
|
byte[] buffer = getBlock();
|
||||||
|
|
||||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
int idx = (int) (position % BLOCK_SIZE);
|
||||||
buffer[idx] = (byte) pByte;
|
buffer[idx] = (byte) pByte;
|
||||||
mPosition++;
|
position++;
|
||||||
|
|
||||||
if (mPosition > mLength) {
|
if (position > length) {
|
||||||
mLength = mPosition;
|
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 {
|
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||||
byte[] buffer = getBlock();
|
byte[] buffer = getBlock();
|
||||||
for (int i = 0; i < pLength; i++) {
|
for (int i = 0; i < pLength; i++) {
|
||||||
int index = (int) mPosition % BLOCK_SIZE;
|
int index = (int) position % BLOCK_SIZE;
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
buffer = getBlock();
|
buffer = getBlock();
|
||||||
}
|
}
|
||||||
buffer[index] = pBuffer[pOffset + i];
|
buffer[index] = pBuffer[pOffset + i];
|
||||||
|
|
||||||
mPosition++;
|
position++;
|
||||||
}
|
}
|
||||||
if (mPosition > mLength) {
|
if (position > length) {
|
||||||
mLength = mPosition;
|
length = position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (mPosition >= mLength) {
|
if (position >= length) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] buffer = getBlock();
|
byte[] buffer = getBlock();
|
||||||
|
|
||||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
int idx = (int) (position % BLOCK_SIZE);
|
||||||
mPosition++;
|
position++;
|
||||||
|
|
||||||
return buffer[idx] & 0xff;
|
return buffer[idx] & 0xff;
|
||||||
}
|
}
|
||||||
@@ -144,33 +144,33 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
|||||||
// TODO: OptimizeMe!!!
|
// TODO: OptimizeMe!!!
|
||||||
@Override
|
@Override
|
||||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||||
if (mPosition >= mLength) {
|
if (position >= length) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] buffer = getBlock();
|
byte[] buffer = getBlock();
|
||||||
|
|
||||||
int bufferPos = (int) (mPosition % BLOCK_SIZE);
|
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||||
|
|
||||||
// Find maxIdx and simplify test in for-loop
|
// 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;
|
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++) {
|
for (i = 0; i < maxLen; i++) {
|
||||||
pBytes[pOffset + i] = buffer[bufferPos + i];
|
pBytes[pOffset + i] = buffer[bufferPos + i];
|
||||||
}
|
}
|
||||||
|
|
||||||
mPosition += i;
|
position += i;
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seek(final long pPosition) throws IOException {
|
public void seek(final long pPosition) throws IOException {
|
||||||
if (pPosition < mStart) {
|
if (pPosition < start) {
|
||||||
throw new IOException("Seek before flush position");
|
throw new IOException("Seek before flush position");
|
||||||
}
|
}
|
||||||
mPosition = pPosition;
|
position = pPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -178,14 +178,14 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
|||||||
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
|
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
|
||||||
|
|
||||||
for (int i = 0; i < firstPos; i++) {
|
for (int i = 0; i < firstPos; i++) {
|
||||||
mCache.remove(0);
|
cache.remove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mStart = pPosition;
|
start = pPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPosition() {
|
public long getPosition() {
|
||||||
return mPosition;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-14
@@ -50,13 +50,12 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
|||||||
// TODO: Package private SeekableDelegate?
|
// TODO: Package private SeekableDelegate?
|
||||||
|
|
||||||
// TODO: Both read and write must update stream position
|
// 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} */
|
/** This random access stream, wrapped in an {@code InputStream} */
|
||||||
SeekableInputStream mInputView = null;
|
SeekableInputStream inputView = null;
|
||||||
/** This random access stream, wrapped in an {@code OutputStream} */
|
/** 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?
|
// TODO: Create an Input and an Output interface matching InputStream and OutputStream?
|
||||||
public int read() throws IOException {
|
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
|
* @return a {@code SeekableInputStream} reading from this stream
|
||||||
*/
|
*/
|
||||||
public final SeekableInputStream asInputStream() {
|
public final SeekableInputStream asInputStream() {
|
||||||
if (mInputView == null) {
|
if (inputView == null) {
|
||||||
mInputView = new InputStreamView(this);
|
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
|
* @return a {@code SeekableOutputStream} writing to this stream
|
||||||
*/
|
*/
|
||||||
public final SeekableOutputStream asOutputStream() {
|
public final SeekableOutputStream asOutputStream() {
|
||||||
if (mOutputView == null) {
|
if (outputView == null) {
|
||||||
mOutputView = new OutputStreamView(this);
|
outputView = new OutputStreamView(this);
|
||||||
}
|
}
|
||||||
return mOutputView;
|
return outputView;
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class InputStreamView extends SeekableInputStream {
|
static final class InputStreamView extends SeekableInputStream {
|
||||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
// TODO: Consider adding synchonization (on stream) for all operations
|
||||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
// TODO: Is is a good thing that close/flush etc works on stream?
|
||||||
// - Or should it rather just work on the views?
|
// - Or should it rather just work on the views?
|
||||||
// - Allow multiple views?
|
// - Allow multiple views?
|
||||||
|
|
||||||
@@ -190,8 +189,8 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
|||||||
}
|
}
|
||||||
|
|
||||||
static final class OutputStreamView extends SeekableOutputStream {
|
static final class OutputStreamView extends SeekableOutputStream {
|
||||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
// TODO: Consider adding synchonization (on stream) for all operations
|
||||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
// TODO: Is is a good thing that close/flush etc works on stream?
|
||||||
// - Or should it rather just work on the views?
|
// - Or should it rather just work on the views?
|
||||||
// - Allow multiple views?
|
// - Allow multiple views?
|
||||||
|
|
||||||
+38
-26
@@ -43,15 +43,15 @@ import java.util.Stack;
|
|||||||
public abstract class SeekableInputStream extends InputStream implements Seekable {
|
public abstract class SeekableInputStream extends InputStream implements Seekable {
|
||||||
|
|
||||||
// TODO: It's at the moment not possible to create subclasses outside this
|
// 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...
|
// updated from the read/read/read methods...
|
||||||
|
|
||||||
/** The stream position in this stream */
|
/** The stream position in this stream */
|
||||||
long mPosition;
|
long position;
|
||||||
long mFlushedPosition;
|
long flushedPosition;
|
||||||
boolean mClosed;
|
boolean closed;
|
||||||
|
|
||||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
protected Stack<Long> markedPositions = new Stack<Long>();
|
||||||
|
|
||||||
/// InputStream overrides
|
/// InputStream overrides
|
||||||
@Override
|
@Override
|
||||||
@@ -69,17 +69,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
|||||||
* @throws IOException if an I/O exception occurs during skip
|
* @throws IOException if an I/O exception occurs during skip
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final long skip(long pLength) throws IOException {
|
public final long skip(final long pLength) throws IOException {
|
||||||
long pos = mPosition;
|
long pos = position;
|
||||||
if (pos + pLength < mFlushedPosition) {
|
long wantedPosition = pos + pLength;
|
||||||
|
if (wantedPosition < flushedPosition) {
|
||||||
throw new IOException("position < 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
|
// 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
|
@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?
|
// TODO: We don't really need to do this.. Is it a good idea?
|
||||||
try {
|
try {
|
||||||
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition));
|
flushBefore(Math.max(position - pLimit, flushedPosition));
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (IOException ignore) {
|
||||||
// Ignore, as it's not really critical
|
// 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),
|
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||||
// but it's kind of inconsistent with reset that throws IOException...
|
// but it's kind of inconsistent with reset that throws IOException...
|
||||||
if (pPosition < mFlushedPosition) {
|
if (pPosition < flushedPosition) {
|
||||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||||
}
|
}
|
||||||
|
|
||||||
seekImpl(pPosition);
|
seekImpl(pPosition);
|
||||||
mPosition = pPosition;
|
position = pPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||||
|
|
||||||
public final void mark() {
|
public final void mark() {
|
||||||
mMarkedPositions.push(mPosition);
|
markedPositions.push(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void reset() throws IOException {
|
public final void reset() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
if (!mMarkedPositions.isEmpty()) {
|
if (!markedPositions.isEmpty()) {
|
||||||
long newPos = mMarkedPositions.pop();
|
long newPos = markedPositions.pop();
|
||||||
|
|
||||||
// NOTE: This is correct according to javax.imageio (IOException),
|
// NOTE: This is correct according to javax.imageio (IOException),
|
||||||
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
|
// 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");
|
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 {
|
public final void flushBefore(long pPosition) throws IOException {
|
||||||
if (pPosition < mFlushedPosition) {
|
if (pPosition < flushedPosition) {
|
||||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||||
}
|
}
|
||||||
if (pPosition > getStreamPosition()) {
|
if (pPosition > getStreamPosition()) {
|
||||||
@@ -158,7 +170,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
|||||||
}
|
}
|
||||||
checkOpen();
|
checkOpen();
|
||||||
flushBeforeImpl(pPosition);
|
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;
|
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||||
|
|
||||||
public final void flush() throws IOException {
|
public final void flush() throws IOException {
|
||||||
flushBefore(mFlushedPosition);
|
flushBefore(flushedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getFlushedPosition() throws IOException {
|
public final long getFlushedPosition() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
return mFlushedPosition;
|
return flushedPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getStreamPosition() throws IOException {
|
public final long getStreamPosition() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
return mPosition;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void checkOpen() throws IOException {
|
protected final void checkOpen() throws IOException {
|
||||||
if (mClosed) {
|
if (closed) {
|
||||||
throw new IOException("closed");
|
throw new IOException("closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +206,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
|||||||
@Override
|
@Override
|
||||||
public final void close() throws IOException {
|
public final void close() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
mClosed = true;
|
closed = true;
|
||||||
closeImpl();
|
closeImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +223,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() throws Throwable {
|
||||||
if (!mClosed) {
|
if (!closed) {
|
||||||
try {
|
try {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
+17
-17
@@ -43,11 +43,11 @@ import java.util.Stack;
|
|||||||
*/
|
*/
|
||||||
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
|
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
long mPosition;
|
long position;
|
||||||
long mFlushedPosition;
|
long flushedPosition;
|
||||||
boolean mClosed;
|
boolean closed;
|
||||||
|
|
||||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
protected Stack<Long> markedPositions = new Stack<Long>();
|
||||||
|
|
||||||
/// Outputstream overrides
|
/// Outputstream overrides
|
||||||
@Override
|
@Override
|
||||||
@@ -63,28 +63,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
|||||||
|
|
||||||
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||||
// but it's inconsistent with reset that throws IOException...
|
// but it's inconsistent with reset that throws IOException...
|
||||||
if (pPosition < mFlushedPosition) {
|
if (pPosition < flushedPosition) {
|
||||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||||
}
|
}
|
||||||
|
|
||||||
seekImpl(pPosition);
|
seekImpl(pPosition);
|
||||||
mPosition = pPosition;
|
position = pPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||||
|
|
||||||
public final void mark() {
|
public final void mark() {
|
||||||
mMarkedPositions.push(mPosition);
|
markedPositions.push(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void reset() throws IOException {
|
public final void reset() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
if (!mMarkedPositions.isEmpty()) {
|
if (!markedPositions.isEmpty()) {
|
||||||
long newPos = mMarkedPositions.pop();
|
long newPos = markedPositions.pop();
|
||||||
|
|
||||||
// TODO: This is correct according to javax.imageio (IOException),
|
// TODO: This is correct according to javax.imageio (IOException),
|
||||||
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
|
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
|
||||||
if (newPos < mFlushedPosition) {
|
if (newPos < flushedPosition) {
|
||||||
throw new IOException("Previous marked position has been discarded!");
|
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 {
|
public final void flushBefore(long pPosition) throws IOException {
|
||||||
if (pPosition < mFlushedPosition) {
|
if (pPosition < flushedPosition) {
|
||||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||||
}
|
}
|
||||||
if (pPosition > getStreamPosition()) {
|
if (pPosition > getStreamPosition()) {
|
||||||
@@ -101,28 +101,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
|||||||
}
|
}
|
||||||
checkOpen();
|
checkOpen();
|
||||||
flushBeforeImpl(pPosition);
|
flushBeforeImpl(pPosition);
|
||||||
mFlushedPosition = pPosition;
|
flushedPosition = pPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void flush() throws IOException {
|
public final void flush() throws IOException {
|
||||||
flushBefore(mFlushedPosition);
|
flushBefore(flushedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getFlushedPosition() throws IOException {
|
public final long getFlushedPosition() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
return mFlushedPosition;
|
return flushedPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final long getStreamPosition() throws IOException {
|
public final long getStreamPosition() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
return mPosition;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final void checkOpen() throws IOException {
|
protected final void checkOpen() throws IOException {
|
||||||
if (mClosed) {
|
if (closed) {
|
||||||
throw new IOException("closed");
|
throw new IOException("closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
|||||||
@Override
|
@Override
|
||||||
public final void close() throws IOException {
|
public final void close() throws IOException {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
mClosed = true;
|
closed = true;
|
||||||
closeImpl();
|
closeImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
+46
-44
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
@@ -42,13 +44,13 @@ import java.io.Reader;
|
|||||||
*/
|
*/
|
||||||
public class StringArrayReader extends StringReader {
|
public class StringArrayReader extends StringReader {
|
||||||
|
|
||||||
private StringReader mCurrent;
|
private StringReader current;
|
||||||
private String[] mStrings;
|
private String[] strings;
|
||||||
protected final Object mLock;
|
protected final Object finalLock;
|
||||||
private int mCurrentSting;
|
private int currentSting;
|
||||||
private int mMarkedString;
|
private int markedString;
|
||||||
private int mMark;
|
private int mark;
|
||||||
private int mNext;
|
private int next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new string array reader.
|
* Create a new string array reader.
|
||||||
@@ -57,28 +59,28 @@ public class StringArrayReader extends StringReader {
|
|||||||
*/
|
*/
|
||||||
public StringArrayReader(final String[] pStrings) {
|
public StringArrayReader(final String[] pStrings) {
|
||||||
super("");
|
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
|
// reference can't change, only it's elements
|
||||||
|
|
||||||
mStrings = pStrings.clone(); // Defensive copy for content
|
strings = pStrings.clone(); // Defensive copy for content
|
||||||
nextReader();
|
nextReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected final Reader nextReader() {
|
protected final Reader nextReader() {
|
||||||
if (mCurrentSting >= mStrings.length) {
|
if (currentSting >= strings.length) {
|
||||||
mCurrent = new EmptyReader();
|
current = new EmptyReader();
|
||||||
}
|
}
|
||||||
else {
|
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
|
* @throws IOException if the stream is closed
|
||||||
*/
|
*/
|
||||||
protected final void ensureOpen() throws IOException {
|
protected final void ensureOpen() throws IOException {
|
||||||
if (mStrings == null) {
|
if (strings == null) {
|
||||||
throw new IOException("Stream closed");
|
throw new IOException("Stream closed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
super.close();
|
super.close();
|
||||||
mStrings = null;
|
strings = null;
|
||||||
mCurrent.close();
|
current.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mark(int pReadLimit) throws IOException {
|
public void mark(int pReadLimit) throws IOException {
|
||||||
@@ -103,29 +105,29 @@ public class StringArrayReader extends StringReader {
|
|||||||
throw new IllegalArgumentException("Read limit < 0");
|
throw new IllegalArgumentException("Read limit < 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
mMark = mNext;
|
mark = next;
|
||||||
mMarkedString = mCurrentSting;
|
markedString = currentSting;
|
||||||
|
|
||||||
mCurrent.mark(pReadLimit);
|
current.mark(pReadLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() throws IOException {
|
public void reset() throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
|
|
||||||
if (mCurrentSting != mMarkedString) {
|
if (currentSting != markedString) {
|
||||||
mCurrentSting = mMarkedString - 1;
|
currentSting = markedString - 1;
|
||||||
nextReader();
|
nextReader();
|
||||||
mCurrent.skip(mMark);
|
current.skip(mark);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mCurrent.reset();
|
current.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext = mMark;
|
next = mark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,49 +136,49 @@ public class StringArrayReader extends StringReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
int read = mCurrent.read();
|
int read = current.read();
|
||||||
|
|
||||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
if (read < 0 && currentSting < strings.length) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return read(); // In case of empty strings
|
return read(); // In case of empty strings
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext++;
|
next++;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
if (read < 0 && currentSting < strings.length) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return read(pBuffer, pOffset, pLength); // In case of empty strings
|
return read(pBuffer, pOffset, pLength); // In case of empty strings
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += read;
|
next += read;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean ready() throws IOException {
|
public boolean ready() throws IOException {
|
||||||
return mCurrent.ready();
|
return current.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long skip(long pChars) throws IOException {
|
public long skip(long pChars) throws IOException {
|
||||||
synchronized (mLock) {
|
synchronized (finalLock) {
|
||||||
long skipped = mCurrent.skip(pChars);
|
long skipped = current.skip(pChars);
|
||||||
|
|
||||||
if (skipped == 0 && mCurrentSting < mStrings.length) {
|
if (skipped == 0 && currentSting < strings.length) {
|
||||||
nextReader();
|
nextReader();
|
||||||
return skip(pChars);
|
return skip(pChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += skipped;
|
next += skipped;
|
||||||
|
|
||||||
return skipped;
|
return skipped;
|
||||||
}
|
}
|
||||||
+14
-14
@@ -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 $
|
* @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 {
|
public final class SubStream extends FilterInputStream {
|
||||||
private long mLeft;
|
private long bytesLeft;
|
||||||
private int mMarkLimit;
|
private int markLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
* 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) {
|
public SubStream(final InputStream pStream, final long pLength) {
|
||||||
super(Validate.notNull(pStream, "stream"));
|
super(Validate.notNull(pStream, "stream"));
|
||||||
mLeft = pLength;
|
bytesLeft = pLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,32 +64,32 @@ public final class SubStream extends FilterInputStream {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// NOTE: Do not close the underlying stream
|
// NOTE: Do not close the underlying stream
|
||||||
while (mLeft > 0) {
|
while (bytesLeft > 0) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
skip(mLeft);
|
skip(bytesLeft);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
public int available() throws IOException {
|
||||||
return (int) Math.min(super.available(), mLeft);
|
return (int) Math.min(super.available(), bytesLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int pReadLimit) {
|
public void mark(int pReadLimit) {
|
||||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
super.mark(pReadLimit);// This either succeeds or does nothing...
|
||||||
mMarkLimit = pReadLimit;
|
markLimit = pReadLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() throws IOException {
|
public void reset() throws IOException {
|
||||||
super.reset();// This either succeeds or throws IOException
|
super.reset();// This either succeeds or throws IOException
|
||||||
mLeft += mMarkLimit;
|
bytesLeft += markLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (mLeft-- <= 0) {
|
if (bytesLeft-- <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return super.read();
|
return super.read();
|
||||||
@@ -102,12 +102,12 @@ public final class SubStream extends FilterInputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||||
if (mLeft <= 0) {
|
if (bytesLeft <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||||
mLeft = read < 0 ? 0 : mLeft - read;
|
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +118,8 @@ public final class SubStream extends FilterInputStream {
|
|||||||
* @return the maximum number of bytes to read
|
* @return the maximum number of bytes to read
|
||||||
*/
|
*/
|
||||||
private long findMaxLen(long pLength) {
|
private long findMaxLen(long pLength) {
|
||||||
if (mLeft < pLength) {
|
if (bytesLeft < pLength) {
|
||||||
return (int) Math.max(mLeft, 0);
|
return (int) Math.max(bytesLeft, 0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return pLength;
|
return pLength;
|
||||||
@@ -129,7 +129,7 @@ public final class SubStream extends FilterInputStream {
|
|||||||
@Override
|
@Override
|
||||||
public long skip(long pLength) throws IOException {
|
public long skip(long pLength) throws IOException {
|
||||||
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
||||||
mLeft -= skipped;
|
bytesLeft -= skipped;
|
||||||
return skipped;
|
return skipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+25
-24
@@ -50,7 +50,8 @@ final class Win32Lnk extends File {
|
|||||||
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
|
(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_ITEM_ID_LIST = 0x01;
|
||||||
private static final int FLAG_FILE_LOC_INFO = 0x02;
|
private static final int FLAG_FILE_LOC_INFO = 0x02;
|
||||||
private static final int FLAG_DESC_STRING = 0x04;
|
private static final int FLAG_DESC_STRING = 0x04;
|
||||||
@@ -65,10 +66,10 @@ final class Win32Lnk extends File {
|
|||||||
File target = parse(this);
|
File target = parse(this);
|
||||||
if (target == this) {
|
if (target == this) {
|
||||||
// NOTE: This is a workaround
|
// 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);
|
target = new File(pPath);
|
||||||
}
|
}
|
||||||
mTarget = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
Win32Lnk(final File pPath) throws IOException {
|
Win32Lnk(final File pPath) throws IOException {
|
||||||
@@ -336,24 +337,24 @@ final class Win32Lnk extends File {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public File getTarget() {
|
public File getTarget() {
|
||||||
return mTarget;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// java.io.File overrides below
|
// java.io.File overrides below
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectory() {
|
public boolean isDirectory() {
|
||||||
return mTarget.isDirectory();
|
return target.isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRead() {
|
public boolean canRead() {
|
||||||
return mTarget.canRead();
|
return target.canRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canWrite() {
|
public boolean canWrite() {
|
||||||
return mTarget.canWrite();
|
return target.canWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: equals is implemented using compareto == 0
|
// NOTE: equals is implemented using compareto == 0
|
||||||
@@ -362,7 +363,7 @@ final class Win32Lnk extends File {
|
|||||||
// TODO: Verify this
|
// TODO: Verify this
|
||||||
// Probably not a good idea, as it IS NOT THE SAME file
|
// Probably not a good idea, as it IS NOT THE SAME file
|
||||||
// It's probably better to not override
|
// 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
|
@Override
|
||||||
public boolean exists() {
|
public boolean exists() {
|
||||||
return mTarget.exists();
|
return target.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A .lnk may be absolute
|
// A .lnk may be absolute
|
||||||
@@ -385,12 +386,12 @@ final class Win32Lnk extends File {
|
|||||||
// Theses should be resolved according to the API (for Unix).
|
// Theses should be resolved according to the API (for Unix).
|
||||||
@Override
|
@Override
|
||||||
public File getCanonicalFile() throws IOException {
|
public File getCanonicalFile() throws IOException {
|
||||||
return mTarget.getCanonicalFile();
|
return target.getCanonicalFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCanonicalPath() throws IOException {
|
public String getCanonicalPath() throws IOException {
|
||||||
return mTarget.getCanonicalPath();
|
return target.getCanonicalPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
//public String getName() {
|
//public String getName() {
|
||||||
@@ -402,47 +403,47 @@ final class Win32Lnk extends File {
|
|||||||
// public boolean isAbsolute() {
|
// public boolean isAbsolute() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isFile() {
|
public boolean isFile() {
|
||||||
return mTarget.isFile();
|
return target.isFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isHidden() {
|
public boolean isHidden() {
|
||||||
return mTarget.isHidden();
|
return target.isHidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long lastModified() {
|
public long lastModified() {
|
||||||
return mTarget.lastModified();
|
return target.lastModified();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long length() {
|
public long length() {
|
||||||
return mTarget.length();
|
return target.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] list() {
|
public String[] list() {
|
||||||
return mTarget.list();
|
return target.list();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] list(final FilenameFilter filter) {
|
public String[] list(final FilenameFilter filter) {
|
||||||
return mTarget.list(filter);
|
return target.list(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File[] listFiles() {
|
public File[] listFiles() {
|
||||||
return Win32File.wrap(mTarget.listFiles());
|
return Win32File.wrap(target.listFiles());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File[] listFiles(final FileFilter filter) {
|
public File[] listFiles(final FileFilter filter) {
|
||||||
return Win32File.wrap(mTarget.listFiles(filter));
|
return Win32File.wrap(target.listFiles(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File[] listFiles(final FilenameFilter filter) {
|
public File[] listFiles(final FilenameFilter filter) {
|
||||||
return Win32File.wrap(mTarget.listFiles(filter));
|
return Win32File.wrap(target.listFiles(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Makes no sense, does it?
|
// Makes no sense, does it?
|
||||||
@@ -454,19 +455,19 @@ final class Win32Lnk extends File {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setLastModified(long time) {
|
public boolean setLastModified(long time) {
|
||||||
return mTarget.setLastModified(time);
|
return target.setLastModified(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean setReadOnly() {
|
public boolean setReadOnly() {
|
||||||
return mTarget.setReadOnly();
|
return target.setReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
if (mTarget.equals(this)) {
|
if (target.equals(this)) {
|
||||||
return super.toString();
|
return super.toString();
|
||||||
}
|
}
|
||||||
return super.toString() + " -> " + mTarget.toString();
|
return super.toString() + " -> " + target.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-17
@@ -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 $
|
* @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 {
|
public class WriterOutputStream extends OutputStream {
|
||||||
protected Writer mWriter;
|
protected Writer writer;
|
||||||
final protected Decoder mDecoder;
|
final protected Decoder decoder;
|
||||||
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024);
|
final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
|
||||||
private volatile boolean mIsFlushing = false; // Ugly but critical...
|
|
||||||
|
private volatile boolean isFlushing = false; // Ugly but critical...
|
||||||
|
|
||||||
private static final boolean NIO_AVAILABLE = isNIOAvailable();
|
private static final boolean NIO_AVAILABLE = isNIOAvailable();
|
||||||
|
|
||||||
@@ -71,8 +72,8 @@ public class WriterOutputStream extends OutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WriterOutputStream(final Writer pWriter, final String pCharset) {
|
public WriterOutputStream(final Writer pWriter, final String pCharset) {
|
||||||
mWriter = pWriter;
|
writer = pWriter;
|
||||||
mDecoder = getDecoder(pCharset);
|
decoder = getDecoder(pCharset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WriterOutputStream(final Writer pWriter) {
|
public WriterOutputStream(final Writer pWriter) {
|
||||||
@@ -94,14 +95,14 @@ public class WriterOutputStream extends OutputStream {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
flush();
|
flush();
|
||||||
mWriter.close();
|
writer.close();
|
||||||
mWriter = null;
|
writer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
flushBuffer();
|
flushBuffer();
|
||||||
mWriter.flush();
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -115,22 +116,22 @@ public class WriterOutputStream extends OutputStream {
|
|||||||
@Override
|
@Override
|
||||||
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||||
flushBuffer();
|
flushBuffer();
|
||||||
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength);
|
decoder.decodeTo(writer, pBytes, pOffset, pLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void write(int pByte) {
|
public final void write(int pByte) {
|
||||||
// TODO: Is it possible to know if this is a good place in the stream to
|
// 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..
|
// flush? It might be in the middle of a multi-byte encoded character..
|
||||||
mBufferStream.write(pByte);
|
bufferStream.write(pByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushBuffer() throws IOException {
|
private void flushBuffer() throws IOException {
|
||||||
if (!mIsFlushing && mBufferStream.size() > 0) {
|
if (!isFlushing && bufferStream.size() > 0) {
|
||||||
mIsFlushing = true;
|
isFlushing = true;
|
||||||
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
|
bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
|
||||||
mBufferStream.reset();
|
bufferStream.reset();
|
||||||
mIsFlushing = false;
|
isFlushing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +139,7 @@ public class WriterOutputStream extends OutputStream {
|
|||||||
public static void main(String[] pArgs) throws IOException {
|
public static void main(String[] pArgs) throws IOException {
|
||||||
int iterations = 1000000;
|
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;
|
Decoder d;
|
||||||
long start;
|
long start;
|
||||||
+39
-39
@@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc;
|
|||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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.
|
* 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
|
// TODO: Move to other package or make public
|
||||||
abstract class AbstractRLEDecoder implements Decoder {
|
abstract class AbstractRLEDecoder implements Decoder {
|
||||||
protected final byte[] mRow;
|
protected final byte[] row;
|
||||||
protected final int mWidth;
|
protected final int width;
|
||||||
protected int mSrcX;
|
protected int srcX;
|
||||||
protected int mSrcY;
|
protected int srcY;
|
||||||
protected int mDstX;
|
protected int dstX;
|
||||||
protected int mDstY;
|
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.
|
* etc, we need to know height and width of the image.
|
||||||
*
|
*
|
||||||
* @param pWidth 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) {
|
AbstractRLEDecoder(final int pWidth, final int pHeight) {
|
||||||
mWidth = pWidth;
|
width = pWidth;
|
||||||
int bytesPerRow = mWidth;
|
int bytesPerRow = width;
|
||||||
int mod = bytesPerRow % 4;
|
int mod = bytesPerRow % 4;
|
||||||
|
|
||||||
if (mod != 0) {
|
if (mod != 0) {
|
||||||
bytesPerRow += 4 - mod;
|
bytesPerRow += 4 - mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRow = new byte[bytesPerRow];
|
row = new byte[bytesPerRow];
|
||||||
|
|
||||||
mSrcX = 0;
|
srcX = 0;
|
||||||
mSrcY = pHeight - 1;
|
srcY = pHeight - 1;
|
||||||
|
|
||||||
mDstX = mSrcX;
|
dstX = srcX;
|
||||||
mDstY = mSrcY;
|
dstY = srcY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes one full row of image data.
|
* 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.
|
* Decodes as much data as possible, from the stream into the buffer.
|
||||||
*
|
*
|
||||||
* @param pStream the input stream containing RLE data
|
* @param stream the input stream containing RLE data
|
||||||
* @param pBuffer tge buffer to decode the data to
|
* @param buffer the buffer to decode the data to
|
||||||
*
|
*
|
||||||
* @return the number of bytes decoded from the stream, to the buffer
|
* @return the number of bytes decoded from the stream, to the buffer
|
||||||
*
|
*
|
||||||
* @throws IOException if an I/O related exception ocurs while reading
|
* @throws IOException if an I/O related exception ocurs while reading
|
||||||
*/
|
*/
|
||||||
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
|
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||||
int decoded = 0;
|
while (buffer.hasRemaining() && dstY >= 0) {
|
||||||
|
|
||||||
while (decoded < pBuffer.length && mDstY >= 0) {
|
|
||||||
// NOTE: Decode only full rows, don't decode if y delta
|
// NOTE: Decode only full rows, don't decode if y delta
|
||||||
if (mDstX == 0 && mSrcY == mDstY) {
|
if (dstX == 0 && srcY == dstY) {
|
||||||
decodeRow(pStream);
|
decodeRow(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded);
|
int length = Math.min(row.length - dstX, buffer.remaining());
|
||||||
System.arraycopy(mRow, mDstX, pBuffer, decoded, length);
|
// System.arraycopy(row, dstX, buffer, decoded, length);
|
||||||
mDstX += length;
|
buffer.put(row, 0, length);
|
||||||
decoded += length;
|
dstX += length;
|
||||||
|
// decoded += length;
|
||||||
|
|
||||||
if (mDstX == mRow.length) {
|
if (dstX == row.length) {
|
||||||
mDstX = 0;
|
dstX = 0;
|
||||||
mDstY--;
|
dstY--;
|
||||||
|
|
||||||
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
|
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
|
||||||
// gap with zero-bytes
|
// gap with zero-bytes
|
||||||
if (mDstY > mSrcY) {
|
if (dstY > srcY) {
|
||||||
for (int i = 0; i < mRow.length; i++) {
|
for (int i = 0; i < row.length; i++) {
|
||||||
mRow[i] = 0x00;
|
row[i] = 0x00;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded;
|
return buffer.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,7 +131,7 @@ abstract class AbstractRLEDecoder implements Decoder {
|
|||||||
*
|
*
|
||||||
* @throws EOFException if {@code pByte} is negative
|
* @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) {
|
if (pByte < 0) {
|
||||||
throw new EOFException("Premature end of file");
|
throw new EOFException("Premature end of file");
|
||||||
}
|
}
|
||||||
+25
-38
@@ -28,9 +28,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.*;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code Decoder} implementation for standard base64 encoding.
|
* {@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
|
* 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
|
//0 1 2 3 4 5 6 7
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
|
||||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
|
'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;
|
final static byte[] PEM_CONVERT_ARRAY;
|
||||||
|
|
||||||
private byte[] mDecodeBuffer = new byte[4];
|
private byte[] decodeBuffer = new byte[4];
|
||||||
private ByteArrayOutputStream mWrapped;
|
|
||||||
private Object mWrappedObject;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
PEM_CONVERT_ARRAY = new byte[256];
|
PEM_CONVERT_ARRAY = new byte[256];
|
||||||
@@ -93,7 +91,7 @@ public final class Base64Decoder implements Decoder {
|
|||||||
return pLength;
|
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 {
|
throws IOException {
|
||||||
|
|
||||||
byte byte0 = -1;
|
byte byte0 = -1;
|
||||||
@@ -116,8 +114,8 @@ public final class Base64Decoder implements Decoder {
|
|||||||
}
|
}
|
||||||
} while (read == 10 || read == 13);
|
} while (read == 10 || read == 13);
|
||||||
|
|
||||||
mDecodeBuffer[0] = (byte) read;
|
decodeBuffer[0] = (byte) read;
|
||||||
read = readFully(pInput, mDecodeBuffer, 1, pLength - 1);
|
read = readFully(pInput, decodeBuffer, 1, pLength - 1);
|
||||||
|
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -125,38 +123,38 @@ public final class Base64Decoder implements Decoder {
|
|||||||
|
|
||||||
int length = pLength;
|
int length = pLength;
|
||||||
|
|
||||||
if (length > 3 && mDecodeBuffer[3] == 61) {
|
if (length > 3 && decodeBuffer[3] == 61) {
|
||||||
length = 3;
|
length = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > 2 && mDecodeBuffer[2] == 61) {
|
if (length > 2 && decodeBuffer[2] == 61) {
|
||||||
length = 2;
|
length = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (length) {
|
switch (length) {
|
||||||
case 4:
|
case 4:
|
||||||
byte3 = PEM_CONVERT_ARRAY[mDecodeBuffer[3] & 255];
|
byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
|
||||||
// fall through
|
// fall through
|
||||||
case 3:
|
case 3:
|
||||||
byte2 = PEM_CONVERT_ARRAY[mDecodeBuffer[2] & 255];
|
byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
|
||||||
// fall through
|
// fall through
|
||||||
case 2:
|
case 2:
|
||||||
byte1 = PEM_CONVERT_ARRAY[mDecodeBuffer[1] & 255];
|
byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
|
||||||
byte0 = PEM_CONVERT_ARRAY[mDecodeBuffer[0] & 255];
|
byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
|
||||||
// fall through
|
// fall through
|
||||||
default:
|
default:
|
||||||
switch (length) {
|
switch (length) {
|
||||||
case 2:
|
case 2:
|
||||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||||
pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63));
|
pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,34 +164,23 @@ public final class Base64Decoder implements Decoder {
|
|||||||
return true;
|
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 {
|
do {
|
||||||
int k = 72;
|
int k = 72;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i + 4 < k; i += 4) {
|
for (i = 0; i + 4 < k; i += 4) {
|
||||||
if(!decodeAtom(pInput, pOutput, 4)) {
|
if(!decodeAtom(stream, buffer, 4)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!decodeAtom(pInput, pOutput, k - i)) {
|
if (!decodeAtom(stream, buffer, k - i)) {
|
||||||
break;
|
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 {
|
return buffer.position();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+30
-36
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
|
|||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code Encoder} implementation for standard base64 encoding.
|
* {@code Encoder} implementation for standard base64 encoding.
|
||||||
@@ -44,15 +45,9 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class Base64Encoder implements Encoder {
|
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
|
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
|
// TODO: Implement
|
||||||
// NOTE: This is impossible, given the current spec, as we need to either:
|
// 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
|
// to ensure proper end of stream handling
|
||||||
|
|
||||||
int length;
|
int length;
|
||||||
int offset = pOffset;
|
|
||||||
|
|
||||||
// TODO: Temp impl, will only work for single writes
|
// TODO: Temp impl, will only work for single writes
|
||||||
while ((pBuffer.length - offset) > 0) {
|
while (buffer.hasRemaining()) {
|
||||||
byte a, b, c;
|
byte a, b, c;
|
||||||
|
|
||||||
if ((pBuffer.length - offset) > 2) {
|
// if ((buffer.remaining()) > 2) {
|
||||||
length = 3;
|
// length = 3;
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
length = pBuffer.length - offset;
|
// length = buffer.remaining();
|
||||||
}
|
// }
|
||||||
|
length = Math.min(3, buffer.remaining());
|
||||||
|
|
||||||
switch (length) {
|
switch (length) {
|
||||||
case 1:
|
case 1:
|
||||||
a = pBuffer[offset];
|
a = buffer.get();
|
||||||
b = 0;
|
b = 0;
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||||
pStream.write('=');
|
stream.write('=');
|
||||||
pStream.write('=');
|
stream.write('=');
|
||||||
offset++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
a = pBuffer[offset];
|
a = buffer.get();
|
||||||
b = pBuffer[offset + 1];
|
b = buffer.get();
|
||||||
c = 0;
|
c = 0;
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||||
pStream.write('=');
|
stream.write('=');
|
||||||
offset += offset + 2; // ???
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
a = pBuffer[offset];
|
a = buffer.get();
|
||||||
b = pBuffer[offset + 1];
|
b = buffer.get();
|
||||||
c = pBuffer[offset + 2];
|
c = buffer.get();
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||||
pStream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
|
stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
|
||||||
offset = offset + 3;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -31,7 +31,7 @@ package com.twelvemonkeys.io.enc;
|
|||||||
import java.io.IOException;
|
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/>
|
* <p/>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
+11
-9
@@ -28,8 +28,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for decoders.
|
* Interface for decoders.
|
||||||
@@ -47,18 +48,19 @@ import java.io.IOException;
|
|||||||
public interface Decoder {
|
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.
|
* into the given buffer.
|
||||||
*
|
*
|
||||||
* @param pStream the inputstream to decode data from
|
* @param stream the input stream to decode data from
|
||||||
* @param pBuffer buffer to store the read data
|
* @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.
|
* if there is no more data because the end of the stream has been reached.
|
||||||
*
|
*
|
||||||
* @throws DecodeException if encoded data is corrupt
|
* @throws DecodeException if encoded data is corrupt.
|
||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs.
|
||||||
* @throws java.io.EOFException if a premature end-of-file is encountered
|
* @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;
|
||||||
}
|
}
|
||||||
+55
-54
@@ -28,9 +28,10 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.FilterInputStream;
|
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
|
* An {@code InputStream} that provides on-the-fly decoding from an underlying
|
||||||
@@ -43,15 +44,13 @@ 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 $
|
* @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 {
|
public final class DecoderStream extends FilterInputStream {
|
||||||
|
protected final ByteBuffer buffer;
|
||||||
protected int mBufferPos;
|
protected final Decoder decoder;
|
||||||
protected int mBufferLimit;
|
|
||||||
protected final byte[] mBuffer;
|
|
||||||
protected final Decoder mDecoder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder stream and chains it to the
|
* Creates a new decoder stream and chains it to the
|
||||||
* input stream specified by the {@code pStream} argument.
|
* input stream specified by the {@code pStream} argument.
|
||||||
|
* The stream will use a default decode buffer size.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream.
|
* @param pStream the underlying input stream.
|
||||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||||
@@ -59,31 +58,40 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
* @see java.io.FilterInputStream#in
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
||||||
|
// TODO: Let the decoder decide preferred buffer size
|
||||||
|
this(pStream, pDecoder, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new decoder stream and chains it to the
|
||||||
|
* input stream specified by the {@code pStream} argument.
|
||||||
|
*
|
||||||
|
* @param pStream the underlying input stream.
|
||||||
|
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||||
|
* @param pBufferSize the size of the decode buffer
|
||||||
|
*
|
||||||
|
* @see java.io.FilterInputStream#in
|
||||||
|
*/
|
||||||
|
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||||
super(pStream);
|
super(pStream);
|
||||||
mDecoder = pDecoder;
|
|
||||||
mBuffer = new byte[1024];
|
decoder = pDecoder;
|
||||||
mBufferPos = 0;
|
buffer = ByteBuffer.allocate(pBufferSize);
|
||||||
mBufferLimit = 0;
|
buffer.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int available() throws IOException {
|
public int available() throws IOException {
|
||||||
return mBufferLimit - mBufferPos + super.available();
|
return buffer.remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (mBufferPos == mBufferLimit) {
|
if (!buffer.hasRemaining()) {
|
||||||
mBufferLimit = fill();
|
if (fill() < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mBufferLimit < 0) {
|
return buffer.get() & 0xff;
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mBuffer[mBufferPos++] & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int read(final byte pBytes[]) throws IOException {
|
|
||||||
return read(pBytes, 0, pBytes.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
||||||
@@ -99,8 +107,10 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// End of file?
|
// End of file?
|
||||||
if ((mBufferLimit - mBufferPos) < 0) {
|
if (!buffer.hasRemaining()) {
|
||||||
return -1;
|
if (fill() < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read until we have read pLength bytes, or have reached EOF
|
// Read until we have read pLength bytes, or have reached EOF
|
||||||
@@ -108,26 +118,20 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
int off = pOffset;
|
int off = pOffset;
|
||||||
|
|
||||||
while (pLength > count) {
|
while (pLength > count) {
|
||||||
int avail = mBufferLimit - mBufferPos;
|
if (!buffer.hasRemaining()) {
|
||||||
|
if (fill() < 0) {
|
||||||
if (avail <= 0) {
|
|
||||||
mBufferLimit = fill();
|
|
||||||
|
|
||||||
if (mBufferLimit < 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy as many bytes as possible
|
// Copy as many bytes as possible
|
||||||
int dstLen = Math.min(pLength - count, avail);
|
int dstLen = Math.min(pLength - count, buffer.remaining());
|
||||||
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen);
|
buffer.get(pBytes, off, dstLen);
|
||||||
|
|
||||||
mBufferPos += dstLen;
|
|
||||||
|
|
||||||
// Update offset (rest)
|
// Update offset (rest)
|
||||||
off += dstLen;
|
off += dstLen;
|
||||||
|
|
||||||
// Inrease count
|
// Increase count
|
||||||
count += dstLen;
|
count += dstLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,29 +140,25 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
public long skip(final long pLength) throws IOException {
|
public long skip(final long pLength) throws IOException {
|
||||||
// End of file?
|
// End of file?
|
||||||
if (mBufferLimit - mBufferPos < 0) {
|
if (!buffer.hasRemaining()) {
|
||||||
return 0;
|
if (fill() < 0) {
|
||||||
|
return 0; // Yes, 0, not -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
// Skip until we have skipped pLength bytes, or have reached EOF
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
|
||||||
while (total < pLength) {
|
while (total < pLength) {
|
||||||
int avail = mBufferLimit - mBufferPos;
|
if (!buffer.hasRemaining()) {
|
||||||
|
if (fill() < 0) {
|
||||||
if (avail == 0) {
|
|
||||||
mBufferLimit = fill();
|
|
||||||
|
|
||||||
if (mBufferLimit < 0) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Skipped can never be more than avail, which is
|
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
||||||
// an int, so the cast is safe
|
int skipped = (int) Math.min(pLength - total, buffer.remaining());
|
||||||
int skipped = (int) Math.min(pLength - total, avail);
|
buffer.position(buffer.position() + skipped);
|
||||||
|
|
||||||
mBufferPos += skipped; // Just skip these bytes
|
|
||||||
total += skipped;
|
total += skipped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,19 +174,20 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
protected int fill() throws IOException {
|
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
|
// TODO: Enforce this in test case, leave here to aid debugging
|
||||||
if (read > mBuffer.length) {
|
if (read > buffer.capacity()) {
|
||||||
throw new AssertionError(
|
throw new AssertionError(
|
||||||
String.format(
|
String.format(
|
||||||
"Decode beyond buffer (%d): %d (using %s decoder)",
|
"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) {
|
if (read == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
+7
-9
@@ -30,11 +30,12 @@ package com.twelvemonkeys.io.enc;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
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
|
* 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/>
|
* <p/>
|
||||||
* Important note: Encoder implementations are typically not synchronized.
|
* Important note: Encoder implementations are typically not synchronized.
|
||||||
*
|
*
|
||||||
@@ -47,18 +48,15 @@ import java.io.OutputStream;
|
|||||||
public interface Encoder {
|
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.
|
* from the given buffer.
|
||||||
*
|
*
|
||||||
* @param pStream the outputstream to encode data to
|
* @param stream the output stream to encode data to
|
||||||
* @param pBuffer buffer to read data from
|
* @param buffer buffer to read data from
|
||||||
* @param pOffset offset into the buffer array
|
|
||||||
* @param pLength length of data in the buffer
|
|
||||||
*
|
*
|
||||||
* @throws java.io.IOException if an I/O error occurs
|
* @throws java.io.IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength)
|
void encode(OutputStream stream, ByteBuffer buffer) throws IOException;
|
||||||
throws IOException;
|
|
||||||
|
|
||||||
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
|
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
|
||||||
// void flush()?
|
// void flush()?
|
||||||
+22
-22
@@ -29,8 +29,9 @@
|
|||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
import java.io.FilterOutputStream;
|
import java.io.FilterOutputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@code OutputStream} that provides on-the-fly encoding to an underlying
|
* 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 $
|
* @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 {
|
public final class EncoderStream extends FilterOutputStream {
|
||||||
|
// TODO: This class need a test case ASAP!!!
|
||||||
|
|
||||||
protected final Encoder mEncoder;
|
protected final Encoder encoder;
|
||||||
private final boolean mFlushOnWrite;
|
private final boolean flushOnWrite;
|
||||||
|
|
||||||
protected int mBufferPos;
|
protected final ByteBuffer buffer;
|
||||||
protected final byte[] mBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an output stream filter built on top of the specified
|
* 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) {
|
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
||||||
super(pStream);
|
super(pStream);
|
||||||
|
|
||||||
mEncoder = pEncoder;
|
encoder = pEncoder;
|
||||||
mFlushOnWrite = pFlushOnWrite;
|
flushOnWrite = pFlushOnWrite;
|
||||||
|
|
||||||
mBuffer = new byte[1024];
|
buffer = ByteBuffer.allocate(1024);
|
||||||
mBufferPos = 0;
|
buffer.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
@@ -91,12 +92,14 @@ public final class EncoderStream extends FilterOutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void encodeBuffer() throws IOException {
|
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
|
// Make sure all remaining data in buffer is written to the stream
|
||||||
mEncoder.encode(out, mBuffer, 0, mBufferPos);
|
encoder.encode(out, buffer);
|
||||||
|
|
||||||
// Reset 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
|
// that the encoder can't buffer. In that case, the encoder should probably
|
||||||
// tell the EncoderStream how large buffer it prefers...
|
// tell the EncoderStream how large buffer it prefers...
|
||||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
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
|
// Buffer data
|
||||||
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength);
|
buffer.put(pBytes, pOffset, pLength);
|
||||||
mBufferPos += pLength;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Encode data already in the buffer
|
// Encode data already in the buffer
|
||||||
if (mBufferPos != 0) {
|
encodeBuffer();
|
||||||
encodeBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode rest without buffering
|
// 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 {
|
public void write(final int pByte) throws IOException {
|
||||||
if (mBufferPos >= mBuffer.length - 1) {
|
if (!buffer.hasRemaining()) {
|
||||||
encodeBuffer(); // Resets mBufferPos to 0
|
encodeBuffer(); // Resets bufferPos to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
mBuffer[mBufferPos++] = (byte) pByte;
|
buffer.put((byte) pByte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+30
-29
@@ -28,9 +28,10 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.EOFException;
|
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
|
* 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 {
|
public final class PackBits16Decoder implements Decoder {
|
||||||
// TODO: Refactor this into an option for the PackBitsDecoder?
|
// TODO: Refactor this into an option for the PackBitsDecoder?
|
||||||
private final boolean mDisableNoop;
|
private final boolean disableNoop;
|
||||||
|
|
||||||
private int mLeftOfRun;
|
private int leftOfRun;
|
||||||
private boolean mSplitRun;
|
private boolean splitRun;
|
||||||
private boolean mEOF;
|
private boolean reachedEOF;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code PackBitsDecoder}.
|
* 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
|
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||||
*/
|
*/
|
||||||
public PackBits16Decoder(final boolean pDisableNoop) {
|
public PackBits16Decoder(final boolean pDisableNoop) {
|
||||||
mDisableNoop = pDisableNoop;
|
disableNoop = pDisableNoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes bytes from the given input stream, to the given buffer.
|
* Decodes bytes from the given input stream, to the given buffer.
|
||||||
*
|
*
|
||||||
* @param pStream the stream to decode from
|
* @param stream the stream to decode from
|
||||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||||
* bytes long
|
* bytes long
|
||||||
* @return The number of bytes decoded
|
* @return The number of bytes decoded
|
||||||
*
|
*
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
*/
|
*/
|
||||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||||
if (mEOF) {
|
if (reachedEOF) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = 0;
|
int read = 0;
|
||||||
final int max = pBuffer.length;
|
final int max = buffer.capacity();
|
||||||
|
|
||||||
while (read < max) {
|
while (read < max) {
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if (mSplitRun) {
|
if (splitRun) {
|
||||||
// Continue run
|
// Continue run
|
||||||
n = mLeftOfRun;
|
n = leftOfRun;
|
||||||
mSplitRun = false;
|
splitRun = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Start new run
|
// Start new run
|
||||||
int b = pStream.read();
|
int b = stream.read();
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
mEOF = true;
|
reachedEOF = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
n = (byte) b;
|
n = (byte) b;
|
||||||
@@ -112,13 +113,13 @@ public final class PackBits16Decoder implements Decoder {
|
|||||||
|
|
||||||
// Split run at or before max
|
// Split run at or before max
|
||||||
if (n >= 0 && 2 * (n + 1) + read > max) {
|
if (n >= 0 && 2 * (n + 1) + read > max) {
|
||||||
mLeftOfRun = n;
|
leftOfRun = n;
|
||||||
mSplitRun = true;
|
splitRun = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (n < 0 && 2 * (-n + 1) + read > max) {
|
else if (n < 0 && 2 * (-n + 1) + read > max) {
|
||||||
mLeftOfRun = n;
|
leftOfRun = n;
|
||||||
mSplitRun = true;
|
splitRun = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,18 +127,18 @@ public final class PackBits16Decoder implements Decoder {
|
|||||||
if (n >= 0) {
|
if (n >= 0) {
|
||||||
// Copy next n + 1 shorts literally
|
// Copy next n + 1 shorts literally
|
||||||
int len = 2 * (n + 1);
|
int len = 2 * (n + 1);
|
||||||
readFully(pStream, pBuffer, read, len);
|
readFully(stream, buffer, len);
|
||||||
read += len;
|
read += len;
|
||||||
}
|
}
|
||||||
// Allow -128 for compatibility, see above
|
// Allow -128 for compatibility, see above
|
||||||
else if (mDisableNoop || n != -128) {
|
else if (disableNoop || n != -128) {
|
||||||
// Replicate the next short -n + 1 times
|
// Replicate the next short -n + 1 times
|
||||||
byte value1 = readByte(pStream);
|
byte value1 = readByte(stream);
|
||||||
byte value2 = readByte(pStream);
|
byte value2 = readByte(stream);
|
||||||
|
|
||||||
for (int i = -n + 1; i > 0; i--) {
|
for (int i = -n + 1; i > 0; i--) {
|
||||||
pBuffer[read++] = value1;
|
buffer.put(value1);
|
||||||
pBuffer[read++] = value2;
|
buffer.put(value2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else NOOP (-128)
|
// else NOOP (-128)
|
||||||
@@ -160,7 +161,7 @@ public final class PackBits16Decoder implements Decoder {
|
|||||||
return (byte) read;
|
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) {
|
if (pLength < 0) {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
}
|
}
|
||||||
@@ -168,7 +169,7 @@ public final class PackBits16Decoder implements Decoder {
|
|||||||
int read = 0;
|
int read = 0;
|
||||||
|
|
||||||
while (read < pLength) {
|
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) {
|
if (count < 0) {
|
||||||
throw new EOFException("Unexpected end of PackBits stream");
|
throw new EOFException("Unexpected end of PackBits stream");
|
||||||
+44
-46
@@ -28,9 +28,10 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.io.enc;
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.EOFException;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decoder implementation for Apple PackBits run-length encoding.
|
* 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 $
|
* @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 {
|
public final class PackBitsDecoder implements Decoder {
|
||||||
private final boolean mDisableNoop;
|
// TODO: Look at ICNSImageReader#unpackbits... What is this weirdness?
|
||||||
|
|
||||||
private int mLeftOfRun;
|
private final boolean disableNoop;
|
||||||
private boolean mSplitRun;
|
|
||||||
private boolean mEOF;
|
private int leftOfRun;
|
||||||
|
private boolean splitRun;
|
||||||
|
private boolean reachedEOF;
|
||||||
|
|
||||||
/** Creates a {@code PackBitsDecoder}. */
|
/** Creates a {@code PackBitsDecoder}. */
|
||||||
public PackBitsDecoder() {
|
public PackBitsDecoder() {
|
||||||
@@ -75,82 +78,75 @@ public final class PackBitsDecoder implements Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code PackBitsDecoder}.
|
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
|
||||||
* <p/>
|
* <p/>
|
||||||
* As some implementations of PackBits-like encoders treat {@code -128} as length of
|
* 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
|
* a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility.
|
||||||
* for compatibility.
|
* Should be used with caution, even though, most known encoders never write no-ops in the compressed streams.
|
||||||
* 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
|
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||||
*/
|
*/
|
||||||
public PackBitsDecoder(final boolean pDisableNoop) {
|
public PackBitsDecoder(final boolean pDisableNoop) {
|
||||||
mDisableNoop = pDisableNoop;
|
disableNoop = pDisableNoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes bytes from the given input stream, to the given buffer.
|
* Decodes bytes from the given input stream, to the given buffer.
|
||||||
*
|
*
|
||||||
* @param pStream the stream to decode from
|
* @param stream the stream to decode from
|
||||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long
|
||||||
* bytes long
|
|
||||||
* @return The number of bytes decoded
|
* @return The number of bytes decoded
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws java.io.IOException
|
||||||
*/
|
*/
|
||||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||||
if (mEOF) {
|
if (reachedEOF) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = 0;
|
// TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream...
|
||||||
final int max = pBuffer.length;
|
while (buffer.hasRemaining()) {
|
||||||
|
|
||||||
while (read < max) {
|
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
if (mSplitRun) {
|
if (splitRun) {
|
||||||
// Continue run
|
// Continue run
|
||||||
n = mLeftOfRun;
|
n = leftOfRun;
|
||||||
mSplitRun = false;
|
splitRun = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Start new run
|
// Start new run
|
||||||
int b = pStream.read();
|
int b = stream.read();
|
||||||
if (b < 0) {
|
if (b < 0) {
|
||||||
mEOF = true;
|
reachedEOF = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
n = (byte) b;
|
n = (byte) b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split run at or before max
|
// Split run at or before max
|
||||||
if (n >= 0 && n + 1 + read > max) {
|
if (n >= 0 && n + 1 > buffer.remaining()) {
|
||||||
mLeftOfRun = n;
|
leftOfRun = n;
|
||||||
mSplitRun = true;
|
splitRun = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (n < 0 && -n + 1 + read > max) {
|
else if (n < 0 && -n + 1 > buffer.remaining()) {
|
||||||
mLeftOfRun = n;
|
leftOfRun = n;
|
||||||
mSplitRun = true;
|
splitRun = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (n >= 0) {
|
if (n >= 0) {
|
||||||
// Copy next n + 1 bytes literally
|
// Copy next n + 1 bytes literally
|
||||||
readFully(pStream, pBuffer, read, n + 1);
|
readFully(stream, buffer, n + 1);
|
||||||
|
|
||||||
read += n + 1;
|
|
||||||
}
|
}
|
||||||
// Allow -128 for compatibility, see above
|
// Allow -128 for compatibility, see above
|
||||||
else if (mDisableNoop || n != -128) {
|
else if (disableNoop || n != -128) {
|
||||||
// Replicate the next byte -n + 1 times
|
// Replicate the next byte -n + 1 times
|
||||||
byte value = readByte(pStream);
|
byte value = readByte(stream);
|
||||||
|
|
||||||
for (int i = -n + 1; i > 0; i--) {
|
for (int i = -n + 1; i > 0; i--) {
|
||||||
pBuffer[read++] = value;
|
buffer.put(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// else NOOP (-128)
|
// else NOOP (-128)
|
||||||
@@ -160,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();
|
int read = pStream.read();
|
||||||
|
|
||||||
if (read < 0) {
|
if (read < 0) {
|
||||||
@@ -173,21 +169,23 @@ public final class PackBitsDecoder implements Decoder {
|
|||||||
return (byte) read;
|
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) {
|
if (pLength < 0) {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = 0;
|
int total = 0;
|
||||||
|
|
||||||
while (read < pLength) {
|
while (total < pLength) {
|
||||||
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
|
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total);
|
||||||
|
|
||||||
if (count < 0) {
|
if (count < 0) {
|
||||||
throw new EOFException("Unexpected end of PackBits stream");
|
throw new EOFException("Unexpected end of PackBits stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
read += count;
|
total += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pBuffer.position(pBuffer.position() + total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+12
-6
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
|
|||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encoder implementation for Apple PackBits run-length encoding.
|
* Encoder implementation for Apple PackBits run-length encoding.
|
||||||
@@ -63,7 +64,7 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public final class PackBitsEncoder implements Encoder {
|
public final class PackBitsEncoder implements Encoder {
|
||||||
|
|
||||||
final private byte[] mBuffer = new byte[128];
|
final private byte[] buffer = new byte[128];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code PackBitsEncoder}.
|
* Creates a {@code PackBitsEncoder}.
|
||||||
@@ -71,7 +72,12 @@ public final class PackBitsEncoder implements Encoder {
|
|||||||
public PackBitsEncoder() {
|
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
|
// NOTE: It's best to encode a 2 byte repeat
|
||||||
// run as a replicate run except when preceded and followed by a
|
// 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
|
// 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
|
// Compressed run
|
||||||
int run = 1;
|
int run = 1;
|
||||||
byte replicate = pBuffer[offset];
|
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++;
|
offset++;
|
||||||
run++;
|
run++;
|
||||||
}
|
}
|
||||||
@@ -101,17 +107,17 @@ public final class PackBitsEncoder implements Encoder {
|
|||||||
run = 0;
|
run = 0;
|
||||||
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|
||||||
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
|
|| (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 last byte, include it in literal run, if space
|
||||||
if (offset == max && run > 0 && run < 128) {
|
if (offset == max && run > 0 && run < 128) {
|
||||||
mBuffer[run++] = pBuffer[offset++];
|
buffer[run++] = pBuffer[offset++];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (run > 0) {
|
if (run > 0) {
|
||||||
pStream.write(run - 1);
|
pStream.write(run - 1);
|
||||||
pStream.write(mBuffer, 0, run);
|
pStream.write(buffer, 0, run);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If last byte, and not space, start new literal run
|
// If last byte, and not space, start new literal run
|
||||||
+20
-20
@@ -32,7 +32,7 @@ import java.io.InputStream;
|
|||||||
import java.io.IOException;
|
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/>
|
* <p/>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @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
|
// TODO: Move to other package or make public
|
||||||
final class RLE4Decoder extends AbstractRLEDecoder {
|
final class RLE4Decoder extends AbstractRLEDecoder {
|
||||||
|
|
||||||
public RLE4Decoder(int pWidth, int pHeight) {
|
public RLE4Decoder(final int pWidth, final int pHeight) {
|
||||||
super((pWidth + 1) / 2, pHeight);
|
super((pWidth + 1) / 2, pHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
int deltaX = 0;
|
int deltaX = 0;
|
||||||
int deltaY = 0;
|
int deltaY = 0;
|
||||||
|
|
||||||
while (mSrcY >= 0) {
|
while (srcY >= 0) {
|
||||||
int byte1 = pInput.read();
|
int byte1 = pInput.read();
|
||||||
int byte2 = checkEOF(pInput.read());
|
int byte2 = checkEOF(pInput.read());
|
||||||
|
|
||||||
@@ -58,20 +58,20 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
case 0x00:
|
case 0x00:
|
||||||
// End of line
|
// End of line
|
||||||
// NOTE: Some BMPs have double EOLs..
|
// NOTE: Some BMPs have double EOLs..
|
||||||
if (mSrcX != 0) {
|
if (srcX != 0) {
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x01:
|
case 0x01:
|
||||||
// End of bitmap
|
// End of bitmap
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
mSrcY = 0;
|
srcY = 0;
|
||||||
break;
|
break;
|
||||||
case 0x02:
|
case 0x02:
|
||||||
// Delta
|
// Delta
|
||||||
deltaX = mSrcX + pInput.read();
|
deltaX = srcX + pInput.read();
|
||||||
deltaY = mSrcY - checkEOF(pInput.read());
|
deltaY = srcY - checkEOF(pInput.read());
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Absolute mode
|
// Absolute mode
|
||||||
@@ -82,13 +82,13 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
||||||
while (byte2 > 1) {
|
while (byte2 > 1) {
|
||||||
int packed = checkEOF(pInput.read());
|
int packed = checkEOF(pInput.read());
|
||||||
mRow[mSrcX++] = (byte) packed;
|
row[srcX++] = (byte) packed;
|
||||||
byte2 -= 2;
|
byte2 -= 2;
|
||||||
}
|
}
|
||||||
if (byte2 == 1) {
|
if (byte2 == 1) {
|
||||||
// TODO: Half byte alignment? Seems to be ok...
|
// TODO: Half byte alignment? Seems to be ok...
|
||||||
int packed = checkEOF(pInput.read());
|
int packed = checkEOF(pInput.read());
|
||||||
mRow[mSrcX++] = (byte) (packed & 0xf0);
|
row[srcX++] = (byte) (packed & 0xf0);
|
||||||
}
|
}
|
||||||
if (paddingByte) {
|
if (paddingByte) {
|
||||||
checkEOF(pInput.read());
|
checkEOF(pInput.read());
|
||||||
@@ -100,24 +100,24 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
// Encoded mode
|
// Encoded mode
|
||||||
// Replicate the two samples in byte2 as many times as byte1 says
|
// Replicate the two samples in byte2 as many times as byte1 says
|
||||||
while (byte1 > 1) {
|
while (byte1 > 1) {
|
||||||
mRow[mSrcX++] = (byte) byte2;
|
row[srcX++] = (byte) byte2;
|
||||||
byte1 -= 2;
|
byte1 -= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (byte1 == 1) {
|
if (byte1 == 1) {
|
||||||
// TODO: Half byte alignment? Seems to be ok...
|
// 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 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
|
// Move to new position, either absolute (delta) or next line
|
||||||
if (deltaX != 0 || deltaY != 0) {
|
if (deltaX != 0 || deltaY != 0) {
|
||||||
mSrcX = (deltaX + 1) / 2;
|
srcX = (deltaX + 1) / 2;
|
||||||
|
|
||||||
if (deltaY > mSrcY) {
|
if (deltaY > srcY) {
|
||||||
mSrcY = deltaY;
|
srcY = deltaY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +125,8 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
deltaY = 0;
|
deltaY = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mSrcX = 0;
|
srcX = 0;
|
||||||
mSrcY--;
|
srcY--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-18
@@ -32,7 +32,7 @@ import java.io.InputStream;
|
|||||||
import java.io.IOException;
|
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/>
|
* <p/>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @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
|
// TODO: Move to other package or make public
|
||||||
final class RLE8Decoder extends AbstractRLEDecoder {
|
final class RLE8Decoder extends AbstractRLEDecoder {
|
||||||
|
|
||||||
public RLE8Decoder(int pWidth, int pHeight) {
|
public RLE8Decoder(final int pWidth, final int pHeight) {
|
||||||
super(pWidth, pHeight);
|
super(pWidth, pHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
|||||||
int deltaX = 0;
|
int deltaX = 0;
|
||||||
int deltaY = 0;
|
int deltaY = 0;
|
||||||
|
|
||||||
while (mSrcY >= 0) {
|
while (srcY >= 0) {
|
||||||
int byte1 = pInput.read();
|
int byte1 = pInput.read();
|
||||||
int byte2 = checkEOF(pInput.read());
|
int byte2 = checkEOF(pInput.read());
|
||||||
|
|
||||||
@@ -58,27 +58,27 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
|||||||
case 0x00:
|
case 0x00:
|
||||||
// End of line
|
// End of line
|
||||||
// NOTE: Some BMPs have double EOLs..
|
// NOTE: Some BMPs have double EOLs..
|
||||||
if (mSrcX != 0) {
|
if (srcX != 0) {
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x01:
|
case 0x01:
|
||||||
// End of bitmap
|
// End of bitmap
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
mSrcY = 0;
|
srcY = 0;
|
||||||
break;
|
break;
|
||||||
case 0x02:
|
case 0x02:
|
||||||
// Delta
|
// Delta
|
||||||
deltaX = mSrcX + pInput.read();
|
deltaX = srcX + pInput.read();
|
||||||
deltaY = mSrcY - checkEOF(pInput.read());
|
deltaY = srcY - checkEOF(pInput.read());
|
||||||
mSrcX = mRow.length;
|
srcX = row.length;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Absolute mode
|
// Absolute mode
|
||||||
// Copy the next byte2 (3..255) bytes from file to output
|
// Copy the next byte2 (3..255) bytes from file to output
|
||||||
boolean paddingByte = (byte2 % 2) != 0;
|
boolean paddingByte = (byte2 % 2) != 0;
|
||||||
while (byte2-- > 0) {
|
while (byte2-- > 0) {
|
||||||
mRow[mSrcX++] = (byte) checkEOF(pInput.read());
|
row[srcX++] = (byte) checkEOF(pInput.read());
|
||||||
}
|
}
|
||||||
if (paddingByte) {
|
if (paddingByte) {
|
||||||
checkEOF(pInput.read());
|
checkEOF(pInput.read());
|
||||||
@@ -90,26 +90,26 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
|||||||
// Replicate byte2 as many times as byte1 says
|
// Replicate byte2 as many times as byte1 says
|
||||||
byte value = (byte) byte2;
|
byte value = (byte) byte2;
|
||||||
while (byte1-- > 0) {
|
while (byte1-- > 0) {
|
||||||
mRow[mSrcX++] = value;
|
row[srcX++] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're done with a complete row, copy the data
|
// 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
|
// Move to new position, either absolute (delta) or next line
|
||||||
if (deltaX != 0 || deltaY != 0) {
|
if (deltaX != 0 || deltaY != 0) {
|
||||||
mSrcX = deltaX;
|
srcX = deltaX;
|
||||||
if (deltaY != mSrcY) {
|
if (deltaY != srcY) {
|
||||||
mSrcY = deltaY;
|
srcY = deltaY;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
deltaX = 0;
|
deltaX = 0;
|
||||||
deltaY = 0;
|
deltaY = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mSrcX = 0;
|
srcX = 0;
|
||||||
mSrcY--;
|
srcY--;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+196
-193
@@ -54,41 +54,44 @@ public final class CompoundDocument {
|
|||||||
// TODO: Write support...
|
// TODO: Write support...
|
||||||
// TODO: Properties: http://support.microsoft.com/kb/186898
|
// 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) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
|
||||||
(byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
|
(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 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 */
|
/** The epoch offset of CompoundDocument time stamps */
|
||||||
public final static long EPOCH_OFFSET = -11644477200000L;
|
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}.
|
* 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
|
* @throws IOException if an I/O exception occurs while reading the header
|
||||||
*/
|
*/
|
||||||
public CompoundDocument(final File pFile) throws IOException {
|
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?!
|
// 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
|
// 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
|
// For testing only, consider exposing later
|
||||||
CompoundDocument(final SeekableInputStream pInput) throws IOException {
|
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?!
|
// 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
|
// 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
|
* @throws IOException if an I/O exception occurs while reading the header
|
||||||
*/
|
*/
|
||||||
public CompoundDocument(final ImageInputStream pInput) throws IOException {
|
public CompoundDocument(final ImageInputStream pInput) throws IOException {
|
||||||
mInput = pInput;
|
input = pInput;
|
||||||
|
|
||||||
// TODO: Might be better to read header on first read operation?!
|
// 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
|
// 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 {
|
private void readHeader() throws IOException {
|
||||||
if (mMasterSAT != null) {
|
if (masterSAT != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canRead(mInput, false)) {
|
if (!canRead(input, false)) {
|
||||||
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
|
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
|
||||||
}
|
}
|
||||||
|
|
||||||
// UID (seems to be all 0s)
|
// 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();
|
// int version =
|
||||||
//System.out.println("version: " + version);
|
input.readUnsignedShort();
|
||||||
/*int revision = */mInput.readUnsignedShort();
|
// System.out.println("version: " + version);
|
||||||
//System.out.println("revision: " + revision);
|
// int revision =
|
||||||
|
input.readUnsignedShort();
|
||||||
|
// System.out.println("revision: " + revision);
|
||||||
|
|
||||||
int byteOrder = mInput.readUnsignedShort();
|
int byteOrder = input.readUnsignedShort();
|
||||||
if (byteOrder != 0xfffe) {
|
// System.out.printf("byteOrder: 0x%04x\n", byteOrder);
|
||||||
// Reversed, as I'm allready reading little-endian
|
if (byteOrder == 0xffff) {
|
||||||
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
|
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();
|
sectorSize = 1 << input.readUnsignedShort();
|
||||||
//System.out.println("sectorSize: " + mSectorSize + " bytes");
|
// System.out.println("sectorSize: " + sectorSize + " bytes");
|
||||||
mShortSectorSize = 1 << mInput.readUnsignedShort();
|
shortSectorSize = 1 << input.readUnsignedShort();
|
||||||
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes");
|
// System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
|
||||||
|
|
||||||
// Reserved
|
// Reserved
|
||||||
if (mInput.skipBytes(10) != 10) {
|
if (skipBytesFully(10) != 10) {
|
||||||
throw new CorruptDocumentException();
|
throw new CorruptDocumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SATSize = mInput.readInt();
|
int SATSize = input.readInt();
|
||||||
//System.out.println("normalSATSize: " + mSATSize);
|
// System.out.println("normalSATSize: " + SATSize);
|
||||||
|
|
||||||
mDirectorySId = mInput.readInt();
|
directorySId = input.readInt();
|
||||||
//System.out.println("directorySId: " + mDirectorySId);
|
// System.out.println("directorySId: " + directorySId);
|
||||||
|
|
||||||
// Reserved
|
// Reserved
|
||||||
if (mInput.skipBytes(4) != 4) {
|
if (skipBytesFully(4) != 4) {
|
||||||
throw new CorruptDocumentException();
|
throw new CorruptDocumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
mMinStreamSize = mInput.readInt();
|
minStreamSize = input.readInt();
|
||||||
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes");
|
// System.out.println("minStreamSize: " + minStreamSize + " bytes");
|
||||||
|
|
||||||
mShortSATSID = mInput.readInt();
|
shortSATSId = input.readInt();
|
||||||
//System.out.println("shortSATSID: " + mShortSATSID);
|
// System.out.println("shortSATSId: " + shortSATSId);
|
||||||
mShortSATSize = mInput.readInt();
|
shortSATSize = input.readInt();
|
||||||
//System.out.println("shortSATSize: " + mShortSATSize);
|
// System.out.println("shortSATSize: " + shortSATSize);
|
||||||
int masterSATSId = mInput.readInt();
|
int masterSATSId = input.readInt();
|
||||||
//System.out.println("masterSATSId: " + mMasterSATSID);
|
// System.out.println("masterSATSId: " + masterSATSId);
|
||||||
int masterSATSize = mInput.readInt();
|
int masterSATSize = input.readInt();
|
||||||
//System.out.println("masterSATSize: " + mMasterSATSize);
|
// System.out.println("masterSATSize: " + masterSATSize);
|
||||||
|
|
||||||
// Read masterSAT: 436 bytes, containing up to 109 SIDs
|
// Read masterSAT: 436 bytes, containing up to 109 SIDs
|
||||||
//System.out.println("MSAT:");
|
//System.out.println("MSAT:");
|
||||||
mMasterSAT = new int[SATSize];
|
masterSAT = new int[SATSize];
|
||||||
final int headerSIds = Math.min(SATSize, 109);
|
final int headerSIds = Math.min(SATSize, 109);
|
||||||
for (int i = 0; i < headerSIds; i++) {
|
for (int i = 0; i < headerSIds; i++) {
|
||||||
mMasterSAT[i] = mInput.readInt();
|
masterSAT[i] = input.readInt();
|
||||||
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]);
|
//System.out.println("\tSID(" + i + "): " + masterSAT[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (masterSATSId == END_OF_CHAIN_SID) {
|
if (masterSATSId == END_OF_CHAIN_SID) {
|
||||||
// End of chain
|
// End of chain
|
||||||
int freeSIdLength = 436 - (SATSize * 4);
|
int freeSIdLength = 436 - (SATSize * 4);
|
||||||
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) {
|
if (skipBytesFully(freeSIdLength) != freeSIdLength) {
|
||||||
throw new CorruptDocumentException();
|
throw new CorruptDocumentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,17 +298,17 @@ public final class CompoundDocument {
|
|||||||
int index = headerSIds;
|
int index = headerSIds;
|
||||||
for (int i = 0; i < masterSATSize; i++) {
|
for (int i = 0; i < masterSATSize; i++) {
|
||||||
for (int j = 0; j < 127; j++) {
|
for (int j = 0; j < 127; j++) {
|
||||||
int sid = mInput.readInt();
|
int sid = input.readInt();
|
||||||
switch (sid) {
|
switch (sid) {
|
||||||
case FREE_SID:// Free
|
case FREE_SID:// Free
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
mMasterSAT[index++] = sid;
|
masterSAT[index++] = sid;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int next = mInput.readInt();
|
int next = input.readInt();
|
||||||
if (next == END_OF_CHAIN_SID) {// End of chain
|
if (next == END_OF_CHAIN_SID) {// End of chain
|
||||||
break;
|
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 {
|
private void readSAT() throws IOException {
|
||||||
if (mSAT != null) {
|
if (SAT != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int intsPerSector = mSectorSize / 4;
|
final int intsPerSector = sectorSize / 4;
|
||||||
|
|
||||||
// Read the Sector Allocation Table
|
// 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++) {
|
for (int i = 0; i < masterSAT.length; i++) {
|
||||||
seekToSId(mMasterSAT[i], FREE_SID);
|
seekToSId(masterSAT[i], FREE_SID);
|
||||||
|
|
||||||
for (int j = 0; j < intsPerSector; j++) {
|
for (int j = 0; j < intsPerSector; j++) {
|
||||||
int nextSID = mInput.readInt();
|
int nextSID = input.readInt();
|
||||||
int index = (j + (i * intsPerSector));
|
int index = (j + (i * intsPerSector));
|
||||||
|
|
||||||
mSAT[index] = nextSID;
|
SAT[index] = nextSID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the short-stream Sector Allocation Table
|
// Read the short-stream Sector Allocation Table
|
||||||
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID);
|
SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
|
||||||
mShortSAT = new int[mShortSATSize * intsPerSector];
|
shortSAT = new int[shortSATSize * intsPerSector];
|
||||||
for (int i = 0; i < mShortSATSize; i++) {
|
for (int i = 0; i < shortSATSize; i++) {
|
||||||
seekToSId(chain.get(i), FREE_SID);
|
seekToSId(chain.get(i), FREE_SID);
|
||||||
|
|
||||||
for (int j = 0; j < intsPerSector; j++) {
|
for (int j = 0; j < intsPerSector; j++) {
|
||||||
int nextSID = mInput.readInt();
|
int nextSID = input.readInt();
|
||||||
int index = (j + (i * intsPerSector));
|
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 {
|
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
|
||||||
SIdChain chain = new SIdChain();
|
SIdChain chain = new SIdChain();
|
||||||
|
|
||||||
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT;
|
int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT;
|
||||||
|
|
||||||
int sid = pSId;
|
int sid = pSId;
|
||||||
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
|
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
|
||||||
@@ -367,7 +392,7 @@ public final class CompoundDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isShortStream(final long pStreamSize) {
|
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;
|
long pos;
|
||||||
|
|
||||||
if (isShortStream(pStreamSize)) {
|
if (isShortStream(pStreamSize)) {
|
||||||
// The short-stream is not continouos...
|
// The short stream is not continuous...
|
||||||
Entry root = getRootEntry();
|
Entry root = getRootEntry();
|
||||||
if (mShortStreamSIdChain == null) {
|
if (shortStreamSIdChain == null) {
|
||||||
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
|
shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
int shortPerStd = mSectorSize / mShortSectorSize;
|
// System.err.println("pSId: " + pSId);
|
||||||
int offset = pSId / shortPerStd;
|
int shortPerSId = sectorSize / shortSectorSize;
|
||||||
int shortOffset = pSId - (offset * shortPerStd);
|
// 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
|
pos = HEADER_SIZE
|
||||||
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize)
|
+ (shortStreamSIdChain.get(offset) * (long) sectorSize)
|
||||||
+ (shortOffset * (long) mShortSectorSize);
|
+ (shortOffset * (long) shortSectorSize);
|
||||||
|
// System.err.println("pos: " + pos);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pos = HEADER_SIZE + pSId * (long) mSectorSize;
|
pos = HEADER_SIZE + pSId * (long) sectorSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
if (input instanceof LittleEndianRandomAccessFile) {
|
||||||
((LittleEndianRandomAccessFile) mInput).seek(pos);
|
((LittleEndianRandomAccessFile) input).seek(pos);
|
||||||
}
|
}
|
||||||
else if (mInput instanceof ImageInputStream) {
|
else if (input instanceof ImageInputStream) {
|
||||||
((ImageInputStream) mInput).seek(pos);
|
((ImageInputStream) input).seek(pos);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
((SeekableLittleEndianDataInputStream) mInput).seek(pos);
|
((SeekableLittleEndianDataInputStream) input).seek(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToDId(final int pDId) throws IOException {
|
private void seekToDId(final int pDId) throws IOException {
|
||||||
if (mDirectorySIdChain == null) {
|
if (directorySIdChain == null) {
|
||||||
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID);
|
directorySIdChain = getSIdChain(directorySId, FREE_SID);
|
||||||
}
|
}
|
||||||
|
|
||||||
int dIdsPerSId = mSectorSize / Entry.LENGTH;
|
int dIdsPerSId = sectorSize / Entry.LENGTH;
|
||||||
|
|
||||||
int sIdOffset = pDId / dIdsPerSId;
|
int sIdOffset = pDId / dIdsPerSId;
|
||||||
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
|
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
|
||||||
|
|
||||||
int sId = mDirectorySIdChain.get(sIdOffset);
|
int sId = directorySIdChain.get(sIdOffset);
|
||||||
|
|
||||||
seekToSId(sId, FREE_SID);
|
seekToSId(sId, FREE_SID);
|
||||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
if (input instanceof LittleEndianRandomAccessFile) {
|
||||||
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput;
|
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
|
||||||
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
|
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
|
||||||
}
|
}
|
||||||
else if (mInput instanceof ImageInputStream) {
|
else if (input instanceof ImageInputStream) {
|
||||||
ImageInputStream input = (ImageInputStream) mInput;
|
ImageInputStream input = (ImageInputStream) this.input;
|
||||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput;
|
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
|
||||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
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);
|
SIdChain chain = getSIdChain(pStreamId, pStreamSize);
|
||||||
|
|
||||||
// TODO: Detach? Means, we have to copy to a byte buffer, or keep track of
|
// 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)..
|
// 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 {
|
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
|
||||||
@@ -453,7 +484,7 @@ public final class CompoundDocument {
|
|||||||
byte[] bytes = new byte[Entry.LENGTH];
|
byte[] bytes = new byte[Entry.LENGTH];
|
||||||
|
|
||||||
seekToDId(pDirectoryId);
|
seekToDId(pDirectoryId);
|
||||||
mInput.readFully(bytes);
|
input.readFully(bytes);
|
||||||
|
|
||||||
return new ByteArrayInputStream(bytes);
|
return new ByteArrayInputStream(bytes);
|
||||||
}
|
}
|
||||||
@@ -462,8 +493,8 @@ public final class CompoundDocument {
|
|||||||
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
|
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
|
||||||
getDirectoryStreamForDId(pDirectoryId)
|
getDirectoryStreamForDId(pDirectoryId)
|
||||||
));
|
));
|
||||||
entry.mParent = pParent;
|
entry.parent = pParent;
|
||||||
entry.mDocument = this;
|
entry.document = this;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,21 +558,23 @@ public final class CompoundDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Entry getRootEntry() throws IOException {
|
public Entry getRootEntry() throws IOException {
|
||||||
if (mRootEntry == null) {
|
if (rootEntry == null) {
|
||||||
readSAT();
|
readSAT();
|
||||||
|
|
||||||
mRootEntry = getEntry(0, null);
|
rootEntry = getEntry(0, null);
|
||||||
|
|
||||||
if (mRootEntry.type != Entry.ROOT_STORAGE) {
|
if (rootEntry.type != Entry.ROOT_STORAGE) {
|
||||||
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type);
|
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
|
// @Override
|
||||||
// public int hashCode() {
|
// public int hashCode() {
|
||||||
// return mUID.hashCode();
|
// return uUID.hashCode();
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// @Override
|
// @Override
|
||||||
@@ -555,7 +588,7 @@ public final class CompoundDocument {
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// if (pOther.getClass() == getClass()) {
|
// if (pOther.getClass() == getClass()) {
|
||||||
// return mUID.equals(((CompoundDocument) pOther).mUID);
|
// return uUID.equals(((CompoundDocument) pOther).uUID);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// return false;
|
// return false;
|
||||||
@@ -565,7 +598,7 @@ public final class CompoundDocument {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(
|
return String.format(
|
||||||
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
|
"%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;
|
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Enforce stream length!
|
static class Stream extends InputStream {
|
||||||
static class Stream extends SeekableInputStream {
|
private final SIdChain chain;
|
||||||
private SIdChain mChain;
|
private final CompoundDocument document;
|
||||||
int mNextSectorPos;
|
private final long length;
|
||||||
byte[] mBuffer;
|
|
||||||
int mBufferPos;
|
|
||||||
|
|
||||||
private final CompoundDocument mDocument;
|
private long streamPos;
|
||||||
private final long mLength;
|
private int nextSectorPos;
|
||||||
|
private byte[] buffer;
|
||||||
|
private int bufferPos;
|
||||||
|
|
||||||
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) {
|
public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
|
||||||
mChain = pChain;
|
this.chain = chain;
|
||||||
mLength = pLength;
|
this.length = streamSize;
|
||||||
|
|
||||||
mBuffer = new byte[pSectorSize];
|
this.buffer = new byte[sectorSize];
|
||||||
mBufferPos = mBuffer.length;
|
this.bufferPos = buffer.length;
|
||||||
|
|
||||||
mDocument = pDocument;
|
this.document = document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
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 {
|
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 {
|
private boolean fillBuffer() throws IOException {
|
||||||
if (mNextSectorPos < mChain.length()) {
|
if (streamPos < length && nextSectorPos < chain.length()) {
|
||||||
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-)
|
// TODO: Sync on document.input here, and we are completely detached... :-)
|
||||||
// TODO: We also need to sync other places...
|
// TODO: Update: We also need to sync other places... :-P
|
||||||
synchronized (mDocument) {
|
synchronized (document) {
|
||||||
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength);
|
document.seekToSId(chain.get(nextSectorPos), length);
|
||||||
mDocument.mInput.readFully(mBuffer);
|
document.input.readFully(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
mNextSectorPos++;
|
nextSectorPos++;
|
||||||
mBufferPos = 0;
|
bufferPos = 0;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,99 +699,66 @@ public final class CompoundDocument {
|
|||||||
|
|
||||||
int toRead = Math.min(len, available());
|
int toRead = Math.min(len, available());
|
||||||
|
|
||||||
System.arraycopy(mBuffer, mBufferPos, b, off, toRead);
|
System.arraycopy(buffer, bufferPos, b, off, toRead);
|
||||||
mBufferPos += toRead;
|
bufferPos += toRead;
|
||||||
|
streamPos += toRead;
|
||||||
|
|
||||||
return toRead;
|
return toRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCached() {
|
@Override
|
||||||
return true;
|
public void close() throws IOException {
|
||||||
}
|
buffer = null;
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add test case for this class!!!
|
|
||||||
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
|
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
|
||||||
private final SeekableInputStream mSeekable;
|
private final SeekableInputStream seekable;
|
||||||
|
|
||||||
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
|
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
|
||||||
super(pInput);
|
super(pInput);
|
||||||
mSeekable = pInput;
|
seekable = pInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void seek(final long pPosition) throws IOException {
|
public void seek(final long pPosition) throws IOException {
|
||||||
mSeekable.seek(pPosition);
|
seekable.seek(pPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCachedFile() {
|
public boolean isCachedFile() {
|
||||||
return mSeekable.isCachedFile();
|
return seekable.isCachedFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCachedMemory() {
|
public boolean isCachedMemory() {
|
||||||
return mSeekable.isCachedMemory();
|
return seekable.isCachedMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCached() {
|
public boolean isCached() {
|
||||||
return mSeekable.isCached();
|
return seekable.isCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getStreamPosition() throws IOException {
|
public long getStreamPosition() throws IOException {
|
||||||
return mSeekable.getStreamPosition();
|
return seekable.getStreamPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getFlushedPosition() throws IOException {
|
public long getFlushedPosition() throws IOException {
|
||||||
return mSeekable.getFlushedPosition();
|
return seekable.getFlushedPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flushBefore(final long pPosition) throws IOException {
|
public void flushBefore(final long pPosition) throws IOException {
|
||||||
mSeekable.flushBefore(pPosition);
|
seekable.flushBefore(pPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
mSeekable.flush();
|
seekable.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() throws IOException {
|
public void reset() throws IOException {
|
||||||
mSeekable.reset();
|
seekable.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void mark() {
|
public void mark() {
|
||||||
mSeekable.mark();
|
seekable.mark();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+30
-30
@@ -32,6 +32,7 @@ import com.twelvemonkeys.io.SeekableInputStream;
|
|||||||
|
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@@ -61,9 +62,9 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
int startSId;
|
int startSId;
|
||||||
int streamSize;
|
int streamSize;
|
||||||
|
|
||||||
CompoundDocument mDocument;
|
CompoundDocument document;
|
||||||
Entry mParent;
|
Entry parent;
|
||||||
SortedSet<Entry> mChildren;
|
SortedSet<Entry> children;
|
||||||
|
|
||||||
public final static int LENGTH = 128;
|
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
|
* @throws IOException if an i/o exception occurs during reading
|
||||||
*/
|
*/
|
||||||
private void read(final DataInput pInput) throws IOException {
|
private void read(final DataInput pInput) throws IOException {
|
||||||
char[] chars = new char[32];
|
byte[] bytes = new byte[64];
|
||||||
for (int i = 0; i < chars.length; i++) {
|
pInput.readFully(bytes);
|
||||||
chars[i] = pInput.readChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Length is in bytes, including the null-terminator...
|
// NOTE: Length is in bytes, including the null-terminator...
|
||||||
int nameLength = pInput.readShort();
|
int nameLength = pInput.readShort();
|
||||||
name = new String(chars, 0, (nameLength - 1) / 2);
|
name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
|
||||||
//System.out.println("name: " + name);
|
// System.out.println("name: " + name);
|
||||||
|
|
||||||
type = pInput.readByte();
|
type = pInput.readByte();
|
||||||
//System.out.println("type: " + type);
|
// System.out.println("type: " + type);
|
||||||
|
|
||||||
nodeColor = pInput.readByte();
|
nodeColor = pInput.readByte();
|
||||||
//System.out.println("nodeColor: " + nodeColor);
|
// System.out.println("nodeColor: " + nodeColor);
|
||||||
|
|
||||||
prevDId = pInput.readInt();
|
prevDId = pInput.readInt();
|
||||||
//System.out.println("prevDID: " + prevDID);
|
// System.out.println("prevDId: " + prevDId);
|
||||||
nextDId = pInput.readInt();
|
nextDId = pInput.readInt();
|
||||||
//System.out.println("nextDID: " + nextDID);
|
// System.out.println("nextDId: " + nextDId);
|
||||||
rootNodeDId = pInput.readInt();
|
rootNodeDId = pInput.readInt();
|
||||||
//System.out.println("rootNodeDID: " + rootNodeDID);
|
// System.out.println("rootNodeDId: " + rootNodeDId);
|
||||||
|
|
||||||
// UID (16) + user flags (4), ignored
|
// UID (16) + user flags (4), ignored
|
||||||
if (pInput.skipBytes(20) != 20) {
|
if (pInput.skipBytes(20) != 20) {
|
||||||
@@ -131,9 +130,9 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
|
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
|
||||||
|
|
||||||
startSId = pInput.readInt();
|
startSId = pInput.readInt();
|
||||||
//System.out.println("startSID: " + startSID);
|
// System.out.println("startSId: " + startSId);
|
||||||
streamSize = pInput.readInt();
|
streamSize = pInput.readInt();
|
||||||
//System.out.println("streamSize: " + streamSize);
|
// System.out.println("streamSize: " + streamSize);
|
||||||
|
|
||||||
// Reserved
|
// Reserved
|
||||||
pInput.readInt();
|
pInput.readInt();
|
||||||
@@ -186,11 +185,11 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
* @see #length()
|
* @see #length()
|
||||||
*/
|
*/
|
||||||
public SeekableInputStream getInputStream() throws IOException {
|
public SeekableInputStream getInputStream() throws IOException {
|
||||||
if (isDirectory()) {
|
if (!isFile()) {
|
||||||
return null;
|
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()
|
* @see #getInputStream()
|
||||||
*/
|
*/
|
||||||
public long length() {
|
public long length() {
|
||||||
if (isDirectory()) {
|
if (!isFile()) {
|
||||||
return 0L;
|
return 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamSize;
|
return streamSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
* the root {@code Entry}
|
* the root {@code Entry}
|
||||||
*/
|
*/
|
||||||
public Entry getParentEntry() {
|
public Entry getParentEntry() {
|
||||||
return mParent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,7 +266,7 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
|
|
||||||
Entry dummy = new Entry();
|
Entry dummy = new Entry();
|
||||||
dummy.name = pName;
|
dummy.name = pName;
|
||||||
dummy.mParent = this;
|
dummy.parent = this;
|
||||||
|
|
||||||
SortedSet child = getChildEntries().tailSet(dummy);
|
SortedSet child = getChildEntries().tailSet(dummy);
|
||||||
return (Entry) child.first();
|
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
|
* @throws java.io.IOException if an I/O exception occurs
|
||||||
*/
|
*/
|
||||||
public SortedSet<Entry> getChildEntries() throws IOException {
|
public SortedSet<Entry> getChildEntries() throws IOException {
|
||||||
if (mChildren == null) {
|
if (children == null) {
|
||||||
if (isFile() || rootNodeDId == -1) {
|
if (isFile() || rootNodeDId == -1) {
|
||||||
mChildren = NO_CHILDREN;
|
children = NO_CHILDREN;
|
||||||
}
|
}
|
||||||
else {
|
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
|
// re-build tree, according to the docs
|
||||||
mChildren = mDocument.getEntries(rootNodeDId, this);
|
children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mChildren;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "\"" + name + "\""
|
return "\"" + name + "\""
|
||||||
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
|
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
|
||||||
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "")
|
+ (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
|
||||||
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)"))
|
+ (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)"))
|
||||||
+ ", SId=" + startSId + ", length=" + streamSize + ")";
|
+ ", SId=" + startSId + ", length=" + streamSize + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,8 +312,8 @@ public final class Entry implements Comparable<Entry> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entry other = (Entry) pOther;
|
Entry other = (Entry) pOther;
|
||||||
return name.equals(other.name) && (mParent == other.mParent
|
return name.equals(other.name) && (parent == other.parent
|
||||||
|| (mParent != null && mParent.equals(other.mParent)));
|
|| (parent != null && parent.equals(other.parent)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
+1
-1
@@ -37,7 +37,7 @@ import java.util.NoSuchElementException;
|
|||||||
* @author last modified by $Author: haku $
|
* @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 $
|
* @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[] chain;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
int next = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-13
@@ -52,13 +52,13 @@ public final class DOMSerializer {
|
|||||||
private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
|
private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
|
||||||
private static final String PARAM_XML_DECLARATION = "xml-declaration";
|
private static final String PARAM_XML_DECLARATION = "xml-declaration";
|
||||||
|
|
||||||
private final LSSerializer mSerializer;
|
private final LSSerializer serializer;
|
||||||
private final LSOutput mOutput;
|
private final LSOutput output;
|
||||||
|
|
||||||
private DOMSerializer() {
|
private DOMSerializer() {
|
||||||
DOMImplementationLS domImpl = Support.getImplementation();
|
DOMImplementationLS domImpl = Support.getImplementation();
|
||||||
mSerializer = domImpl.createLSSerializer();
|
serializer = domImpl.createLSSerializer();
|
||||||
mOutput = domImpl.createLSOutput();
|
output = domImpl.createLSOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,8 +71,8 @@ public final class DOMSerializer {
|
|||||||
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
|
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
|
||||||
this();
|
this();
|
||||||
|
|
||||||
mOutput.setByteStream(pStream);
|
output.setByteStream(pStream);
|
||||||
mOutput.setEncoding(pEncoding);
|
output.setEncoding(pEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,17 +84,17 @@ public final class DOMSerializer {
|
|||||||
public DOMSerializer(final Writer pStream) {
|
public DOMSerializer(final Writer pStream) {
|
||||||
this();
|
this();
|
||||||
|
|
||||||
mOutput.setCharacterStream(pStream);
|
output.setCharacterStream(pStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// TODO: Is it useful?
|
// TODO: Is it useful?
|
||||||
public void setNewLine(final String pNewLine) {
|
public void setNewLine(final String pNewLine) {
|
||||||
mSerializer.setNewLine(pNewLine);
|
serializer.setNewLine(pNewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewLine() {
|
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
|
* @param pPrettyPrint {@code true} to enable pretty printing
|
||||||
*/
|
*/
|
||||||
public void setPrettyPrint(final boolean pPrettyPrint) {
|
public void setPrettyPrint(final boolean pPrettyPrint) {
|
||||||
DOMConfiguration configuration = mSerializer.getDomConfig();
|
DOMConfiguration configuration = serializer.getDomConfig();
|
||||||
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
|
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
|
||||||
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
|
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getPrettyPrint() {
|
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) {
|
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) {
|
private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
|
||||||
setXMLDeclaration(pOmitDecl);
|
setXMLDeclaration(pOmitDecl);
|
||||||
mSerializer.write(pNode, mOutput);
|
serializer.write(pNode, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Support {
|
private static class Support {
|
||||||
+73
-53
@@ -51,56 +51,72 @@ public class XMLSerializer {
|
|||||||
// Main problem: Sun's Java 5 does not have LS 3.0 support
|
// Main problem: Sun's Java 5 does not have LS 3.0 support
|
||||||
// This class has no dependencies, which probably makes it more useful
|
// This class has no dependencies, which probably makes it more useful
|
||||||
|
|
||||||
|
// TODO: Don't insert initial and ending line-break for text-nodes
|
||||||
|
// TODO: Support not inserting line-breaks, to preserve space
|
||||||
// TODO: Support line breaking (at configurable width)
|
// TODO: Support line breaking (at configurable width)
|
||||||
// TODO: Support skipping XML declaration?
|
|
||||||
// TODO: Support standalone?
|
// TODO: Support standalone?
|
||||||
// TODO: Support more than version 1.0?
|
// TODO: Support more than version 1.0?
|
||||||
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
||||||
// to be more compatible...
|
// to be more compatible...
|
||||||
// TODO: Support not inserting line-breaks, to preserve space
|
|
||||||
|
|
||||||
// TODO: Idea: Create a SerializationContext that stores attributes on
|
private final OutputStream output;
|
||||||
// serialization, to keep the serialization thread-safe
|
private final Charset encoding;
|
||||||
// Store preserveSpace attribute in this context, to avoid costly traversals
|
private final SerializationContext context;
|
||||||
// Store user options here too
|
|
||||||
// TODO: Push/pop?
|
|
||||||
|
|
||||||
private final OutputStream mOutput;
|
|
||||||
private final Charset mEncoding;
|
|
||||||
private final SerializationContext mContext;
|
|
||||||
|
|
||||||
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
|
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
|
||||||
mOutput = pOutput;
|
output = pOutput;
|
||||||
mEncoding = Charset.forName(pEncoding);
|
encoding = Charset.forName(pEncoding);
|
||||||
mContext = new SerializationContext();
|
context = new SerializationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIndentation(String pIndent) {
|
public final XMLSerializer indentation(String pIndent) {
|
||||||
mContext.indent = pIndent != null ? pIndent : " ";
|
// TODO: Verify that indent value is only whitespace?
|
||||||
|
context.indent = pIndent != null ? pIndent : "\t";
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStripComments(boolean pStrip) {
|
public final XMLSerializer stripComments(boolean pStrip) {
|
||||||
mContext.stripComments = pStrip;
|
context.stripComments = pStrip;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the entire document, along with the XML declaration
|
||||||
|
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||||
|
*
|
||||||
|
* @param pDocument the document to serialize.
|
||||||
|
*/
|
||||||
public void serialize(final Document pDocument) {
|
public void serialize(final Document pDocument) {
|
||||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding));
|
serialize(pDocument, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the entire sub tree starting at {@code pRootNode}, along with an optional XML declaration
|
||||||
|
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||||
|
*
|
||||||
|
* @param pRootNode the root node to serialize.
|
||||||
|
* @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(output, encoding));
|
||||||
try {
|
try {
|
||||||
writeXMLDeclararion(out);
|
if (pWriteXMLDeclaration) {
|
||||||
writeXML(out, pDocument, mContext.copy());
|
writeXMLDeclaration(out);
|
||||||
|
}
|
||||||
|
writeXML(out, pRootNode, context.copy());
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeXMLDeclararion(final PrintWriter pOut) {
|
private void writeXMLDeclaration(final PrintWriter pOut) {
|
||||||
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
||||||
pOut.print(mEncoding.name());
|
pOut.print(encoding.name());
|
||||||
pOut.println("\"?>");
|
pOut.println("\"?>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeXML(final PrintWriter pOut, final Document pDocument, final SerializationContext pContext) {
|
private void writeXML(final PrintWriter pOut, final Node pDocument, final SerializationContext pContext) {
|
||||||
writeNodeRecursive(pOut, pDocument, pContext);
|
writeNodeRecursive(pOut, pDocument, pContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +149,7 @@ public class XMLSerializer {
|
|||||||
writeComment(pOut, pNode, pContext);
|
writeComment(pOut, pNode, pContext);
|
||||||
break;
|
break;
|
||||||
case Node.PROCESSING_INSTRUCTION_NODE:
|
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||||
writeProcessingInstruction(pOut, pNode);
|
writeProcessingInstruction(pOut, (ProcessingInstruction) pNode);
|
||||||
break;
|
break;
|
||||||
case Node.ATTRIBUTE_NODE:
|
case Node.ATTRIBUTE_NODE:
|
||||||
throw new IllegalArgumentException("Malformed input Document: Attribute nodes should only occur inside Element nodes");
|
throw new IllegalArgumentException("Malformed input Document: Attribute nodes should only occur inside Element nodes");
|
||||||
@@ -148,9 +164,16 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeProcessingInstruction(final PrintWriter pOut, final Node pNode) {
|
private void writeProcessingInstruction(final PrintWriter pOut, final ProcessingInstruction pNode) {
|
||||||
pOut.print("\n<?");
|
pOut.print("\n<?");
|
||||||
pOut.print(pNode.getNodeValue());
|
pOut.print(pNode.getTarget());
|
||||||
|
String value = pNode.getData();
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
pOut.print(" ");
|
||||||
|
pOut.print(value);
|
||||||
|
}
|
||||||
|
|
||||||
pOut.println("?>");
|
pOut.println("?>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,8 +184,11 @@ public class XMLSerializer {
|
|||||||
pOut.print(maybeEscapeElementValue(value));
|
pOut.print(maybeEscapeElementValue(value));
|
||||||
}
|
}
|
||||||
else if (!StringUtil.isEmpty(value)) {
|
else if (!StringUtil.isEmpty(value)) {
|
||||||
indentToLevel(pOut, pContext);
|
String escapedValue = maybeEscapeElementValue(value.trim());
|
||||||
pOut.println(maybeEscapeElementValue(value.trim()));
|
//if (escapedValue.length() + (pContext.level * pContext.indent.length()) > 78) {
|
||||||
|
indentToLevel(pOut, pContext);
|
||||||
|
//}
|
||||||
|
pOut.println(escapedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +209,7 @@ public class XMLSerializer {
|
|||||||
else if ("default".equals(space.getNodeValue())) {
|
else if ("default".equals(space.getNodeValue())) {
|
||||||
pContext.preserveSpace = false;
|
pContext.preserveSpace = false;
|
||||||
}
|
}
|
||||||
// No other values are allowed per spec, ingore
|
// No other values are allowed per spec, ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +226,7 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String value = pNode.getNodeValue();
|
String value = pNode.getNodeValue();
|
||||||
validateCommenValue(value);
|
validateCommentValue(value);
|
||||||
|
|
||||||
if (value.startsWith(" ")) {
|
if (value.startsWith(" ")) {
|
||||||
pOut.print("<!--");
|
pOut.print("<!--");
|
||||||
@@ -229,7 +255,7 @@ public class XMLSerializer {
|
|||||||
int startEscape = needsEscapeElement(pValue);
|
int startEscape = needsEscapeElement(pValue);
|
||||||
|
|
||||||
if (startEscape < 0) {
|
if (startEscape < 0) {
|
||||||
// If no escpaing is needed, simply return original
|
// If no escaping is needed, simply return original
|
||||||
return pValue;
|
return pValue;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -250,11 +276,7 @@ public class XMLSerializer {
|
|||||||
pos = appendAndEscape(pValue, pos, i, builder, ">");
|
pos = appendAndEscape(pValue, pos, i, builder, ">");
|
||||||
break;
|
break;
|
||||||
//case '\'':
|
//case '\'':
|
||||||
// pos = appendAndEscape(pString, pos, i, builder, "'");
|
|
||||||
// break;
|
|
||||||
//case '"':
|
//case '"':
|
||||||
// pos = appendAndEscape(pString, pos, i, builder, """);
|
|
||||||
// break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -318,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(""");
|
|
||||||
// start = end + 1;
|
|
||||||
// end = pValue.indexOf('"', start);
|
|
||||||
//}
|
|
||||||
//builder.append(pValue.substring(start));
|
|
||||||
|
|
||||||
builder.append(pValue.substring(pos));
|
builder.append(pValue.substring(pos));
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
@@ -355,18 +366,19 @@ public class XMLSerializer {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String validateCDataValue(final String pValue) {
|
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 ']]>'");
|
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
|
||||||
}
|
}
|
||||||
return pValue;
|
return pValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String validateCommenValue(final String 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 '--'");
|
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
|
||||||
}
|
}
|
||||||
return pValue;
|
return pValue;
|
||||||
@@ -388,6 +400,7 @@ public class XMLSerializer {
|
|||||||
|
|
||||||
// TODO: Attributes should probably include namespaces, so that it works
|
// TODO: Attributes should probably include namespaces, so that it works
|
||||||
// even if the document was created using attributes instead of namespaces...
|
// even if the document was created using attributes instead of namespaces...
|
||||||
|
// In that case, prefix will be null...
|
||||||
|
|
||||||
// Handle namespace
|
// Handle namespace
|
||||||
String namespace = pNode.getNamespaceURI();
|
String namespace = pNode.getNamespaceURI();
|
||||||
@@ -425,6 +438,7 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider not indenting/newline if the first child is a text node
|
||||||
// Iterate children if any
|
// Iterate children if any
|
||||||
if (pNode.hasChildNodes()) {
|
if (pNode.hasChildNodes()) {
|
||||||
pOut.print(">");
|
pOut.print(">");
|
||||||
@@ -433,11 +447,9 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NodeList children = pNode.getChildNodes();
|
NodeList children = pNode.getChildNodes();
|
||||||
//pContext.level++;
|
|
||||||
for (int i = 0; i < children.getLength(); i++) {
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
writeNodeRecursive(pOut, children.item(i), pContext.push());
|
writeNodeRecursive(pOut, children.item(i), pContext.push());
|
||||||
}
|
}
|
||||||
//pContext.level--;
|
|
||||||
|
|
||||||
if (!pContext.preserveSpace) {
|
if (!pContext.preserveSpace) {
|
||||||
indentToLevel(pOut, pContext);
|
indentToLevel(pOut, pContext);
|
||||||
@@ -496,12 +508,15 @@ public class XMLSerializer {
|
|||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
factory.setNamespaceAware(true);
|
factory.setNamespaceAware(true);
|
||||||
DocumentBuilder builder;
|
DocumentBuilder builder;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
builder = factory.newDocumentBuilder();
|
builder = factory.newDocumentBuilder();
|
||||||
}
|
}
|
||||||
catch (ParserConfigurationException e) {
|
catch (ParserConfigurationException e) {
|
||||||
|
//noinspection ThrowableInstanceNeverThrown BOGUS
|
||||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMImplementation dom = builder.getDOMImplementation();
|
DOMImplementation dom = builder.getDOMImplementation();
|
||||||
|
|
||||||
Document document = dom.createDocument("http://www.twelvemonkeys.com/xml/test", "test", dom.createDocumentType("test", null, null));
|
Document document = dom.createDocument("http://www.twelvemonkeys.com/xml/test", "test", dom.createDocumentType("test", null, null));
|
||||||
@@ -535,6 +550,11 @@ public class XMLSerializer {
|
|||||||
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
|
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
|
||||||
test.appendChild(pre);
|
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
|
// Create serializer and output document
|
||||||
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
|
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
|
||||||
System.out.println("XMLSerializer:");
|
System.out.println("XMLSerializer:");
|
||||||
@@ -577,7 +597,7 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static class SerializationContext implements Cloneable {
|
static class SerializationContext implements Cloneable {
|
||||||
String indent = " ";
|
String indent = "\t";
|
||||||
int level = 0;
|
int level = 0;
|
||||||
boolean preserveSpace = false;
|
boolean preserveSpace = false;
|
||||||
boolean stripComments = false;
|
boolean stripComments = false;
|
||||||
+1
-1
@@ -60,7 +60,7 @@ iff,ilbm=image/x-iff;image/iff
|
|||||||
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
||||||
jpm=image/jpm
|
jpm=image/jpm
|
||||||
png=image/png;image/x-png
|
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
|
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
|
||||||
tga=image/targa;image/x-targa
|
tga=image/targa;image/x-targa
|
||||||
tif,tiff=image/tiff;image/x-tiff
|
tif,tiff=image/tiff;image/x-tiff
|
||||||
+6
-1
@@ -2,6 +2,7 @@ package com.twelvemonkeys.io;
|
|||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
import com.twelvemonkeys.util.CollectionUtil;
|
import com.twelvemonkeys.util.CollectionUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -9,6 +10,8 @@ import java.io.StringReader;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CompoundReaderTestCase
|
* CompoundReaderTestCase
|
||||||
* <p/>
|
* <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 $
|
* @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 {
|
public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
||||||
|
|
||||||
protected Reader makeReader(String pInput) {
|
protected Reader makeReader(String pInput) {
|
||||||
// Split
|
// Split
|
||||||
String[] input = StringUtil.toStringArray(pInput, " ");
|
String[] input = StringUtil.toStringArray(pInput, " ");
|
||||||
@@ -36,6 +38,7 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
|||||||
return new CompoundReader(readers.iterator());
|
return new CompoundReader(readers.iterator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testNullConstructor() {
|
public void testNullConstructor() {
|
||||||
try {
|
try {
|
||||||
new CompoundReader(null);
|
new CompoundReader(null);
|
||||||
@@ -46,11 +49,13 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testEmptyIteratorConstructor() throws IOException {
|
public void testEmptyIteratorConstructor() throws IOException {
|
||||||
Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0]));
|
Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0]));
|
||||||
assertEquals(-1, reader.read());
|
assertEquals(-1, reader.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testIteratorWithNullConstructor() throws IOException {
|
public void testIteratorWithNullConstructor() throws IOException {
|
||||||
try {
|
try {
|
||||||
new CompoundReader(CollectionUtil.iterator(new Reader[] {null}));
|
new CompoundReader(CollectionUtil.iterator(new Reader[] {null}));
|
||||||
+5
@@ -1,8 +1,12 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FastByteArrayOutputStreamTestCase
|
* FastByteArrayOutputStreamTestCase
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -16,6 +20,7 @@ public class FastByteArrayOutputStreamTestCase extends OutputStreamAbstractTestC
|
|||||||
return new FastByteArrayOutputStream(256);
|
return new FastByteArrayOutputStream(256);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testCreateInputStream() throws IOException {
|
public void testCreateInputStream() throws IOException {
|
||||||
FastByteArrayOutputStream out = makeObject();
|
FastByteArrayOutputStream out = makeObject();
|
||||||
|
|
||||||
-4
@@ -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 $
|
* @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 class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||||
public FileCacheSeekableStreamTestCase(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||||
try {
|
try {
|
||||||
return new FileCacheSeekableStream(pStream);
|
return new FileCacheSeekableStream(pStream);
|
||||||
+6
-4
@@ -1,7 +1,11 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MemoryCacheSeekableStreamTestCase
|
* MemoryCacheSeekableStreamTestCase
|
||||||
* <p/>
|
* <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 $
|
* @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 class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||||
public FileSeekableStreamTestCase(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||||
try {
|
try {
|
||||||
return new FileSeekableStream(createFileWithContent(pStream));
|
return new FileSeekableStream(createFileWithContent(pStream));
|
||||||
@@ -37,11 +37,13 @@ public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestC
|
|||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testCloseUnderlyingStream() throws IOException {
|
public void testCloseUnderlyingStream() throws IOException {
|
||||||
// There is no underlying stream here...
|
// There is no underlying stream here...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testCloseUnderlyingFile() throws IOException {
|
public void testCloseUnderlyingFile() throws IOException {
|
||||||
final boolean[] closed = new boolean[1];
|
final boolean[] closed = new boolean[1];
|
||||||
|
|
||||||
+31
-11
@@ -17,12 +17,15 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InputStreamAbstractTestCase
|
* InputStreamAbstractTestCase
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -38,10 +41,6 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
final static private long SEED = 29487982745l;
|
final static private long SEED = 29487982745l;
|
||||||
final static Random sRandom = new Random(SEED);
|
final static Random sRandom = new Random(SEED);
|
||||||
|
|
||||||
public InputStreamAbstractTestCase(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Object makeObject() {
|
protected final Object makeObject() {
|
||||||
return makeInputStream();
|
return makeInputStream();
|
||||||
}
|
}
|
||||||
@@ -71,11 +70,12 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testRead() throws Exception {
|
public void testRead() throws Exception {
|
||||||
int size = 5;
|
int size = 5;
|
||||||
InputStream input = makeInputStream(makeOrderedArray(size));
|
InputStream input = makeInputStream(makeOrderedArray(size));
|
||||||
for (int i = 0; i < size; i++) {
|
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("Check Value [" + i + "]", i, input.read());
|
||||||
}
|
}
|
||||||
assertEquals("Available after contents all read", 0, input.available());
|
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 {
|
public void testAvailable() throws Exception {
|
||||||
InputStream input = makeInputStream(1);
|
InputStream input = makeInputStream(1);
|
||||||
assertFalse("Unexpected EOF", input.read() < 0);
|
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());
|
assertEquals("Available after End of File", 0, input.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReadByteArray() throws Exception {
|
public void testReadByteArray() throws Exception {
|
||||||
byte[] bytes = new byte[10];
|
byte[] bytes = new byte[10];
|
||||||
byte[] data = makeOrderedArray(15);
|
byte[] data = makeOrderedArray(15);
|
||||||
@@ -145,6 +147,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testEOF() throws Exception {
|
public void testEOF() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(2));
|
InputStream input = makeInputStream(makeOrderedArray(2));
|
||||||
assertEquals("Read 1", 0, input.read());
|
assertEquals("Read 1", 0, input.read());
|
||||||
@@ -154,6 +157,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
assertEquals("Read 5", -1, input.read());
|
assertEquals("Read 5", -1, input.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testMarkResetUnsupported() throws IOException {
|
public void testMarkResetUnsupported() throws IOException {
|
||||||
InputStream input = makeInputStream(10);
|
InputStream input = makeInputStream(10);
|
||||||
if (input.markSupported()) {
|
if (input.markSupported()) {
|
||||||
@@ -163,7 +167,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
input.mark(100); // Should be a no-op
|
input.mark(100); // Should be a no-op
|
||||||
|
|
||||||
int read = input.read();
|
int read = input.read();
|
||||||
assertEquals(0, read);
|
assertTrue(read >= 0);
|
||||||
|
|
||||||
// TODO: According to InputStream#reset, it is allowed to do some
|
// TODO: According to InputStream#reset, it is allowed to do some
|
||||||
// implementation specific reset, and still be correct...
|
// implementation specific reset, and still be correct...
|
||||||
@@ -176,6 +180,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResetNoMark() throws Exception {
|
public void testResetNoMark() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(10));
|
InputStream input = makeInputStream(makeOrderedArray(10));
|
||||||
|
|
||||||
@@ -196,6 +201,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testMarkReset() throws Exception {
|
public void testMarkReset() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||||
|
|
||||||
@@ -226,6 +232,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResetAfterReadLimit() throws Exception {
|
public void testResetAfterReadLimit() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||||
|
|
||||||
@@ -257,6 +264,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResetAfterReset() throws Exception {
|
public void testResetAfterReset() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||||
|
|
||||||
@@ -264,7 +272,8 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
return; // Not supported, skip test
|
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;
|
int readlimit = 5;
|
||||||
|
|
||||||
@@ -273,19 +282,24 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
int read = input.read();
|
int read = input.read();
|
||||||
assertTrue("Expected to read positive value", read >= 0);
|
assertTrue("Expected to read positive value", read >= 0);
|
||||||
|
|
||||||
input.reset();
|
assertTrue(input.read() >= 0);
|
||||||
assertEquals("Expected value read differes from actual", read, input.read());
|
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 {
|
try {
|
||||||
input.reset();
|
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) {
|
catch (Exception e) {
|
||||||
assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark"));
|
assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSkip() throws Exception {
|
public void testSkip() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(10));
|
InputStream input = makeInputStream(makeOrderedArray(10));
|
||||||
|
|
||||||
@@ -302,6 +316,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
assertEquals("Unexpected value read after EOF", -1, input.read());
|
assertEquals("Unexpected value read after EOF", -1, input.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityOrdered() throws IOException {
|
public void testSanityOrdered() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = makeOrderedArray(25);
|
byte[] bytes = makeOrderedArray(25);
|
||||||
@@ -314,6 +329,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityOrdered2() throws IOException {
|
public void testSanityOrdered2() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = makeOrderedArray(25);
|
byte[] bytes = makeOrderedArray(25);
|
||||||
@@ -332,6 +348,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityNegative() throws IOException {
|
public void testSanityNegative() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = new byte[25];
|
byte[] bytes = new byte[25];
|
||||||
@@ -347,6 +364,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityNegative2() throws IOException {
|
public void testSanityNegative2() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = new byte[25];
|
byte[] bytes = new byte[25];
|
||||||
@@ -368,6 +386,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityRandom() throws IOException {
|
public void testSanityRandom() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = makeRandomArray(25);
|
byte[] bytes = makeRandomArray(25);
|
||||||
@@ -380,6 +399,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSanityRandom2() throws IOException {
|
public void testSanityRandom2() throws IOException {
|
||||||
// This is to sanity check that the test itself is correct...
|
// This is to sanity check that the test itself is correct...
|
||||||
byte[] bytes = makeRandomArray(25);
|
byte[] bytes = makeRandomArray(25);
|
||||||
+208
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
-4
@@ -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 $
|
* @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 class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||||
public MemoryCacheSeekableStreamTestCase(String name) {
|
|
||||||
super(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||||
return new MemoryCacheSeekableStream(pStream);
|
return new MemoryCacheSeekableStream(pStream);
|
||||||
}
|
}
|
||||||
+21
-3
@@ -1,10 +1,13 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InputStreamAbstractTestCase
|
* InputStreamAbstractTestCase
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -15,6 +18,7 @@ import java.io.IOException;
|
|||||||
public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase {
|
public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase {
|
||||||
protected abstract OutputStream makeObject();
|
protected abstract OutputStream makeObject();
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWrite() throws IOException {
|
public void testWrite() throws IOException {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
|
|
||||||
@@ -23,12 +27,14 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArray() throws IOException {
|
public void testWriteByteArray() throws IOException {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
|
|
||||||
os.write(new byte[256]);
|
os.write(new byte[256]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArrayNull() {
|
public void testWriteByteArrayNull() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
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];
|
byte[] input = new byte[256];
|
||||||
|
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
@@ -65,7 +72,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWriteByteArrayZeroLenght() {
|
@Test
|
||||||
|
public void testWriteByteArrayZeroLength() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
os.write(new byte[1], 0, 0);
|
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();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
os.write(null, 5, 10);
|
os.write(null, 5, 10);
|
||||||
@@ -92,6 +101,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArrayNegativeOffset() {
|
public void testWriteByteArrayNegativeOffset() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
@@ -109,6 +119,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArrayNegativeLength() {
|
public void testWriteByteArrayNegativeLength() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
@@ -126,6 +137,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArrayOffsetOutOfBounds() {
|
public void testWriteByteArrayOffsetOutOfBounds() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
@@ -143,6 +155,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteByteArrayLengthOutOfBounds() {
|
public void testWriteByteArrayLengthOutOfBounds() {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
try {
|
try {
|
||||||
@@ -160,14 +173,17 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testFlush() {
|
public void testFlush() {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testClose() {
|
public void testClose() {
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testWriteAfterClose() throws IOException {
|
public void testWriteAfterClose() throws IOException {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
|
|
||||||
@@ -200,6 +216,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testFlushAfterClose() throws IOException {
|
public void testFlushAfterClose() throws IOException {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
|
|
||||||
@@ -221,6 +238,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testCloseAfterClose() throws IOException {
|
public void testCloseAfterClose() throws IOException {
|
||||||
OutputStream os = makeObject();
|
OutputStream os = makeObject();
|
||||||
|
|
||||||
+10
-1
@@ -1,10 +1,13 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ReaderAbstractTestCase
|
* ReaderAbstractTestCase
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -36,6 +39,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
|
|
||||||
protected abstract Reader makeReader(String pInput);
|
protected abstract Reader makeReader(String pInput);
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testRead() throws IOException {
|
public void testRead() throws IOException {
|
||||||
Reader reader = makeReader();
|
Reader reader = makeReader();
|
||||||
|
|
||||||
@@ -51,6 +55,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
assertEquals(mInput, buffer.toString());
|
assertEquals(mInput, buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReadBuffer() throws IOException {
|
public void testReadBuffer() throws IOException {
|
||||||
Reader reader = makeReader();
|
Reader reader = makeReader();
|
||||||
|
|
||||||
@@ -70,6 +75,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
assertEquals(mInput, new String(chars));
|
assertEquals(mInput, new String(chars));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSkipToEnd() throws IOException {
|
public void testSkipToEnd() throws IOException {
|
||||||
Reader reader = makeReader();
|
Reader reader = makeReader();
|
||||||
|
|
||||||
@@ -83,6 +89,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
assertEquals(0, toSkip);
|
assertEquals(0, toSkip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSkipToEndAndRead() throws IOException {
|
public void testSkipToEndAndRead() throws IOException {
|
||||||
Reader reader = makeReader();
|
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)
|
// TODO: It's possible to support reset and not mark (resets to beginning of stream, for example)
|
||||||
|
@Test
|
||||||
public void testResetMarkSupported() throws IOException {
|
public void testResetMarkSupported() throws IOException {
|
||||||
Reader reader = makeReader();
|
Reader reader = makeReader();
|
||||||
|
|
||||||
@@ -154,6 +162,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testResetMarkNotSupported() throws IOException {
|
public void testResetMarkNotSupported() throws IOException {
|
||||||
Reader reader = makeReader();
|
Reader reader = makeReader();
|
||||||
|
|
||||||
@@ -198,7 +207,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReadAfterClose() throws IOException {
|
public void testReadAfterClose() throws IOException {
|
||||||
Reader reader = makeReader("foo bar");
|
Reader reader = makeReader("foo bar");
|
||||||
|
|
||||||
+7
-3
@@ -1,6 +1,8 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SeekableAbstractTestCase
|
* SeekableAbstractTestCase
|
||||||
@@ -9,14 +11,16 @@ import junit.framework.TestCase;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @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 $
|
* @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();
|
protected abstract Seekable createSeekable();
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testFail() {
|
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() {
|
public void testSeekable() {
|
||||||
assertTrue(createSeekable() instanceof Seekable);
|
assertTrue(createSeekable() instanceof Seekable);
|
||||||
}
|
}
|
||||||
+19
-11
@@ -1,10 +1,14 @@
|
|||||||
package com.twelvemonkeys.io;
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SeekableInputStreamAbstractTestCase
|
* SeekableInputStreamAbstractTestCase
|
||||||
* <p/>
|
* <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 $
|
* @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 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
|
//// 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() {
|
protected Seekable createSeekable() {
|
||||||
return makeInputStream();
|
return makeInputStream();
|
||||||
}
|
}
|
||||||
@@ -41,6 +40,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
|
|
||||||
protected abstract SeekableInputStream makeInputStream(InputStream pStream);
|
protected abstract SeekableInputStream makeInputStream(InputStream pStream);
|
||||||
|
|
||||||
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testResetAfterReset() throws Exception {
|
public void testResetAfterReset() throws Exception {
|
||||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||||
@@ -59,9 +59,9 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
assertTrue("Expected to read positive value", read >= 0);
|
assertTrue("Expected to read positive value", read >= 0);
|
||||||
|
|
||||||
input.reset();
|
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 {
|
try {
|
||||||
input.reset();
|
input.reset();
|
||||||
assertEquals("Re-read of reset data should be first", 0, input.read());
|
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() {
|
public void testSeekable() {
|
||||||
mSeekableTestCase.testSeekable();
|
seekableTestCase.testSeekable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testFlushBeyondCurrentPos() throws Exception {
|
public void testFlushBeyondCurrentPos() throws Exception {
|
||||||
SeekableInputStream seekable = makeInputStream(20);
|
SeekableInputStream seekable = makeInputStream(20);
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSeek() throws Exception {
|
public void testSeek() throws Exception {
|
||||||
SeekableInputStream seekable = makeInputStream(55);
|
SeekableInputStream seekable = makeInputStream(55);
|
||||||
int pos = 37;
|
int pos = 37;
|
||||||
@@ -97,6 +100,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
assertEquals("Stream positon should match seeked position", pos, streamPos);
|
assertEquals("Stream positon should match seeked position", pos, streamPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSeekFlush() throws Exception {
|
public void testSeekFlush() throws Exception {
|
||||||
SeekableInputStream seekable = makeInputStream(133);
|
SeekableInputStream seekable = makeInputStream(133);
|
||||||
int pos = 45;
|
int pos = 45;
|
||||||
@@ -114,6 +118,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testMarkFlushReset() throws Exception {
|
public void testMarkFlushReset() throws Exception {
|
||||||
SeekableInputStream seekable = makeInputStream(77);
|
SeekableInputStream seekable = makeInputStream(77);
|
||||||
|
|
||||||
@@ -134,6 +139,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
assertEquals(position, seekable.getStreamPosition());
|
assertEquals(position, seekable.getStreamPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testSeekSkipRead() throws Exception {
|
public void testSeekSkipRead() throws Exception {
|
||||||
SeekableInputStream seekable = makeInputStream(133);
|
SeekableInputStream seekable = makeInputStream(133);
|
||||||
int pos = 45;
|
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();
|
System.out.println();
|
||||||
pSeekable.seek(pStr.length());
|
pSeekable.seek(pStr.length());
|
||||||
FileUtil.read(pSeekable);
|
FileUtil.read(pSeekable);
|
||||||
@@ -330,6 +336,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReadResetReadDirectBufferBug() throws IOException {
|
public void testReadResetReadDirectBufferBug() throws IOException {
|
||||||
// Make sure we use the exact size of the buffer
|
// Make sure we use the exact size of the buffer
|
||||||
final int size = 1024;
|
final int size = 1024;
|
||||||
@@ -365,6 +372,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
assertTrue(rangeEquals(bytes, size, result, 0, size));
|
assertTrue(rangeEquals(bytes, size, result, 0, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testReadAllByteValuesRegression() throws IOException {
|
public void testReadAllByteValuesRegression() throws IOException {
|
||||||
final int size = 128;
|
final int size = 128;
|
||||||
|
|
||||||
@@ -401,6 +409,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
public void testCloseUnderlyingStream() throws IOException {
|
public void testCloseUnderlyingStream() throws IOException {
|
||||||
final boolean[] closed = new boolean[1];
|
final boolean[] closed = new boolean[1];
|
||||||
|
|
||||||
@@ -476,5 +485,4 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user