mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-01 00:00:02 -04:00
Compare commits
440 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 3050899903 | |||
| abdecf1d5e | |||
| 673527abe5 | |||
| 830035bcec | |||
| 41853cec7e | |||
| 5b7fcd5c95 | |||
| ee99550a65 | |||
| 40e6486154 | |||
| 38d6c58eba | |||
| c310083d2a | |||
| ec4334cbb5 | |||
| 27553dc47a | |||
| e8a4cc048c | |||
| f8b716687c | |||
| 0786949c1c | |||
| b8faa6e36f | |||
| 7167a7a4ad | |||
| e0a6c0a2bd | |||
| 6eaac4ec8b | |||
| b3aa378f16 | |||
| ad913b5093 | |||
| 9b615de8ed | |||
| ba5f0a2f5f | |||
| 2016be8f9a | |||
| 45a42ea8a3 | |||
| 0d41db32cf | |||
| b5f6c96583 | |||
| 34d874d69d |
@@ -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.1</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 $
|
||||
*/
|
||||
public abstract class AbstractImageSource implements ImageProducer {
|
||||
private List<ImageConsumer> mConsumers = new ArrayList<ImageConsumer>();
|
||||
protected int mWidth;
|
||||
protected int mHeight;
|
||||
protected int mXOff;
|
||||
protected int mYOff;
|
||||
private List<ImageConsumer> consumers = new ArrayList<ImageConsumer>();
|
||||
protected int width;
|
||||
protected int height;
|
||||
protected int xOff;
|
||||
protected int yOff;
|
||||
|
||||
// ImageProducer interface
|
||||
public void addConsumer(ImageConsumer pConsumer) {
|
||||
if (mConsumers.contains(pConsumer)) {
|
||||
public void addConsumer(final ImageConsumer pConsumer) {
|
||||
if (consumers.contains(pConsumer)) {
|
||||
return;
|
||||
}
|
||||
mConsumers.add(pConsumer);
|
||||
|
||||
consumers.add(pConsumer);
|
||||
|
||||
try {
|
||||
initConsumer(pConsumer);
|
||||
sendPixels(pConsumer);
|
||||
|
||||
if (isConsumer(pConsumer)) {
|
||||
pConsumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
|
||||
|
||||
@@ -68,34 +71,35 @@ public abstract class AbstractImageSource implements ImageProducer {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
if (isConsumer(pConsumer)) {
|
||||
pConsumer.imageComplete(ImageConsumer.IMAGEERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeConsumer(ImageConsumer pConsumer) {
|
||||
mConsumers.remove(pConsumer);
|
||||
public void removeConsumer(final ImageConsumer pConsumer) {
|
||||
consumers.remove(pConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation silently ignores this instruction. If pixeldata is
|
||||
* This implementation silently ignores this instruction. If pixel data is
|
||||
* not in TDLR order by default, subclasses must override this method.
|
||||
*
|
||||
* @param pConsumer the consumer that requested the resend
|
||||
*
|
||||
* @see ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
|
||||
*/
|
||||
public void requestTopDownLeftRightResend(ImageConsumer pConsumer) {
|
||||
public void requestTopDownLeftRightResend(final ImageConsumer pConsumer) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void startProduction(ImageConsumer pConsumer) {
|
||||
public void startProduction(final ImageConsumer pConsumer) {
|
||||
addConsumer(pConsumer);
|
||||
}
|
||||
|
||||
public boolean isConsumer(ImageConsumer pConsumer) {
|
||||
return mConsumers.contains(pConsumer);
|
||||
public boolean isConsumer(final ImageConsumer pConsumer) {
|
||||
return consumers.contains(pConsumer);
|
||||
}
|
||||
|
||||
protected abstract void initConsumer(ImageConsumer pConsumer);
|
||||
+34
-33
@@ -47,33 +47,34 @@ import java.io.IOException;
|
||||
*/
|
||||
public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
final private int mWidth;
|
||||
final private int mHeight;
|
||||
final private int width;
|
||||
final private int height;
|
||||
|
||||
private Rectangle mSourceRegion;
|
||||
private Rectangle sourceRegion;
|
||||
|
||||
public AreaAverageOp(final int pWidth, final int pHeight) {
|
||||
mWidth = pWidth;
|
||||
mHeight = pHeight;
|
||||
width = pWidth;
|
||||
height = pHeight;
|
||||
}
|
||||
|
||||
public Rectangle getSourceRegion() {
|
||||
if (mSourceRegion == null) {
|
||||
if (sourceRegion == null) {
|
||||
return null;
|
||||
}
|
||||
return new Rectangle(mSourceRegion);
|
||||
|
||||
return new Rectangle(sourceRegion);
|
||||
}
|
||||
|
||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||
if (pSourceRegion == null) {
|
||||
mSourceRegion = null;
|
||||
sourceRegion = null;
|
||||
}
|
||||
else {
|
||||
if (mSourceRegion == null) {
|
||||
mSourceRegion = new Rectangle(pSourceRegion);
|
||||
if (sourceRegion == null) {
|
||||
sourceRegion = new Rectangle(pSourceRegion);
|
||||
}
|
||||
else {
|
||||
mSourceRegion.setBounds(pSourceRegion);
|
||||
sourceRegion.setBounds(pSourceRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +94,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// Straight-forward version
|
||||
//Image scaled = src.getScaledInstance(mWidth, mHeight, Image.SCALE_AREA_AVERAGING);
|
||||
//Image scaled = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
|
||||
//ImageUtil.drawOnto(result, scaled);
|
||||
//result = new BufferedImageFactory(scaled).getBufferedImage();
|
||||
|
||||
@@ -104,7 +105,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
AffineTransform xform = null;
|
||||
int w = src.getWidth();
|
||||
int h = src.getHeight();
|
||||
while (w / 2 > mWidth && h / 2 > mHeight) {
|
||||
while (w / 2 > width && h / 2 > height) {
|
||||
w /= 2;
|
||||
h /= 2;
|
||||
|
||||
@@ -129,7 +130,7 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
src = temp.getSubimage(0, 0, w, h);
|
||||
}
|
||||
|
||||
resample(src, result, AffineTransform.getScaleInstance(mWidth / (double) w, mHeight / (double) h));
|
||||
resample(src, result, AffineTransform.getScaleInstance(width / (double) w, height / (double) h));
|
||||
*/
|
||||
|
||||
// The real version
|
||||
@@ -160,11 +161,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
if (mSourceRegion != null) {
|
||||
int cx = mSourceRegion.x;
|
||||
int cy = mSourceRegion.y;
|
||||
int cw = mSourceRegion.width;
|
||||
int ch = mSourceRegion.height;
|
||||
if (sourceRegion != null) {
|
||||
int cx = sourceRegion.x;
|
||||
int cy = sourceRegion.y;
|
||||
int cw = sourceRegion.width;
|
||||
int ch = sourceRegion.height;
|
||||
|
||||
boolean same = src == dest;
|
||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
||||
@@ -179,11 +180,11 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
// TODO: This don't work too well..
|
||||
// The thing is that the step length and the scan length will vary, for
|
||||
// non-even (1/2, 1/4, 1/8 etc) resampling
|
||||
int widthSteps = (width + mWidth - 1) / mWidth;
|
||||
int heightSteps = (height + mHeight - 1) / mHeight;
|
||||
int widthSteps = (width + this.width - 1) / this.width;
|
||||
int heightSteps = (height + this.height - 1) / this.height;
|
||||
|
||||
final boolean oddX = width % mWidth != 0;
|
||||
final boolean oddY = height % mHeight != 0;
|
||||
final boolean oddX = width % this.width != 0;
|
||||
final boolean oddY = height % this.height != 0;
|
||||
|
||||
final int dataElements = src.getNumDataElements();
|
||||
final int bands = src.getNumBands();
|
||||
@@ -210,16 +211,16 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < mHeight; y++) {
|
||||
if (!oddY || y < mHeight) {
|
||||
for (int y = 0; y < this.height; y++) {
|
||||
if (!oddY || y < this.height) {
|
||||
scanH = heightSteps;
|
||||
}
|
||||
else {
|
||||
scanH = height - (y * heightSteps);
|
||||
}
|
||||
|
||||
for (int x = 0; x < mWidth; x++) {
|
||||
if (!oddX || x < mWidth) {
|
||||
for (int x = 0; x < this.width; x++) {
|
||||
if (!oddX || x < this.width) {
|
||||
scanW = widthSteps;
|
||||
}
|
||||
else {
|
||||
@@ -243,8 +244,8 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
//
|
||||
//System.err.println("width: " + width);
|
||||
//System.err.println("height: " + height);
|
||||
//System.err.println("mWidth: " + mWidth);
|
||||
//System.err.println("mHeight: " + mHeight);
|
||||
//System.err.println("width: " + width);
|
||||
//System.err.println("height: " + height);
|
||||
//
|
||||
//e.printStackTrace();
|
||||
continue;
|
||||
@@ -382,20 +383,20 @@ public class AreaAverageOp implements BufferedImageOp, RasterOp {
|
||||
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
|
||||
ColorModel cm = destCM != null ? destCM : src.getColorModel();
|
||||
return new BufferedImage(cm,
|
||||
ImageUtil.createCompatibleWritableRaster(src, cm, mWidth, mHeight),
|
||||
ImageUtil.createCompatibleWritableRaster(src, cm, width, height),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(Raster src) {
|
||||
return src.createCompatibleWritableRaster(mWidth, mHeight);
|
||||
return src.createCompatibleWritableRaster(width, height);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(Raster src) {
|
||||
return new Rectangle(mWidth, mHeight);
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(mWidth, mHeight);
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
+8
-7
@@ -60,14 +60,16 @@ import java.awt.image.RGBImageFilter;
|
||||
|
||||
public class BrightnessContrastFilter extends RGBImageFilter {
|
||||
|
||||
// TODO: Replace with RescaleOp?
|
||||
|
||||
// This filter can filter IndexColorModel, as it is does not depend on
|
||||
// the pixels' location
|
||||
{
|
||||
canFilterIndexColorModel = true;
|
||||
}
|
||||
|
||||
// Use a precalculated lookup table for performace
|
||||
private int[] mLUT = null;
|
||||
// Use a pre-calculated lookup table for performance
|
||||
private final int[] LUT;
|
||||
|
||||
/**
|
||||
* Creates a BrightnessContrastFilter with default values
|
||||
@@ -105,7 +107,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
||||
* {@code -1.0,..,0.0,..,1.0}.
|
||||
*/
|
||||
public BrightnessContrastFilter(float pBrightness, float pContrast) {
|
||||
mLUT = createLUT(pBrightness, pContrast);
|
||||
LUT = createLUT(pBrightness, pContrast);
|
||||
}
|
||||
|
||||
private static int[] createLUT(float pBrightness, float pContrast) {
|
||||
@@ -149,7 +151,6 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
||||
*
|
||||
* @return the filtered pixel value in the default color space
|
||||
*/
|
||||
|
||||
public int filterRGB(int pX, int pY, int pARGB) {
|
||||
// Get color components
|
||||
int r = pARGB >> 16 & 0xFF;
|
||||
@@ -157,9 +158,9 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
||||
int b = pARGB & 0xFF;
|
||||
|
||||
// Scale to new contrast
|
||||
r = mLUT[r];
|
||||
g = mLUT[g];
|
||||
b = mLUT[b];
|
||||
r = LUT[r];
|
||||
g = LUT[g];
|
||||
b = LUT[b];
|
||||
|
||||
// Return ARGB pixel, leave transparency as is
|
||||
return (pARGB & 0xFF000000) | (r << 16) | (g << 8) | b;
|
||||
+609
-542
File diff suppressed because it is too large
Load Diff
+20
-21
@@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.*;
|
||||
@@ -41,51 +43,48 @@ import java.awt.geom.AffineTransform;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/BufferedImageIcon.java#2 $
|
||||
*/
|
||||
public class BufferedImageIcon implements Icon {
|
||||
private final BufferedImage mImage;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private final boolean mFast;
|
||||
private final BufferedImage image;
|
||||
private int width;
|
||||
private int height;
|
||||
private final boolean fast;
|
||||
|
||||
public BufferedImageIcon(BufferedImage pImage) {
|
||||
this(pImage, pImage.getWidth(), pImage.getHeight());
|
||||
this(pImage, pImage != null ? pImage.getWidth() : 0, pImage != null ? pImage.getHeight() : 0);
|
||||
}
|
||||
|
||||
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
|
||||
if (pImage == null) {
|
||||
throw new IllegalArgumentException("image == null");
|
||||
}
|
||||
if (pWidth <= 0 || pHeight <= 0) {
|
||||
throw new IllegalArgumentException("Icon size must be positive");
|
||||
}
|
||||
this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight);
|
||||
}
|
||||
|
||||
mImage = pImage;
|
||||
mWidth = pWidth;
|
||||
mHeight = pHeight;
|
||||
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) {
|
||||
image = Validate.notNull(pImage, "image");
|
||||
width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d");
|
||||
height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d");
|
||||
|
||||
mFast = pImage.getWidth() == mWidth && pImage.getHeight() == mHeight;
|
||||
fast = useFastRendering;
|
||||
}
|
||||
|
||||
public int getIconHeight() {
|
||||
return mHeight;
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getIconWidth() {
|
||||
return mWidth;
|
||||
return width;
|
||||
}
|
||||
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
if (mFast || !(g instanceof Graphics2D)) {
|
||||
if (fast || !(g instanceof Graphics2D)) {
|
||||
//System.out.println("Scaling fast");
|
||||
g.drawImage(mImage, x, y, mWidth, mHeight, null);
|
||||
g.drawImage(image, x, y, width, height, null);
|
||||
}
|
||||
else {
|
||||
//System.out.println("Scaling using interpolation");
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
||||
xform.scale(mWidth / (double) mImage.getWidth(), mHeight / (double) mImage.getHeight());
|
||||
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(mImage, xform, null);
|
||||
g2.drawImage(image, xform, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-21
@@ -73,14 +73,15 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
*/
|
||||
public static final int EDGE_WRAP = 3; // as JAI BORDER_WRAP
|
||||
|
||||
private final Kernel mKernel;
|
||||
private final int mEdgeCondition;
|
||||
private final Kernel kernel;
|
||||
private final int edgeCondition;
|
||||
|
||||
private final ConvolveOp mConvolve;
|
||||
private final ConvolveOp convolve;
|
||||
|
||||
public ConvolveWithEdgeOp(final Kernel pKernel, final int pEdgeCondition, final RenderingHints pHints) {
|
||||
// Create convolution operation
|
||||
int edge;
|
||||
|
||||
switch (pEdgeCondition) {
|
||||
case EDGE_REFLECT:
|
||||
case EDGE_WRAP:
|
||||
@@ -90,9 +91,10 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
edge = pEdgeCondition;
|
||||
break;
|
||||
}
|
||||
mKernel = pKernel;
|
||||
mEdgeCondition = pEdgeCondition;
|
||||
mConvolve = new ConvolveOp(pKernel, edge, pHints);
|
||||
|
||||
kernel = pKernel;
|
||||
edgeCondition = pEdgeCondition;
|
||||
convolve = new ConvolveOp(pKernel, edge, pHints);
|
||||
}
|
||||
|
||||
public ConvolveWithEdgeOp(final Kernel pKernel) {
|
||||
@@ -107,8 +109,8 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
throw new IllegalArgumentException("source image cannot be the same as the destination image");
|
||||
}
|
||||
|
||||
int borderX = mKernel.getWidth() / 2;
|
||||
int borderY = mKernel.getHeight() / 2;
|
||||
int borderX = kernel.getWidth() / 2;
|
||||
int borderY = kernel.getHeight() / 2;
|
||||
|
||||
BufferedImage original = addBorder(pSource, borderX, borderY);
|
||||
|
||||
@@ -126,7 +128,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
}
|
||||
|
||||
// Do the filtering (if destination is null, a new image will be created)
|
||||
destination = mConvolve.filter(original, destination);
|
||||
destination = convolve.filter(original, destination);
|
||||
|
||||
if (pSource != original) {
|
||||
// Remove the border
|
||||
@@ -137,7 +139,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
}
|
||||
|
||||
private BufferedImage addBorder(final BufferedImage pOriginal, final int pBorderX, final int pBorderY) {
|
||||
if ((mEdgeCondition & 2) == 0) {
|
||||
if ((edgeCondition & 2) == 0) {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
@@ -158,7 +160,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
g.drawImage(pOriginal, pBorderX, pBorderY, null);
|
||||
|
||||
// TODO: I guess we need the top/left etc, if the corner pixels are covered by the kernel
|
||||
switch (mEdgeCondition) {
|
||||
switch (edgeCondition) {
|
||||
case EDGE_REFLECT:
|
||||
// Top/left (empty)
|
||||
g.drawImage(pOriginal, pBorderX, 0, pBorderX + w, pBorderY, 0, 0, w, 1, null); // Top/center
|
||||
@@ -186,7 +188,7 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
g.drawImage(pOriginal, w + pBorderX, h + pBorderY, null); // Bottom/right
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Illegal edge operation " + mEdgeCondition);
|
||||
throw new IllegalArgumentException("Illegal edge operation " + edgeCondition);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -206,39 +208,39 @@ public class ConvolveWithEdgeOp implements BufferedImageOp, RasterOp {
|
||||
* @see #EDGE_WRAP
|
||||
*/
|
||||
public int getEdgeCondition() {
|
||||
return mEdgeCondition;
|
||||
return edgeCondition;
|
||||
}
|
||||
|
||||
public WritableRaster filter(final Raster pSource, final WritableRaster pDestination) {
|
||||
return mConvolve.filter(pSource, pDestination);
|
||||
return convolve.filter(pSource, pDestination);
|
||||
}
|
||||
|
||||
public BufferedImage createCompatibleDestImage(final BufferedImage pSource, final ColorModel pDesinationColorModel) {
|
||||
return mConvolve.createCompatibleDestImage(pSource, pDesinationColorModel);
|
||||
return convolve.createCompatibleDestImage(pSource, pDesinationColorModel);
|
||||
}
|
||||
|
||||
public WritableRaster createCompatibleDestRaster(final Raster pSource) {
|
||||
return mConvolve.createCompatibleDestRaster(pSource);
|
||||
return convolve.createCompatibleDestRaster(pSource);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(final BufferedImage pSource) {
|
||||
return mConvolve.getBounds2D(pSource);
|
||||
return convolve.getBounds2D(pSource);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(final Raster pSource) {
|
||||
return mConvolve.getBounds2D(pSource);
|
||||
return convolve.getBounds2D(pSource);
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(final Point2D pSourcePoint, final Point2D pDestinationPoint) {
|
||||
return mConvolve.getPoint2D(pSourcePoint, pDestinationPoint);
|
||||
return convolve.getPoint2D(pSourcePoint, pDestinationPoint);
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
return mConvolve.getRenderingHints();
|
||||
return convolve.getRenderingHints();
|
||||
}
|
||||
|
||||
public Kernel getKernel() {
|
||||
return mConvolve.getKernel();
|
||||
return convolve.getKernel();
|
||||
}
|
||||
|
||||
}
|
||||
+14
-25
@@ -51,7 +51,7 @@ import java.awt.image.WritableRaster;
|
||||
*/
|
||||
public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
|
||||
protected IndexColorModel mIndexColorModel = null;
|
||||
protected IndexColorModel indexColorModel = null;
|
||||
|
||||
/**
|
||||
* Creates a {@code CopyDither}, using the given
|
||||
@@ -61,7 +61,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
*/
|
||||
public CopyDither(IndexColorModel pICM) {
|
||||
// Store colormodel
|
||||
mIndexColorModel = pICM;
|
||||
indexColorModel = pICM;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,17 +83,12 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
|
||||
* an instance of {@code IndexColorModel}.
|
||||
*/
|
||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
|
||||
ColorModel pDestCM) {
|
||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
|
||||
if (pDestCM == null) {
|
||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED,
|
||||
mIndexColorModel);
|
||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, indexColorModel);
|
||||
}
|
||||
else if (pDestCM instanceof IndexColorModel) {
|
||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED,
|
||||
(IndexColorModel) pDestCM);
|
||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, (IndexColorModel) pDestCM);
|
||||
}
|
||||
else {
|
||||
throw new ImageFilterException("Only IndexColorModel allowed.");
|
||||
@@ -112,13 +107,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
||||
}
|
||||
|
||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
|
||||
IndexColorModel pIndexColorModel) {
|
||||
/*
|
||||
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED,
|
||||
pIndexColorModel).getRaster();
|
||||
*/
|
||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
|
||||
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
|
||||
}
|
||||
|
||||
@@ -207,8 +196,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
* @return the destination image, or a new image, if {@code pDest} was
|
||||
* {@code null}.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage pSource,
|
||||
BufferedImage pDest) {
|
||||
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
|
||||
// Create destination image, if none provided
|
||||
if (pDest == null) {
|
||||
pDest = createCompatibleDestImage(pSource, getICM(pSource));
|
||||
@@ -238,16 +226,17 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
}
|
||||
|
||||
private IndexColorModel getICM(BufferedImage pSource) {
|
||||
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
|
||||
return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY));
|
||||
}
|
||||
|
||||
private IndexColorModel getICM(Raster pSource) {
|
||||
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
|
||||
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
|
||||
}
|
||||
|
||||
private IndexColorModel createIndexColorModel(Raster pSource) {
|
||||
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage image = new BufferedImage(pSource.getWidth(), pSource.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
image.setData(pSource);
|
||||
|
||||
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK | IndexImage.COLOR_SELECTION_QUALITY);
|
||||
}
|
||||
|
||||
@@ -261,8 +250,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||
* {@code null}.
|
||||
*/
|
||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
|
||||
IndexColorModel pColorModel) {
|
||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
|
||||
int width = pSource.getWidth();
|
||||
int height = pSource.getHeight();
|
||||
|
||||
@@ -292,6 +280,7 @@ public class CopyDither implements BufferedImageOp, RasterOp {
|
||||
pDest.setDataElements(x, y, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
return pDest;
|
||||
}
|
||||
}
|
||||
+73
-78
@@ -17,7 +17,7 @@ import java.util.Random;
|
||||
* This {@code BufferedImageOp/RasterOp} implements basic
|
||||
* Floyd-Steinberg error-diffusion algorithm for dithering.
|
||||
* <P/>
|
||||
* The weights used are 7/16 3/16 5/16 1/16, distributed like this:
|
||||
* The weights used are 7/16, 3/16, 5/16 and 1/16, distributed like this:
|
||||
* <!-- - -
|
||||
* | |x|7|
|
||||
* - - - -
|
||||
@@ -36,45 +36,47 @@ import java.util.Random;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/DiffusionDither.java#1 $
|
||||
*
|
||||
* @version $Id: DiffusionDither.java#1 $
|
||||
*/
|
||||
public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
|
||||
protected IndexColorModel mIndexColorModel = null;
|
||||
private boolean mAlternateScans = true;
|
||||
private static final int FS_SCALE = 1 << 8;
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
protected final IndexColorModel indexColorModel;
|
||||
private boolean alternateScans = true;
|
||||
|
||||
/**
|
||||
* Creates a {@code DiffusionDither}, using the given
|
||||
* {@code IndexColorModel} for dithering into.
|
||||
*
|
||||
* @param pICM an IndexColorModel.
|
||||
*/
|
||||
public DiffusionDither(IndexColorModel pICM) {
|
||||
// Store colormodel
|
||||
mIndexColorModel = pICM;
|
||||
public DiffusionDither(final IndexColorModel pICM) {
|
||||
// Store color model
|
||||
indexColorModel = pICM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code DiffusionDither}, with no fixed
|
||||
* {@code IndexColorModel}. The colormodel will be generated for each
|
||||
* filtering, unless the dest image allready has an
|
||||
* {@code IndexColorModel}. The color model will be generated for each
|
||||
* filtering, unless the destination image already has an
|
||||
* {@code IndexColorModel}.
|
||||
*/
|
||||
public DiffusionDither() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scan mode. If the parameter is true, error distribution for
|
||||
* every even line will be left-to-right, while odd lines will be
|
||||
* right-to-left.
|
||||
* The default is {@code true}.
|
||||
*
|
||||
* @param pUse {@code true} if scan mode should be alternating left/right
|
||||
*/
|
||||
public void setAlternateScans(boolean pUse) {
|
||||
mAlternateScans = pUse;
|
||||
alternateScans = pUse;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,8 +88,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
* @throws ImageFilterException if {@code pDestCM} is not {@code null} or
|
||||
* an instance of {@code IndexColorModel}.
|
||||
*/
|
||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource,
|
||||
ColorModel pDestCM) {
|
||||
public final BufferedImage createCompatibleDestImage(BufferedImage pSource, ColorModel pDestCM) {
|
||||
if (pDestCM == null) {
|
||||
return new BufferedImage(pSource.getWidth(), pSource.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED,
|
||||
@@ -107,7 +108,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
* Creates a compatible {@code Raster} to dither into.
|
||||
* Only {@code IndexColorModel} allowed.
|
||||
*
|
||||
* @param pSrc
|
||||
* @param pSrc the source raster
|
||||
*
|
||||
* @return a {@code WritableRaster}
|
||||
*/
|
||||
@@ -115,14 +116,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
return createCompatibleDestRaster(pSrc, getICM(pSrc));
|
||||
}
|
||||
|
||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc,
|
||||
IndexColorModel pIndexColorModel) {
|
||||
/**
|
||||
* Creates a compatible {@code Raster} to dither into.
|
||||
*
|
||||
* @param pSrc the source raster.
|
||||
* @param pIndexColorModel the index color model used to create a {@code Raster}.
|
||||
*
|
||||
* @return a {@code WritableRaster}
|
||||
*/
|
||||
public final WritableRaster createCompatibleDestRaster(Raster pSrc, IndexColorModel pIndexColorModel) {
|
||||
return pIndexColorModel.createCompatibleWritableRaster(pSrc.getWidth(), pSrc.getHeight());
|
||||
/*
|
||||
return new BufferedImage(pSrc.getWidth(), pSrc.getHeight(),
|
||||
BufferedImage.TYPE_BYTE_INDEXED,
|
||||
pIndexColorModel).getRaster();
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -216,13 +219,12 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
* Floyd-Steinberg error-diffusion to the image.
|
||||
*
|
||||
* @param pSource the source image
|
||||
* @param pDest the destiantion image
|
||||
* @param pDest the destination image
|
||||
*
|
||||
* @return the destination image, or a new image, if {@code pDest} was
|
||||
* {@code null}.
|
||||
*/
|
||||
public final BufferedImage filter(BufferedImage pSource,
|
||||
BufferedImage pDest) {
|
||||
public final BufferedImage filter(BufferedImage pSource, BufferedImage pDest) {
|
||||
// Create destination image, if none provided
|
||||
if (pDest == null) {
|
||||
pDest = createCompatibleDestImage(pSource, getICM(pSource));
|
||||
@@ -241,8 +243,8 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
* Performs a single-input/single-output dither operation, applying basic
|
||||
* Floyd-Steinberg error-diffusion to the image.
|
||||
*
|
||||
* @param pSource
|
||||
* @param pDest
|
||||
* @param pSource the source raster, assumed to be in sRGB
|
||||
* @param pDest the destination raster, may be {@code null}
|
||||
*
|
||||
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||
* {@code null}.
|
||||
@@ -252,10 +254,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
}
|
||||
|
||||
private IndexColorModel getICM(BufferedImage pSource) {
|
||||
return (mIndexColorModel != null ? mIndexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
|
||||
return (indexColorModel != null ? indexColorModel : IndexImage.getIndexColorModel(pSource, 256, IndexImage.TRANSPARENCY_BITMASK));
|
||||
}
|
||||
private IndexColorModel getICM(Raster pSource) {
|
||||
return (mIndexColorModel != null ? mIndexColorModel : createIndexColorModel(pSource));
|
||||
return (indexColorModel != null ? indexColorModel : createIndexColorModel(pSource));
|
||||
}
|
||||
|
||||
private IndexColorModel createIndexColorModel(Raster pSource) {
|
||||
@@ -265,21 +267,18 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
return IndexImage.getIndexColorModel(image, 256, IndexImage.TRANSPARENCY_BITMASK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Performs a single-input/single-output dither operation, applying basic
|
||||
* Floyd-Steinberg error-diffusion to the image.
|
||||
*
|
||||
* @param pSource
|
||||
* @param pDest
|
||||
* @param pColorModel
|
||||
* @param pSource the source raster, assumed to be in sRGB
|
||||
* @param pDest the destination raster, may be {@code null}
|
||||
* @param pColorModel the indexed color model to use
|
||||
*
|
||||
* @return the destination raster, or a new raster, if {@code pDest} was
|
||||
* {@code null}.
|
||||
*/
|
||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest,
|
||||
IndexColorModel pColorModel) {
|
||||
public final WritableRaster filter(final Raster pSource, WritableRaster pDest, IndexColorModel pColorModel) {
|
||||
int width = pSource.getWidth();
|
||||
int height = pSource.getHeight();
|
||||
|
||||
@@ -293,20 +292,15 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
// When reference for column, add 1 to reference as this buffer is
|
||||
// offset from actual column position by one to allow FS to not check
|
||||
// left/right edge conditions
|
||||
int[][] mCurrErr = new int[width + 2][3];
|
||||
int[][] mNextErr = new int[width + 2][3];
|
||||
int[][] currErr = new int[width + 2][3];
|
||||
int[][] nextErr = new int[width + 2][3];
|
||||
|
||||
// Random errors in [-1 .. 1] - for first row
|
||||
for (int i = 0; i < width + 2; i++) {
|
||||
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
|
||||
/*
|
||||
mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||
mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||
mCurrErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||
*/
|
||||
mCurrErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
mCurrErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
mCurrErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||
}
|
||||
|
||||
// Temp buffers
|
||||
@@ -319,10 +313,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
// Loop through image data
|
||||
for (int y = 0; y < height; y++) {
|
||||
// Clear out next error rows for colour errors
|
||||
for (int i = mNextErr.length; --i >= 0;) {
|
||||
mNextErr[i][0] = 0;
|
||||
mNextErr[i][1] = 0;
|
||||
mNextErr[i][2] = 0;
|
||||
for (int i = nextErr.length; --i >= 0;) {
|
||||
nextErr[i][0] = 0;
|
||||
nextErr[i][1] = 0;
|
||||
nextErr[i][2] = 0;
|
||||
}
|
||||
|
||||
// Set up start column and limit
|
||||
@@ -349,7 +343,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// Make a 28.4 FP number, add Error (with fraction),
|
||||
// rounding and truncate to int
|
||||
inRGB[i] = ((inRGB[i] << 4) + mCurrErr[x + 1][i] + 0x08) >> 4;
|
||||
inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4;
|
||||
|
||||
// Clamp
|
||||
if (inRGB[i] > 255) {
|
||||
@@ -385,26 +379,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
if (forward) {
|
||||
// Row 1 (y)
|
||||
// Update error in this pixel (x + 1)
|
||||
mCurrErr[x + 2][0] += diff[0] * 7;
|
||||
mCurrErr[x + 2][1] += diff[1] * 7;
|
||||
mCurrErr[x + 2][2] += diff[2] * 7;
|
||||
currErr[x + 2][0] += diff[0] * 7;
|
||||
currErr[x + 2][1] += diff[1] * 7;
|
||||
currErr[x + 2][2] += diff[2] * 7;
|
||||
|
||||
// Row 2 (y + 1)
|
||||
// Update error in this pixel (x - 1)
|
||||
mNextErr[x][0] += diff[0] * 3;
|
||||
mNextErr[x][1] += diff[1] * 3;
|
||||
mNextErr[x][2] += diff[2] * 3;
|
||||
nextErr[x][0] += diff[0] * 3;
|
||||
nextErr[x][1] += diff[1] * 3;
|
||||
nextErr[x][2] += diff[2] * 3;
|
||||
// Update error in this pixel (x)
|
||||
mNextErr[x + 1][0] += diff[0] * 5;
|
||||
mNextErr[x + 1][1] += diff[1] * 5;
|
||||
mNextErr[x + 1][2] += diff[2] * 5;
|
||||
nextErr[x + 1][0] += diff[0] * 5;
|
||||
nextErr[x + 1][1] += diff[1] * 5;
|
||||
nextErr[x + 1][2] += diff[2] * 5;
|
||||
// Update error in this pixel (x + 1)
|
||||
// TODO: Consider calculating this using
|
||||
// error term = error - sum(error terms 1, 2 and 3)
|
||||
// See Computer Graphics (Foley et al.), p. 573
|
||||
mNextErr[x + 2][0] += diff[0]; // * 1;
|
||||
mNextErr[x + 2][1] += diff[1]; // * 1;
|
||||
mNextErr[x + 2][2] += diff[2]; // * 1;
|
||||
nextErr[x + 2][0] += diff[0]; // * 1;
|
||||
nextErr[x + 2][1] += diff[1]; // * 1;
|
||||
nextErr[x + 2][2] += diff[2]; // * 1;
|
||||
|
||||
// Next
|
||||
x++;
|
||||
@@ -418,26 +412,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
else {
|
||||
// Row 1 (y)
|
||||
// Update error in this pixel (x - 1)
|
||||
mCurrErr[x][0] += diff[0] * 7;
|
||||
mCurrErr[x][1] += diff[1] * 7;
|
||||
mCurrErr[x][2] += diff[2] * 7;
|
||||
currErr[x][0] += diff[0] * 7;
|
||||
currErr[x][1] += diff[1] * 7;
|
||||
currErr[x][2] += diff[2] * 7;
|
||||
|
||||
// Row 2 (y + 1)
|
||||
// Update error in this pixel (x + 1)
|
||||
mNextErr[x + 2][0] += diff[0] * 3;
|
||||
mNextErr[x + 2][1] += diff[1] * 3;
|
||||
mNextErr[x + 2][2] += diff[2] * 3;
|
||||
nextErr[x + 2][0] += diff[0] * 3;
|
||||
nextErr[x + 2][1] += diff[1] * 3;
|
||||
nextErr[x + 2][2] += diff[2] * 3;
|
||||
// Update error in this pixel (x)
|
||||
mNextErr[x + 1][0] += diff[0] * 5;
|
||||
mNextErr[x + 1][1] += diff[1] * 5;
|
||||
mNextErr[x + 1][2] += diff[2] * 5;
|
||||
nextErr[x + 1][0] += diff[0] * 5;
|
||||
nextErr[x + 1][1] += diff[1] * 5;
|
||||
nextErr[x + 1][2] += diff[2] * 5;
|
||||
// Update error in this pixel (x - 1)
|
||||
// TODO: Consider calculating this using
|
||||
// error term = error - sum(error terms 1, 2 and 3)
|
||||
// See Computer Graphics (Foley et al.), p. 573
|
||||
mNextErr[x][0] += diff[0]; // * 1;
|
||||
mNextErr[x][1] += diff[1]; // * 1;
|
||||
mNextErr[x][2] += diff[2]; // * 1;
|
||||
nextErr[x][0] += diff[0]; // * 1;
|
||||
nextErr[x][1] += diff[1]; // * 1;
|
||||
nextErr[x][2] += diff[2]; // * 1;
|
||||
|
||||
// Previous
|
||||
x--;
|
||||
@@ -451,15 +445,16 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
||||
|
||||
// Make next error info current for next iteration
|
||||
int[][] temperr;
|
||||
temperr = mCurrErr;
|
||||
mCurrErr = mNextErr;
|
||||
mNextErr = temperr;
|
||||
temperr = currErr;
|
||||
currErr = nextErr;
|
||||
nextErr = temperr;
|
||||
|
||||
// Toggle direction
|
||||
if (mAlternateScans) {
|
||||
if (alternateScans) {
|
||||
forward = !forward;
|
||||
}
|
||||
}
|
||||
|
||||
return pDest;
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -48,8 +48,8 @@ public class GrayFilter extends RGBImageFilter {
|
||||
canFilterIndexColorModel = true;
|
||||
}
|
||||
|
||||
private int mLow = 0;
|
||||
private float mRange = 1.0f;
|
||||
private int low = 0;
|
||||
private float range = 1.0f;
|
||||
|
||||
/**
|
||||
* Constructs a GrayFilter using ITU color-conversion.
|
||||
@@ -82,8 +82,8 @@ public class GrayFilter extends RGBImageFilter {
|
||||
pHigh = 1f;
|
||||
}
|
||||
|
||||
mLow = (int) (pLow * 255f);
|
||||
mRange = pHigh - pLow;
|
||||
low = (int) (pLow * 255f);
|
||||
range = pHigh - pLow;
|
||||
|
||||
}
|
||||
|
||||
@@ -118,9 +118,9 @@ public class GrayFilter extends RGBImageFilter {
|
||||
|
||||
//int gray = (int) ((float) (r + g + b) / 3.0f);
|
||||
|
||||
if (mRange != 1.0f) {
|
||||
if (range != 1.0f) {
|
||||
// Apply range
|
||||
gray = mLow + (int) (gray * mRange);
|
||||
gray = low + (int) (gray * range);
|
||||
}
|
||||
|
||||
// Return ARGB pixel
|
||||
+7
-29
@@ -32,42 +32,20 @@ package com.twelvemonkeys.image;
|
||||
* This class wraps IllegalArgumentException as thrown by the
|
||||
* BufferedImageOp interface for more fine-grained control.
|
||||
*
|
||||
* @author Harald Kuhr
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageFilterException.java#1 $
|
||||
*/
|
||||
public class ImageFilterException extends IllegalArgumentException {
|
||||
private Throwable mCause = null;
|
||||
|
||||
public ImageFilterException(String pStr) {
|
||||
super(pStr);
|
||||
public ImageFilterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ImageFilterException(Throwable pT) {
|
||||
initCause(pT);
|
||||
public ImageFilterException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ImageFilterException(String pStr, Throwable pT) {
|
||||
super(pStr);
|
||||
initCause(pT);
|
||||
}
|
||||
|
||||
public Throwable initCause(Throwable pThrowable) {
|
||||
if (mCause != null) {
|
||||
// May only be called once
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
else if (pThrowable == this) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
mCause = pThrowable;
|
||||
|
||||
// Hmmm...
|
||||
return this;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return mCause;
|
||||
public ImageFilterException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
+81
-163
@@ -32,19 +32,17 @@ import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.*;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* This class contains methods for basic image manipulation and conversion.
|
||||
*
|
||||
* @todo Split palette generation out, into ColorModel classes.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
|
||||
* @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
|
||||
*/
|
||||
public final class ImageUtil {
|
||||
// TODO: Split palette generation out, into ColorModel classes (?)
|
||||
|
||||
public final static int ROTATE_90_CCW = -90;
|
||||
public final static int ROTATE_90_CW = 90;
|
||||
@@ -59,12 +57,14 @@ public final class ImageUtil {
|
||||
* @see #EDGE_REFLECT
|
||||
*/
|
||||
public static final int EDGE_ZERO_FILL = ConvolveOp.EDGE_ZERO_FILL;
|
||||
|
||||
/**
|
||||
* Alias for {@link ConvolveOp#EDGE_NO_OP}.
|
||||
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
|
||||
* @see #EDGE_REFLECT
|
||||
*/
|
||||
public static final int EDGE_NO_OP = ConvolveOp.EDGE_NO_OP;
|
||||
|
||||
/**
|
||||
* Adds a border to the image while convolving. The border will reflect the
|
||||
* edges of the original image. This is usually a good default.
|
||||
@@ -74,6 +74,7 @@ public final class ImageUtil {
|
||||
* @see #convolve(java.awt.image.BufferedImage, java.awt.image.Kernel, int)
|
||||
*/
|
||||
public static final int EDGE_REFLECT = 2; // as JAI BORDER_REFLECT
|
||||
|
||||
/**
|
||||
* Adds a border to the image while convolving. The border will wrap the
|
||||
* edges of the original image. This is usually the best choice for tiles.
|
||||
@@ -174,19 +175,12 @@ public final class ImageUtil {
|
||||
|
||||
/** Our static image tracker */
|
||||
private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT);
|
||||
//private static Object sTrackerMutex = new Object();
|
||||
|
||||
/** Image id used by the image tracker */
|
||||
//private static int sTrackerId = 0;
|
||||
|
||||
/** */
|
||||
protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
|
||||
/** */
|
||||
protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
|
||||
|
||||
/** */
|
||||
private static final boolean COLORMODEL_TRANSFERTYPE_SUPPORTED = isColorModelTransferTypeSupported();
|
||||
|
||||
/** */
|
||||
private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration();
|
||||
|
||||
@@ -208,28 +202,12 @@ public final class ImageUtil {
|
||||
private ImageUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if {@code ColorModel} has a {@code getTransferType} method.
|
||||
*
|
||||
* @return {@code true} if {@code ColorModel} has a
|
||||
* {@code getTransferType} method
|
||||
*/
|
||||
private static boolean isColorModelTransferTypeSupported() {
|
||||
try {
|
||||
ColorModel.getRGBdefault().getTransferType();
|
||||
return true;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the {@code RenderedImage} to a {@code BufferedImage}.
|
||||
* The new image will have the <em>same</em> {@code ColorModel},
|
||||
* {@code Raster} and properties as the original image, if possible.
|
||||
* <p/>
|
||||
* If the image is allready a {@code BufferedImage}, it is simply returned
|
||||
* If the image is already a {@code BufferedImage}, it is simply returned
|
||||
* and no conversion takes place.
|
||||
*
|
||||
* @param pOriginal the image to convert.
|
||||
@@ -237,7 +215,7 @@ public final class ImageUtil {
|
||||
* @return a {@code BufferedImage}
|
||||
*/
|
||||
public static BufferedImage toBuffered(RenderedImage pOriginal) {
|
||||
// Don't convert if it allready is a BufferedImage
|
||||
// Don't convert if it already is a BufferedImage
|
||||
if (pOriginal instanceof BufferedImage) {
|
||||
return (BufferedImage) pOriginal;
|
||||
}
|
||||
@@ -283,7 +261,7 @@ public final class ImageUtil {
|
||||
* Converts the {@code RenderedImage} to a {@code BufferedImage} of the
|
||||
* given type.
|
||||
* <p/>
|
||||
* If the image is allready a {@code BufferedImage} of the given type, it
|
||||
* If the image is already a {@code BufferedImage} of the given type, it
|
||||
* is simply returned and no conversion takes place.
|
||||
*
|
||||
* @param pOriginal the image to convert.
|
||||
@@ -297,7 +275,7 @@ public final class ImageUtil {
|
||||
* @see java.awt.image.BufferedImage#getType()
|
||||
*/
|
||||
public static BufferedImage toBuffered(RenderedImage pOriginal, int pType) {
|
||||
// Don't convert if it allready is BufferedImage and correct type
|
||||
// Don't convert if it already is BufferedImage and correct type
|
||||
if ((pOriginal instanceof BufferedImage) && ((BufferedImage) pOriginal).getType() == pType) {
|
||||
return (BufferedImage) pOriginal;
|
||||
}
|
||||
@@ -329,7 +307,7 @@ public final class ImageUtil {
|
||||
* given type. The new image will have the same {@code ColorModel},
|
||||
* {@code Raster} and properties as the original image, if possible.
|
||||
* <p/>
|
||||
* If the image is allready a {@code BufferedImage} of the given type, it
|
||||
* If the image is already a {@code BufferedImage} of the given type, it
|
||||
* is simply returned and no conversion takes place.
|
||||
* <p/>
|
||||
* This method simply invokes
|
||||
@@ -354,7 +332,7 @@ public final class ImageUtil {
|
||||
* The new image will have the same {@code ColorModel}, {@code Raster} and
|
||||
* properties as the original image, if possible.
|
||||
* <p/>
|
||||
* If the image is allready a {@code BufferedImage}, it is simply returned
|
||||
* If the image is already a {@code BufferedImage}, it is simply returned
|
||||
* and no conversion takes place.
|
||||
*
|
||||
* @param pOriginal the image to convert.
|
||||
@@ -365,7 +343,7 @@ public final class ImageUtil {
|
||||
* @throws ImageConversionException if the image cannot be converted
|
||||
*/
|
||||
public static BufferedImage toBuffered(Image pOriginal) {
|
||||
// Don't convert if it allready is BufferedImage
|
||||
// Don't convert if it already is BufferedImage
|
||||
if (pOriginal instanceof BufferedImage) {
|
||||
return (BufferedImage) pOriginal;
|
||||
}
|
||||
@@ -381,7 +359,7 @@ public final class ImageUtil {
|
||||
|
||||
/**
|
||||
* Creates a copy of the given image. The image will have the same
|
||||
* colormodel and raster type, but will not share image (pixel) data.
|
||||
* color model and raster type, but will not share image (pixel) data.
|
||||
*
|
||||
* @param pImage the image to clone.
|
||||
*
|
||||
@@ -411,11 +389,11 @@ public final class ImageUtil {
|
||||
* <p/>
|
||||
* This method is optimized for the most common cases of {@code ColorModel}
|
||||
* and pixel data combinations. The raster's backing {@code DataBuffer} is
|
||||
* created directly from the pixel data, as this is faster and with more
|
||||
* created directly from the pixel data, as this is faster and more
|
||||
* resource-friendly than using
|
||||
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
|
||||
* <p/>
|
||||
* For unknown combinations, the method will fallback to using
|
||||
* For uncommon combinations, the method will fallback to using
|
||||
* {@code ColorModel.createCompatibleWritableRaster(w, h)} and
|
||||
* {@code WritableRaster.setDataElements(w, h, pixels)}
|
||||
* <p/>
|
||||
@@ -441,8 +419,8 @@ public final class ImageUtil {
|
||||
*/
|
||||
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
|
||||
// NOTE: This is optimized code for most common cases.
|
||||
// We create a DataBuffer with the array from grabber.getPixels()
|
||||
// directly, and creating a raster based on the ColorModel.
|
||||
// We create a DataBuffer from the pixel array directly,
|
||||
// and creating a raster based on the DataBuffer and ColorModel.
|
||||
// Creating rasters this way is faster and more resource-friendly, as
|
||||
// cm.createCompatibleWritableRaster allocates an
|
||||
// "empty" DataBuffer with a storage array of w*h. This array is
|
||||
@@ -456,14 +434,12 @@ public final class ImageUtil {
|
||||
if (pPixels instanceof int[]) {
|
||||
int[] data = (int[]) pPixels;
|
||||
buffer = new DataBufferInt(data, data.length);
|
||||
//bands = data.length / (w * h);
|
||||
bands = pColorModel.getNumComponents();
|
||||
}
|
||||
else if (pPixels instanceof short[]) {
|
||||
short[] data = (short[]) pPixels;
|
||||
buffer = new DataBufferUShort(data, data.length);
|
||||
bands = data.length / (pWidth * pHeight);
|
||||
//bands = cm.getNumComponents();
|
||||
}
|
||||
else if (pPixels instanceof byte[]) {
|
||||
byte[] data = (byte[]) pPixels;
|
||||
@@ -476,47 +452,30 @@ public final class ImageUtil {
|
||||
else {
|
||||
bands = data.length / (pWidth * pHeight);
|
||||
}
|
||||
|
||||
//bands = pColorModel.getNumComponents();
|
||||
//System.out.println("Pixels: " + data.length + " (" + buffer.getSize() + ")");
|
||||
//System.out.println("w*h*bands: " + (pWidth * pHeight * bands));
|
||||
//System.out.println("Bands: " + bands);
|
||||
//System.out.println("Numcomponents: " + pColorModel.getNumComponents());
|
||||
}
|
||||
else {
|
||||
//System.out.println("Fallback!");
|
||||
// Fallback mode, slower & requires more memory, but compatible
|
||||
bands = -1;
|
||||
|
||||
// Create raster from colormodel, w and h
|
||||
// Create raster from color model, w and h
|
||||
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
||||
raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
|
||||
}
|
||||
|
||||
//System.out.println("Bands: " + bands);
|
||||
//System.out.println("Pixels: " + pixels.getClass() + " length: " + buffer.getSize());
|
||||
//System.out.println("Needed Raster: " + cm.createCompatibleWritableRaster(1, 1));
|
||||
|
||||
if (raster == null) {
|
||||
//int bits = cm.getPixelSize();
|
||||
//if (bits > 4) {
|
||||
if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
|
||||
//System.out.println("Creating packed indexed model");
|
||||
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
|
||||
}
|
||||
else if (pColorModel instanceof PackedColorModel) {
|
||||
//System.out.println("Creating packed model");
|
||||
PackedColorModel pcm = (PackedColorModel) pColorModel;
|
||||
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
|
||||
}
|
||||
else {
|
||||
//System.out.println("Creating interleaved model");
|
||||
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
|
||||
int[] bandsOffsets = new int[bands];
|
||||
for (int i = 0; i < bands;) {
|
||||
bandsOffsets[i] = bands - (++i);
|
||||
}
|
||||
//System.out.println("zzz Data array: " + buffer.getSize());
|
||||
|
||||
raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT);
|
||||
}
|
||||
@@ -536,32 +495,45 @@ public final class ImageUtil {
|
||||
*
|
||||
* @param pOriginal the orignal image
|
||||
* @param pModel the original color model
|
||||
* @param mWidth the requested width of the raster
|
||||
* @param mHeight the requested height of the raster
|
||||
* @param width the requested width of the raster
|
||||
* @param height the requested height of the raster
|
||||
*
|
||||
* @return a new WritableRaster
|
||||
*/
|
||||
static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int mWidth, int mHeight) {
|
||||
static WritableRaster createCompatibleWritableRaster(BufferedImage pOriginal, ColorModel pModel, int width, int height) {
|
||||
if (pModel == null || equals(pOriginal.getColorModel(), pModel)) {
|
||||
int[] bOffs;
|
||||
switch (pOriginal.getType()) {
|
||||
case BufferedImage.TYPE_3BYTE_BGR:
|
||||
int[] bOffs = {2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
|
||||
bOffs = new int[]{2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
|
||||
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
||||
mWidth, mHeight,
|
||||
mWidth * 3, 3,
|
||||
width, height,
|
||||
width * 3, 3,
|
||||
bOffs, null);
|
||||
case BufferedImage.TYPE_4BYTE_ABGR:
|
||||
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
|
||||
bOffs = new int[] {3, 2, 1, 0}; // NOTE: These are reversed from what the cm.createCompatibleWritableRaster would return
|
||||
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
|
||||
mWidth, mHeight,
|
||||
mWidth * 4, 4,
|
||||
width, height,
|
||||
width * 4, 4,
|
||||
bOffs, null);
|
||||
case BufferedImage.TYPE_CUSTOM:
|
||||
// Peek into the sample model to see if we have a sample model that will be incompatible with the default case
|
||||
SampleModel sm = pOriginal.getRaster().getSampleModel();
|
||||
if (sm instanceof ComponentSampleModel) {
|
||||
bOffs = ((ComponentSampleModel) sm).getBandOffsets();
|
||||
return Raster.createInterleavedRaster(sm.getDataType(),
|
||||
width, height,
|
||||
width * bOffs.length, bOffs.length,
|
||||
bOffs, null);
|
||||
}
|
||||
// Else fall through
|
||||
default:
|
||||
return pOriginal.getColorModel().createCompatibleWritableRaster(mWidth, mHeight);
|
||||
return pOriginal.getColorModel().createCompatibleWritableRaster(width, height);
|
||||
}
|
||||
}
|
||||
return pModel.createCompatibleWritableRaster(mWidth, mHeight);
|
||||
|
||||
return pModel.createCompatibleWritableRaster(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -569,7 +541,7 @@ public final class ImageUtil {
|
||||
* The new image will have the same {@code ColorModel}, {@code Raster} and
|
||||
* properties as the original image, if possible.
|
||||
* <p/>
|
||||
* If the image is allready a {@code BufferedImage} of the given type, it
|
||||
* If the image is already a {@code BufferedImage} of the given type, it
|
||||
* is simply returned and no conversion takes place.
|
||||
*
|
||||
* @param pOriginal the image to convert.
|
||||
@@ -597,7 +569,7 @@ public final class ImageUtil {
|
||||
* the color model
|
||||
*/
|
||||
private static BufferedImage toBuffered(Image pOriginal, int pType, IndexColorModel pICM) {
|
||||
// Don't convert if it allready is BufferedImage and correct type
|
||||
// Don't convert if it already is BufferedImage and correct type
|
||||
if ((pOriginal instanceof BufferedImage)
|
||||
&& ((BufferedImage) pOriginal).getType() == pType
|
||||
&& (pICM == null || equals(((BufferedImage) pOriginal).getColorModel(), pICM))) {
|
||||
@@ -784,7 +756,7 @@ public final class ImageUtil {
|
||||
* Creates a scaled instance of the given {@code Image}, and converts it to
|
||||
* a {@code BufferedImage} if needed.
|
||||
* If the original image is a {@code BufferedImage} the result will have
|
||||
* same type and colormodel. Note that this implies overhead, and is
|
||||
* same type and color model. Note that this implies overhead, and is
|
||||
* probably not useful for anything but {@code IndexColorModel} images.
|
||||
*
|
||||
* @param pImage the {@code Image} to scale
|
||||
@@ -820,7 +792,7 @@ public final class ImageUtil {
|
||||
|
||||
BufferedImage scaled = createResampled(pImage, pWidth, pHeight, pHints);
|
||||
|
||||
// Convert if colormodels or type differ, to behave as documented
|
||||
// Convert if color models or type differ, to behave as documented
|
||||
if (type != scaled.getType() && type != BI_TYPE_ANY || !equals(scaled.getColorModel(), cm)) {
|
||||
//System.out.print("Converting TYPE " + scaled.getType() + " -> " + type + "... ");
|
||||
//long start = System.currentTimeMillis();
|
||||
@@ -835,11 +807,13 @@ public final class ImageUtil {
|
||||
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
|
||||
// TODO: DiffusionDither does not support transparency at the moment, this will create bad results
|
||||
new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
|
||||
}
|
||||
else {
|
||||
drawOnto(temp, scaled);
|
||||
}
|
||||
|
||||
scaled = temp;
|
||||
//long end = System.currentTimeMillis();
|
||||
//System.out.println("Time: " + (end - start) + " ms");
|
||||
@@ -965,9 +939,6 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
private static int convertAWTHints(int pHints) {
|
||||
// TODO: These conversions are broken!
|
||||
// box == area average
|
||||
// point == replicate (or..?)
|
||||
switch (pHints) {
|
||||
case Image.SCALE_FAST:
|
||||
case Image.SCALE_REPLICATE:
|
||||
@@ -1129,26 +1100,26 @@ public final class ImageUtil {
|
||||
* Sharpens an image using a convolution matrix.
|
||||
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
|
||||
* <TABLE border="1" cellspacing="0">
|
||||
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
|
||||
* <TR><TD>-{@code pAmmount}</TD>
|
||||
* <TD>4.0 * {@code pAmmount} + 1.0</TD>
|
||||
* <TD>-{@code pAmmount}</TD></TR>
|
||||
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
|
||||
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
|
||||
* <TR><TD>-{@code pAmount}</TD>
|
||||
* <TD>4.0 * {@code pAmount} + 1.0</TD>
|
||||
* <TD>-{@code pAmount}</TD></TR>
|
||||
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
|
||||
* </TABLE>
|
||||
*
|
||||
* @param pOriginal the BufferedImage to sharpen
|
||||
* @param pAmmount the ammount of sharpening
|
||||
* @param pAmount the amount of sharpening
|
||||
*
|
||||
* @return a BufferedImage, containing the sharpened image.
|
||||
*/
|
||||
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmmount) {
|
||||
if (pAmmount == 0f) {
|
||||
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) {
|
||||
if (pAmount == 0f) {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
// Create the convolution matrix
|
||||
float[] data = new float[] {
|
||||
0.0f, -pAmmount, 0.0f, -pAmmount, 4f * pAmmount + 1f, -pAmmount, 0.0f, -pAmmount, 0.0f
|
||||
0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f
|
||||
};
|
||||
|
||||
// Do the filtering
|
||||
@@ -1174,7 +1145,7 @@ public final class ImageUtil {
|
||||
* Creates a blurred version of the given image.
|
||||
*
|
||||
* @param pOriginal the original image
|
||||
* @param pRadius the ammount to blur
|
||||
* @param pRadius the amount to blur
|
||||
*
|
||||
* @return a new {@code BufferedImage} with a blurred version of the given image
|
||||
*/
|
||||
@@ -1187,18 +1158,18 @@ public final class ImageUtil {
|
||||
// See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation
|
||||
// Also see http://www.jhlabs.com/ip/blurring.html
|
||||
|
||||
// TODO: Rethink... Fixed ammount and scale matrix instead?
|
||||
// pAmmount = 1f - pAmmount;
|
||||
// float pAmmount = 1f - pRadius;
|
||||
// TODO: Rethink... Fixed amount and scale matrix instead?
|
||||
// pAmount = 1f - pAmount;
|
||||
// float pAmount = 1f - pRadius;
|
||||
//
|
||||
// // Normalize ammount
|
||||
// float normAmt = (1f - pAmmount) / 24;
|
||||
// // Normalize amount
|
||||
// float normAmt = (1f - pAmount) / 24;
|
||||
//
|
||||
// // Create the convolution matrix
|
||||
// float[] data = new float[] {
|
||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
|
||||
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
||||
// normAmt, normAmt * 2, pAmmount, normAmt * 2, normAmt,
|
||||
// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt,
|
||||
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
|
||||
// };
|
||||
@@ -1380,18 +1351,18 @@ public final class ImageUtil {
|
||||
* Changes the contrast of the image
|
||||
*
|
||||
* @param pOriginal the {@code Image} to change
|
||||
* @param pAmmount the ammount of contrast in the range [-1.0..1.0].
|
||||
* @param pAmount the amount of contrast in the range [-1.0..1.0].
|
||||
*
|
||||
* @return an {@code Image}, containing the contrasted image.
|
||||
*/
|
||||
public static Image contrast(Image pOriginal, float pAmmount) {
|
||||
public static Image contrast(Image pOriginal, float pAmount) {
|
||||
// No change, return original
|
||||
if (pAmmount == 0f) {
|
||||
if (pAmount == 0f) {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
// Create filter
|
||||
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmmount);
|
||||
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount);
|
||||
|
||||
// Return contrast adjusted image
|
||||
return filter(pOriginal, filter);
|
||||
@@ -1402,18 +1373,18 @@ public final class ImageUtil {
|
||||
* Changes the brightness of the original image.
|
||||
*
|
||||
* @param pOriginal the {@code Image} to change
|
||||
* @param pAmmount the ammount of brightness in the range [-2.0..2.0].
|
||||
* @param pAmount the amount of brightness in the range [-2.0..2.0].
|
||||
*
|
||||
* @return an {@code Image}
|
||||
*/
|
||||
public static Image brightness(Image pOriginal, float pAmmount) {
|
||||
public static Image brightness(Image pOriginal, float pAmount) {
|
||||
// No change, return original
|
||||
if (pAmmount == 0f) {
|
||||
if (pAmount == 0f) {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
// Create filter
|
||||
RGBImageFilter filter = new BrightnessContrastFilter(pAmmount, 0f);
|
||||
RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f);
|
||||
|
||||
// Return brightness adjusted image
|
||||
return filter(pOriginal, filter);
|
||||
@@ -1454,7 +1425,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
||||
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||
* Note that transparent parts of the image might be replaced by solid
|
||||
* color. Additional image information not used by the current diplay
|
||||
* hardware may be discarded, like extra bith depth etc.
|
||||
@@ -1467,7 +1438,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
||||
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||
* Note that transparent parts of the image might be replaced by solid
|
||||
* color. Additional image information not used by the current diplay
|
||||
* hardware may be discarded, like extra bith depth etc.
|
||||
@@ -1483,7 +1454,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
||||
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||
* Note that transparent parts of the image will be replaced by solid
|
||||
* color. Additional image information not used by the current diplay
|
||||
* hardware may be discarded, like extra bith depth etc.
|
||||
@@ -1773,7 +1744,7 @@ public final class ImageUtil {
|
||||
* @param pTimeOut the time to wait, in milliseconds.
|
||||
*
|
||||
* @return true if the image was loaded successfully, false if an error
|
||||
* occured, or the wait was interrupted.
|
||||
* occurred, or the wait was interrupted.
|
||||
*
|
||||
* @see #waitForImages(Image[],long)
|
||||
*/
|
||||
@@ -1788,7 +1759,7 @@ public final class ImageUtil {
|
||||
* @param pImages an array of Image objects to wait for.
|
||||
*
|
||||
* @return true if the images was loaded successfully, false if an error
|
||||
* occured, or the wait was interrupted.
|
||||
* occurred, or the wait was interrupted.
|
||||
*
|
||||
* @see #waitForImages(Image[],long)
|
||||
*/
|
||||
@@ -1804,7 +1775,7 @@ public final class ImageUtil {
|
||||
* @param pTimeOut the time to wait, in milliseconds
|
||||
*
|
||||
* @return true if the images was loaded successfully, false if an error
|
||||
* occured, or the wait was interrupted.
|
||||
* occurred, or the wait was interrupted.
|
||||
*/
|
||||
public static boolean waitForImages(Image[] pImages, long pTimeOut) {
|
||||
// TODO: Need to make sure that we don't wait for the same image many times
|
||||
@@ -1814,13 +1785,6 @@ public final class ImageUtil {
|
||||
// Create a local id for use with the mediatracker
|
||||
int imageId;
|
||||
|
||||
// NOTE: The synchronization throws IllegalMonitorStateException if
|
||||
// using JIT on J2SE 1.2 (tested version Sun JRE 1.2.2_017).
|
||||
// Works perfectly interpreted... Hmmm...
|
||||
//synchronized (sTrackerMutex) {
|
||||
//imageId = ++sTrackerId;
|
||||
//}
|
||||
|
||||
// NOTE: This is very experimental...
|
||||
imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages);
|
||||
|
||||
@@ -1866,7 +1830,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests wether the image has any transparent or semi-transparent pixels.
|
||||
* Tests whether the image has any transparent or semi-transparent pixels.
|
||||
*
|
||||
* @param pImage the image
|
||||
* @param pFast if {@code true}, the method tests maximum 10 x 10 pixels,
|
||||
@@ -1934,7 +1898,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blends two ARGB values half and half, to create a tone inbetween.
|
||||
* Blends two ARGB values half and half, to create a tone in between.
|
||||
*
|
||||
* @param pRGB1 color 1
|
||||
* @param pRGB2 color 2
|
||||
@@ -1947,7 +1911,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blends two colors half and half, to create a tone inbetween.
|
||||
* Blends two colors half and half, to create a tone in between.
|
||||
*
|
||||
* @param pColor color 1
|
||||
* @param pOther color 2
|
||||
@@ -1965,7 +1929,7 @@ public final class ImageUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Blends two colors, controlled by the blendfactor.
|
||||
* Blends two colors, controlled by the blending factor.
|
||||
* A factor of {@code 0.0} will return the first color,
|
||||
* a factor of {@code 1.0} will return the second.
|
||||
*
|
||||
@@ -1987,50 +1951,4 @@ public final class ImageUtil {
|
||||
private static int clamp(float f) {
|
||||
return (int) f;
|
||||
}
|
||||
/**
|
||||
* PixelGrabber subclass that stores any potential properties from an image.
|
||||
*/
|
||||
/*
|
||||
private static class MyPixelGrabber extends PixelGrabber {
|
||||
private Hashtable mProps = null;
|
||||
|
||||
public MyPixelGrabber(Image pImage) {
|
||||
// Simply grab all pixels, do not convert to default RGB space
|
||||
super(pImage, 0, 0, -1, -1, false);
|
||||
}
|
||||
|
||||
// Default implementation does not store the properties...
|
||||
public void setProperties(Hashtable pProps) {
|
||||
super.setProperties(pProps);
|
||||
mProps = pProps;
|
||||
}
|
||||
|
||||
public Hashtable getProperties() {
|
||||
return mProps;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the transfer type from the given {@code ColorModel}.
|
||||
* <p/>
|
||||
* NOTE: This is a workaround for missing functionality in JDK 1.2.
|
||||
*
|
||||
* @param pModel the color model
|
||||
* @return the transfer type
|
||||
*
|
||||
* @throws NullPointerException if {@code pModel} is {@code null}.
|
||||
*
|
||||
* @see java.awt.image.ColorModel#getTransferType()
|
||||
*/
|
||||
public static int getTransferType(ColorModel pModel) {
|
||||
if (COLORMODEL_TRANSFERTYPE_SUPPORTED) {
|
||||
return pModel.getTransferType();
|
||||
}
|
||||
else {
|
||||
// Stupid workaround
|
||||
// TODO: Create something that performs better
|
||||
return pModel.createCompatibleSampleModel(1, 1).getDataType();
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
-105
@@ -89,12 +89,14 @@ import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class implements an adaptive pallete generator to reduce images
|
||||
* This class implements an adaptive palette generator to reduce images
|
||||
* to a variable number of colors.
|
||||
* It can also render images into fixed color pallettes.
|
||||
* <p/>
|
||||
@@ -127,7 +129,7 @@ import java.util.List;
|
||||
* @author <A href="mailto:deweese@apache.org">Thomas DeWeese</A>
|
||||
* @author <A href="mailto:jun@oop-reserch.com">Jun Inamori</A>
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/IndexImage.java#1 $
|
||||
* @version $Id: IndexImage.java#1 $
|
||||
* @see DiffusionDither
|
||||
*/
|
||||
class IndexImage {
|
||||
@@ -237,19 +239,22 @@ class IndexImage {
|
||||
if (this.val != val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to define a cube of the colorspace. The cube can be split
|
||||
* approximagely in half to generate two cubes.
|
||||
* Used to define a cube of the color space. The cube can be split
|
||||
* approximately in half to generate two cubes.
|
||||
*/
|
||||
private static class Cube {
|
||||
int[] min = {0, 0, 0}, max = {255, 255, 255};
|
||||
int[] min = {0, 0, 0};
|
||||
int[] max = {255, 255, 255};
|
||||
boolean done = false;
|
||||
List[] colors = null;
|
||||
List<Counter>[] colors = null;
|
||||
int count = 0;
|
||||
static final int RED = 0;
|
||||
static final int GRN = 1;
|
||||
@@ -261,7 +266,7 @@ class IndexImage {
|
||||
* @param colors contains the 3D color histogram to be subdivided
|
||||
* @param count the total number of pixels in the 3D histogram.
|
||||
*/
|
||||
public Cube(List[] colors, int count) {
|
||||
public Cube(List<Counter>[] colors, int count) {
|
||||
this.colors = colors;
|
||||
this.count = count;
|
||||
}
|
||||
@@ -312,20 +317,27 @@ class IndexImage {
|
||||
c0 = RED;
|
||||
c1 = GRN;
|
||||
}
|
||||
|
||||
Cube ret;
|
||||
|
||||
ret = splitChannel(splitChannel, c0, c1);
|
||||
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = splitChannel(c0, splitChannel, c1);
|
||||
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = splitChannel(c1, splitChannel, c0);
|
||||
|
||||
if (ret != null) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
done = true;
|
||||
|
||||
return null;
|
||||
@@ -381,16 +393,13 @@ class IndexImage {
|
||||
|
||||
for (int k = minIdx[c1]; k <= maxIdx[c1]; k++) {
|
||||
int idx = idx2 | (k << c1Sh4);
|
||||
List v = colors[idx];
|
||||
List<Counter> v = colors[idx];
|
||||
|
||||
if (v == null) {
|
||||
continue;
|
||||
}
|
||||
Iterator itr = v.iterator();
|
||||
Counter c;
|
||||
|
||||
while (itr.hasNext()) {
|
||||
c = (Counter) itr.next();
|
||||
|
||||
for (Counter c : v) {
|
||||
val = c.val;
|
||||
vals[0] = (val & 0xFF0000) >> 16;
|
||||
vals[1] = (val & 0xFF00) >> 8;
|
||||
@@ -425,7 +434,6 @@ class IndexImage {
|
||||
int c = counts[i];
|
||||
|
||||
if (c == 0) {
|
||||
|
||||
// No counts below this so move up bottom of cube.
|
||||
if ((tcount == 0) && (i < max[splitChannel])) {
|
||||
this.min[splitChannel] = i + 1;
|
||||
@@ -438,10 +446,8 @@ class IndexImage {
|
||||
continue;
|
||||
}
|
||||
if ((half - tcount) <= ((tcount + c) - half)) {
|
||||
|
||||
// Then lastAdd is a better top idx for this then i.
|
||||
if (lastAdd == -1) {
|
||||
|
||||
// No lower place to break.
|
||||
if (c == this.count) {
|
||||
|
||||
@@ -465,13 +471,11 @@ class IndexImage {
|
||||
else {
|
||||
if (i == this.max[splitChannel]) {
|
||||
if (c == this.count) {
|
||||
|
||||
// would move min up but that should
|
||||
// have happened already.
|
||||
return null;// no split to make.
|
||||
}
|
||||
else {
|
||||
|
||||
// Would like to break between i and i+1
|
||||
// but no i+1 so use lastAdd and i;
|
||||
splitLo = lastAdd;
|
||||
@@ -503,6 +507,7 @@ class IndexImage {
|
||||
ret.max[c0] = this.max[c0];
|
||||
ret.min[c1] = this.min[c1];
|
||||
ret.max[c1] = this.max[c1];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -515,6 +520,7 @@ class IndexImage {
|
||||
if (this.count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
float red = 0, grn = 0, blu = 0;
|
||||
int minR = min[0], minG = min[1], minB = min[2];
|
||||
int maxR = max[0], maxG = max[1], maxB = max[2];
|
||||
@@ -531,20 +537,18 @@ class IndexImage {
|
||||
|
||||
for (int k = minIdx[2]; k <= maxIdx[2]; k++) {
|
||||
int idx = idx2 | k;
|
||||
List v = colors[idx];
|
||||
List<Counter> v = colors[idx];
|
||||
|
||||
if (v == null) {
|
||||
continue;
|
||||
}
|
||||
Iterator itr = v.iterator();
|
||||
Counter c;
|
||||
|
||||
while (itr.hasNext()) {
|
||||
c = (Counter) itr.next();
|
||||
for (Counter c : v) {
|
||||
val = c.val;
|
||||
ired = (val & 0xFF0000) >> 16;
|
||||
igrn = (val & 0x00FF00) >> 8;
|
||||
iblu = (val & 0x0000FF);
|
||||
|
||||
if (((ired >= minR) && (ired <= maxR)) && ((igrn >= minG) && (igrn <= maxG)) && ((iblu >= minB) && (iblu <= maxB))) {
|
||||
weight = (c.count / (float) this.count);
|
||||
red += ((float) ired) * weight;
|
||||
@@ -579,16 +583,13 @@ class IndexImage {
|
||||
* This version will be removed in a later version of the API.
|
||||
*/
|
||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
||||
|
||||
return getIndexColorModel(pImage, pNumberOfColors, pFast
|
||||
? COLOR_SELECTION_FAST
|
||||
: COLOR_SELECTION_QUALITY);
|
||||
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an {@code IndexColorModel} from the given image. If the image has an
|
||||
* {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel}
|
||||
* is created, using an adaptive pallete.
|
||||
* is created, using an adaptive palette.
|
||||
*
|
||||
* @param pImage the image to get {@code IndexColorModel} from
|
||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
||||
@@ -636,23 +637,18 @@ class IndexImage {
|
||||
// We now have at least a buffered image, create model from it
|
||||
if (icm == null) {
|
||||
icm = createIndexColorModel(ImageUtil.toBuffered(image), pNumberOfColors, pHints);
|
||||
|
||||
//System.out.println("IndexColorModel created from colors.");
|
||||
}
|
||||
else if (!(icm instanceof InverseColorMapIndexColorModel)) {
|
||||
// If possible, use faster code
|
||||
//System.out.println("Wrappimg IndexColorModel in InverseColorMapIndexColorModel");
|
||||
icm = new InverseColorMapIndexColorModel(icm);
|
||||
}
|
||||
//else {
|
||||
//System.out.println("Allredy InverseColorMapIndexColorModel");
|
||||
//}
|
||||
|
||||
return icm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code IndexColorModel} from the given image, using an adaptive
|
||||
* pallete.
|
||||
* palette.
|
||||
*
|
||||
* @param pImage the image to get {@code IndexColorModel} from
|
||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
||||
@@ -674,7 +670,8 @@ class IndexImage {
|
||||
int height = pImage.getHeight();
|
||||
|
||||
// Using 4 bits from R, G & B.
|
||||
List[] colors = new List[1 << 12];// [4096]
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Counter>[] colors = new List[1 << 12];// [4096]
|
||||
|
||||
// Speedup, doesn't decrease image quality much
|
||||
int step = 1;
|
||||
@@ -739,13 +736,16 @@ class IndexImage {
|
||||
while (numberOfCubes < pNumberOfColors) {
|
||||
while (cubes[fCube].isDone()) {
|
||||
fCube++;
|
||||
|
||||
if (fCube == numberOfCubes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fCube == numberOfCubes) {
|
||||
break;
|
||||
}
|
||||
|
||||
Cube cube = cubes[fCube];
|
||||
Cube newCube = cube.split();
|
||||
|
||||
@@ -756,6 +756,7 @@ class IndexImage {
|
||||
cube = newCube;
|
||||
newCube = tmp;
|
||||
}
|
||||
|
||||
int j = fCube;
|
||||
int count = cube.count;
|
||||
|
||||
@@ -765,17 +766,19 @@ class IndexImage {
|
||||
}
|
||||
cubes[j++] = cubes[i];
|
||||
}
|
||||
|
||||
cubes[j++] = cube;
|
||||
count = newCube.count;
|
||||
|
||||
while (j < numberOfCubes) {
|
||||
if (cubes[j].count < count) {
|
||||
break;
|
||||
}
|
||||
j++;
|
||||
}
|
||||
for (int i = numberOfCubes; i > j; i--) {
|
||||
cubes[i] = cubes[i - 1];
|
||||
}
|
||||
|
||||
System.arraycopy(cubes, j, cubes, j + 1, numberOfCubes - j);
|
||||
|
||||
cubes[j/*++*/] = newCube;
|
||||
numberOfCubes++;
|
||||
}
|
||||
@@ -803,15 +806,13 @@ class IndexImage {
|
||||
// - transparency added to all totally black colors?
|
||||
int numOfBits = 8;
|
||||
|
||||
// -- haraldK, 20021024, as suggested by Thomas E Deweese
|
||||
// -- haraldK, 20021024, as suggested by Thomas E. Deweese
|
||||
// plus adding a transparent pixel
|
||||
IndexColorModel icm;
|
||||
if (useTransparency) {
|
||||
//icm = new IndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
|
||||
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b, r.length - 1);
|
||||
}
|
||||
else {
|
||||
//icm = new IndexColorModel(numOfBits, r.length, r, g, b);
|
||||
icm = new InverseColorMapIndexColorModel(numOfBits, r.length, r, g, b);
|
||||
}
|
||||
return icm;
|
||||
@@ -820,7 +821,7 @@ class IndexImage {
|
||||
/**
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||
* pallete (8 bit) from the color data in the image, and uses default
|
||||
* palette (8 bit) from the color data in the image, and uses default
|
||||
* dither.
|
||||
* <p/>
|
||||
* The image returned is a new image, the input image is not modified.
|
||||
@@ -864,7 +865,7 @@ class IndexImage {
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
||||
* adaptive pallete (8 bit) from the given palette image.
|
||||
* adaptive palette (8 bit) from the given palette image.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints}parameter.
|
||||
* <p/>
|
||||
@@ -874,7 +875,7 @@ class IndexImage {
|
||||
* @param pPalette the Image to read color information from
|
||||
* @param pMatte the background color, used where the original image was
|
||||
* transparent
|
||||
* @param pHints mHints that control output quality and speed.
|
||||
* @param pHints hints that control output quality and speed.
|
||||
* @return the indexed BufferedImage. The image will be of type
|
||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||
@@ -899,7 +900,7 @@ class IndexImage {
|
||||
/**
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||
* pallete with the given number of colors.
|
||||
* palette with the given number of colors.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints}parameter.
|
||||
* <p/>
|
||||
@@ -909,7 +910,7 @@ class IndexImage {
|
||||
* @param pNumberOfColors the number of colors for the image
|
||||
* @param pMatte the background color, used where the original image was
|
||||
* transparent
|
||||
* @param pHints mHints that control output quality and speed.
|
||||
* @param pHints hints that control output quality and speed.
|
||||
* @return the indexed BufferedImage. The image will be of type
|
||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||
@@ -925,7 +926,7 @@ class IndexImage {
|
||||
* @see IndexColorModel
|
||||
*/
|
||||
public static BufferedImage getIndexedImage(BufferedImage pImage, int pNumberOfColors, Color pMatte, int pHints) {
|
||||
// NOTE: We need to apply matte before creating colormodel, otherwise we
|
||||
// NOTE: We need to apply matte before creating color model, otherwise we
|
||||
// won't have colors for potential faded transitions
|
||||
IndexColorModel icm;
|
||||
|
||||
@@ -946,7 +947,7 @@ class IndexImage {
|
||||
/**
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
||||
* {@code IndexColorModel}'s pallete.
|
||||
* {@code IndexColorModel}'s palette.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints} parameter.
|
||||
* <p/>
|
||||
@@ -985,15 +986,16 @@ class IndexImage {
|
||||
final int width = pImage.getWidth();
|
||||
final int height = pImage.getHeight();
|
||||
|
||||
// Support transparancy?
|
||||
// Support transparency?
|
||||
boolean transparency = isTransparent(pHints) && (pImage.getColorModel().getTransparency() != Transparency.OPAQUE) && (pColors.getTransparency() != Transparency.OPAQUE);
|
||||
|
||||
// Create image with solid background
|
||||
BufferedImage solid = pImage;
|
||||
|
||||
if (pMatte != null) {// transparency doesn't really matter
|
||||
if (pMatte != null) { // transparency doesn't really matter
|
||||
solid = createSolid(pImage, pMatte);
|
||||
}
|
||||
|
||||
BufferedImage indexed;
|
||||
|
||||
// Support TYPE_BYTE_BINARY, but only for 2 bit images, as the default
|
||||
@@ -1044,12 +1046,12 @@ class IndexImage {
|
||||
finally {
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Transparency support, this approach seems lame, but it's the only
|
||||
// solution I've found until now (that actually works).
|
||||
// Got anything to do with isPremultiplied? Hmm...
|
||||
if (transparency) {
|
||||
// Re-apply the alpha-channel of the original image
|
||||
applyAlpha(indexed, pImage);
|
||||
@@ -1062,7 +1064,7 @@ class IndexImage {
|
||||
/**
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||
* pallete with the given number of colors.
|
||||
* palette with the given number of colors.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints}parameter.
|
||||
* <p/>
|
||||
@@ -1070,7 +1072,7 @@ class IndexImage {
|
||||
*
|
||||
* @param pImage the BufferedImage to index
|
||||
* @param pNumberOfColors the number of colors for the image
|
||||
* @param pHints mHints that control output quality and speed.
|
||||
* @param pHints hints that control output quality and speed.
|
||||
* @return the indexed BufferedImage. The image will be of type
|
||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||
@@ -1092,7 +1094,7 @@ class IndexImage {
|
||||
/**
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
||||
* {@code IndexColorModel}'s pallete.
|
||||
* {@code IndexColorModel}'s palette.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints}parameter.
|
||||
* <p/>
|
||||
@@ -1123,7 +1125,7 @@ class IndexImage {
|
||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
||||
* adaptive pallete (8 bit) from the given palette image.
|
||||
* adaptive palette (8 bit) from the given palette image.
|
||||
* Dithering, transparency and color selection is controlled with the
|
||||
* {@code pHints}parameter.
|
||||
* <p/>
|
||||
@@ -1131,7 +1133,7 @@ class IndexImage {
|
||||
*
|
||||
* @param pImage the BufferedImage to index
|
||||
* @param pPalette the Image to read color information from
|
||||
* @param pHints mHints that control output quality and speed.
|
||||
* @param pHints hints that control output quality and speed.
|
||||
* @return the indexed BufferedImage. The image will be of type
|
||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||
@@ -1183,14 +1185,14 @@ class IndexImage {
|
||||
* @param pAlpha the image containing the alpha
|
||||
*/
|
||||
private static void applyAlpha(BufferedImage pImage, BufferedImage pAlpha) {
|
||||
// Apply alpha as transparancy, using threshold of 25%
|
||||
// Apply alpha as transparency, using threshold of 25%
|
||||
for (int y = 0; y < pAlpha.getHeight(); y++) {
|
||||
for (int x = 0; x < pAlpha.getWidth(); x++) {
|
||||
|
||||
// Get alpha component of pixel, if less than 25% opaque
|
||||
// (0x40 = 64 => 25% of 256), the pixel will be transparent
|
||||
if (((pAlpha.getRGB(x, y) >> 24) & 0xFF) < 0x40) {
|
||||
pImage.setRGB(x, y, 0x00FFFFFF);// 100% transparent
|
||||
pImage.setRGB(x, y, 0x00FFFFFF); // 100% transparent
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1200,7 +1202,6 @@ class IndexImage {
|
||||
* This class is also a command-line utility.
|
||||
*/
|
||||
public static void main(String pArgs[]) {
|
||||
|
||||
// Defaults
|
||||
int argIdx = 0;
|
||||
int speedTest = -1;
|
||||
@@ -1237,14 +1238,13 @@ class IndexImage {
|
||||
speedTest = 10;
|
||||
}
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'w') || pArgs[argIdx].equals("--overwrite")) {
|
||||
overWrite = true;
|
||||
argIdx++;
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--colors")) {
|
||||
argIdx++;
|
||||
|
||||
try {
|
||||
numColors = Integer.parseInt(pArgs[argIdx++]);
|
||||
}
|
||||
@@ -1253,34 +1253,28 @@ class IndexImage {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'g') || pArgs[argIdx].equals("--grayscale")) {
|
||||
argIdx++;
|
||||
gray = true;
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'm') || pArgs[argIdx].equals("--monochrome")) {
|
||||
argIdx++;
|
||||
numColors = 2;
|
||||
monochrome = true;
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--dither")) {
|
||||
argIdx++;
|
||||
dither = pArgs[argIdx++];
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--palette")) {
|
||||
argIdx++;
|
||||
paletteFileName = pArgs[argIdx++];
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'q') || pArgs[argIdx].equals("--quality")) {
|
||||
argIdx++;
|
||||
quality = pArgs[argIdx++];
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'b') || pArgs[argIdx].equals("--bgcolor")) {
|
||||
argIdx++;
|
||||
try {
|
||||
background = StringUtil.toColor(pArgs[argIdx++]);
|
||||
@@ -1290,18 +1284,15 @@ class IndexImage {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 't') || pArgs[argIdx].equals("--transparency")) {
|
||||
argIdx++;
|
||||
transparency = true;
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'f') || pArgs[argIdx].equals("--outputformat")) {
|
||||
argIdx++;
|
||||
format = StringUtil.toLowerCase(pArgs[argIdx++]);
|
||||
}
|
||||
else
|
||||
if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
|
||||
else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) {
|
||||
argIdx++;
|
||||
|
||||
// Setting errArgs to true, to print usage
|
||||
@@ -1321,6 +1312,7 @@ class IndexImage {
|
||||
? ", "
|
||||
: "\n"));
|
||||
}
|
||||
|
||||
System.err.print("Output format names: ");
|
||||
String[] writers = ImageIO.getWriterFormatNames();
|
||||
|
||||
@@ -1333,7 +1325,7 @@ class IndexImage {
|
||||
}
|
||||
|
||||
// Read in image
|
||||
java.io.File in = new java.io.File(pArgs[argIdx++]);
|
||||
File in = new File(pArgs[argIdx++]);
|
||||
|
||||
if (!in.exists()) {
|
||||
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
||||
@@ -1341,10 +1333,10 @@ class IndexImage {
|
||||
}
|
||||
|
||||
// Read palette if needed
|
||||
java.io.File paletteFile = null;
|
||||
File paletteFile = null;
|
||||
|
||||
if (paletteFileName != null) {
|
||||
paletteFile = new java.io.File(paletteFileName);
|
||||
paletteFile = new File(paletteFileName);
|
||||
if (!paletteFile.exists()) {
|
||||
System.err.println("File \"" + in.getAbsolutePath() + "\" does not exist!");
|
||||
System.exit(5);
|
||||
@@ -1352,10 +1344,10 @@ class IndexImage {
|
||||
}
|
||||
|
||||
// Make sure we can write
|
||||
java.io.File out;
|
||||
File out;
|
||||
|
||||
if (argIdx < pArgs.length) {
|
||||
out = new java.io.File(pArgs[argIdx/*++*/]);
|
||||
out = new File(pArgs[argIdx/*++*/]);
|
||||
|
||||
// Get format from file extension
|
||||
if (format == null) {
|
||||
@@ -1363,7 +1355,6 @@ class IndexImage {
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
// Create new file in current dir, same name + format extension
|
||||
String baseName = FileUtil.getBasename(in);
|
||||
|
||||
@@ -1371,8 +1362,9 @@ class IndexImage {
|
||||
if (format == null) {
|
||||
format = "png";
|
||||
}
|
||||
out = new java.io.File(baseName + '.' + format);
|
||||
out = new File(baseName + '.' + format);
|
||||
}
|
||||
|
||||
if (!overWrite && out.exists()) {
|
||||
System.err.println("The file \"" + out.getAbsolutePath() + "\" allready exists!");
|
||||
System.exit(5);
|
||||
@@ -1396,12 +1388,12 @@ class IndexImage {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (java.io.IOException ioe) {
|
||||
catch (IOException ioe) {
|
||||
ioe.printStackTrace(System.err);
|
||||
System.exit(5);
|
||||
}
|
||||
|
||||
// Create mHints
|
||||
// Create hints
|
||||
int hints = DITHER_DEFAULT;
|
||||
|
||||
if ("DIFFUSION".equalsIgnoreCase(dither)) {
|
||||
@@ -1441,16 +1433,14 @@ class IndexImage {
|
||||
///////////////////////////////
|
||||
// Index
|
||||
long start = 0;
|
||||
long end;
|
||||
|
||||
if (speedTest > 0) {
|
||||
|
||||
// SPEED TESTING
|
||||
System.out.println("Measuring speed!");
|
||||
start = System.currentTimeMillis();
|
||||
|
||||
// END SPEED TESTING
|
||||
}
|
||||
|
||||
BufferedImage indexed;
|
||||
IndexColorModel colors;
|
||||
|
||||
@@ -1459,7 +1449,6 @@ class IndexImage {
|
||||
colors = MonochromeColorModel.getInstance();
|
||||
}
|
||||
else if (gray) {
|
||||
|
||||
//indexed = ImageUtil.toBuffered(ImageUtil.grayscale(image), BufferedImage.TYPE_BYTE_GRAY);
|
||||
image = ImageUtil.toBuffered(ImageUtil.grayscale(image));
|
||||
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
||||
@@ -1470,7 +1459,6 @@ class IndexImage {
|
||||
}
|
||||
}
|
||||
else if (paletteImg != null) {
|
||||
|
||||
// Get palette from image
|
||||
indexed = getIndexedImage(ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB),
|
||||
colors = getIndexColorModel(paletteImg, numColors, hints), background, hints);
|
||||
@@ -1479,12 +1467,10 @@ class IndexImage {
|
||||
image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
|
||||
indexed = getIndexedImage(image, colors = getIndexColorModel(image, numColors, hints), background, hints);
|
||||
}
|
||||
|
||||
if (speedTest > 0) {
|
||||
|
||||
// SPEED TESTING
|
||||
end = System.currentTimeMillis();
|
||||
System.out.println("Color selection + dither: " + (end - start) + " ms");
|
||||
|
||||
System.out.println("Color selection + dither: " + (System.currentTimeMillis() - start) + " ms");
|
||||
// END SPEED TESTING
|
||||
}
|
||||
|
||||
@@ -1494,11 +1480,11 @@ class IndexImage {
|
||||
System.err.println("No writer for format: \"" + format + "\"!");
|
||||
}
|
||||
}
|
||||
catch (java.io.IOException ioe) {
|
||||
catch (IOException ioe) {
|
||||
ioe.printStackTrace(System.err);
|
||||
}
|
||||
if (speedTest > 0) {
|
||||
|
||||
if (speedTest > 0) {
|
||||
// SPEED TESTING
|
||||
System.out.println("Measuring speed!");
|
||||
|
||||
@@ -1513,17 +1499,16 @@ class IndexImage {
|
||||
for (int i = 0; i < speedTest; i++) {
|
||||
start = System.currentTimeMillis();
|
||||
getIndexedImage(image, colors, background, hints);
|
||||
end = System.currentTimeMillis();
|
||||
time += (end - start);
|
||||
time += (System.currentTimeMillis() - start);
|
||||
System.out.print('.');
|
||||
if ((i + 1) % 10 == 0) {
|
||||
System.out.println("\nAverage (after " + (i + 1) + " iterations): " + (time / (i + 1)) + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("\nDither only:");
|
||||
System.out.println("Total time (" + speedTest + " invocations): " + time + "ms");
|
||||
System.out.println("Average: " + time / speedTest + "ms");
|
||||
|
||||
// END SPEED TESTING
|
||||
}
|
||||
}
|
||||
+27
-27
@@ -72,12 +72,12 @@ class InverseColorMap {
|
||||
*/
|
||||
final static int MAXQUANTVAL = 1 << 5;
|
||||
|
||||
byte[] mRGBMapByte;
|
||||
int[] mRGBMapInt;
|
||||
int mNumColors;
|
||||
int mMaxColor;
|
||||
byte[] mInverseRGB; // inverse rgb color map
|
||||
int mTransparentIndex = -1;
|
||||
byte[] rgbMapByte;
|
||||
int[] rgbMapInt;
|
||||
int numColors;
|
||||
int maxColor;
|
||||
byte[] inverseRGB; // inverse rgb color map
|
||||
int transparentIndex = -1;
|
||||
|
||||
/**
|
||||
* @param pRGBColorMap the rgb color map to create inverse color map for.
|
||||
@@ -99,11 +99,11 @@ class InverseColorMap {
|
||||
* @param pTransparent the index of the transparent pixel in the map
|
||||
*/
|
||||
InverseColorMap(byte[] pRGBColorMap, int pTransparent) {
|
||||
mRGBMapByte = pRGBColorMap;
|
||||
mNumColors = mRGBMapByte.length / 4;
|
||||
mTransparentIndex = pTransparent;
|
||||
rgbMapByte = pRGBColorMap;
|
||||
numColors = rgbMapByte.length / 4;
|
||||
transparentIndex = pTransparent;
|
||||
|
||||
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
|
||||
}
|
||||
|
||||
@@ -112,11 +112,11 @@ class InverseColorMap {
|
||||
* @param pTransparent the index of the transparent pixel in the map
|
||||
*/
|
||||
InverseColorMap(int[] pRGBColorMap, int pTransparent) {
|
||||
mRGBMapInt = pRGBColorMap;
|
||||
mNumColors = mRGBMapInt.length;
|
||||
mTransparentIndex = pTransparent;
|
||||
rgbMapInt = pRGBColorMap;
|
||||
numColors = rgbMapInt.length;
|
||||
transparentIndex = pTransparent;
|
||||
|
||||
mInverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||
inverseRGB = new byte[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL];
|
||||
initIRGB(new int[MAXQUANTVAL * MAXQUANTVAL * MAXQUANTVAL]);
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ class InverseColorMap {
|
||||
final int xsqr = 1 << (TRUNCBITS * 2); // 64 - twice the smallest step size vale of quantized colors
|
||||
final int xsqr2 = xsqr + xsqr;
|
||||
|
||||
for (int i = 0; i < mNumColors; ++i) {
|
||||
if (i == mTransparentIndex) {
|
||||
for (int i = 0; i < numColors; ++i) {
|
||||
if (i == transparentIndex) {
|
||||
// Skip the transparent pixel
|
||||
continue;
|
||||
}
|
||||
@@ -141,15 +141,15 @@ class InverseColorMap {
|
||||
int blue, b, bdist, binc, bxx;
|
||||
|
||||
// HaraldK 20040801: Added support for int[]
|
||||
if (mRGBMapByte != null) {
|
||||
red = mRGBMapByte[i * 4] & 0xFF;
|
||||
green = mRGBMapByte[i * 4 + 1] & 0xFF;
|
||||
blue = mRGBMapByte[i * 4 + 2] & 0xFF;
|
||||
if (rgbMapByte != null) {
|
||||
red = rgbMapByte[i * 4] & 0xFF;
|
||||
green = rgbMapByte[i * 4 + 1] & 0xFF;
|
||||
blue = rgbMapByte[i * 4 + 2] & 0xFF;
|
||||
}
|
||||
else if (mRGBMapInt != null) {
|
||||
red = (mRGBMapInt[i] >> 16) & 0xFF;
|
||||
green = (mRGBMapInt[i] >> 8) & 0xFF;
|
||||
blue = mRGBMapInt[i] & 0xFF;
|
||||
else if (rgbMapInt != null) {
|
||||
red = (rgbMapInt[i] >> 16) & 0xFF;
|
||||
green = (rgbMapInt[i] >> 8) & 0xFF;
|
||||
blue = rgbMapInt[i] & 0xFF;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("colormap == null");
|
||||
@@ -170,7 +170,7 @@ class InverseColorMap {
|
||||
for (b = 0, bdist = gdist, bxx = binc; b < MAXQUANTVAL; bdist += bxx, ++b, ++rgbI, bxx += xsqr2) {
|
||||
if (i == 0 || pTemp[rgbI] > bdist) {
|
||||
pTemp[rgbI] = bdist;
|
||||
mInverseRGB[rgbI] = (byte) i;
|
||||
inverseRGB[rgbI] = (byte) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ class InverseColorMap {
|
||||
* created inverse color map.
|
||||
*/
|
||||
public final int getIndexNearest(int pColor) {
|
||||
return mInverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
|
||||
return inverseRGB[((pColor >> (3 * TRUNCBITS)) & QUANTMASK_RED) +
|
||||
((pColor >> (2 * TRUNCBITS)) & QUANTMASK_GREEN) +
|
||||
((pColor >> (/* 1 * */ TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ class InverseColorMap {
|
||||
*/
|
||||
public final int getIndexNearest(int pRed, int pGreen, int pBlue) {
|
||||
// NOTE: the third line in expression for blue is shifting DOWN not UP.
|
||||
return mInverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
|
||||
return inverseRGB[((pRed << (2 * QUANTBITS - TRUNCBITS)) & QUANTMASK_RED) +
|
||||
((pGreen << (/* 1 * */ QUANTBITS - TRUNCBITS)) & QUANTMASK_GREEN) +
|
||||
((pBlue >> (TRUNCBITS)) & QUANTMASK_BLUE)] & 0xFF;
|
||||
}
|
||||
+40
-47
@@ -30,6 +30,7 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.DataBuffer;
|
||||
@@ -37,7 +38,7 @@ import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* A faster implementation of {@code IndexColorModel}, that is backed by an
|
||||
* inverse color-map, for fast lookups.
|
||||
* inverse color-map, for fast look-ups.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author $Author: haku $
|
||||
@@ -46,13 +47,13 @@ import java.awt.image.IndexColorModel;
|
||||
*/
|
||||
public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
|
||||
protected int mRGBs[];
|
||||
protected int mMapSize;
|
||||
protected int rgbs[];
|
||||
protected int mapSize;
|
||||
|
||||
protected InverseColorMap mInverseMap = null;
|
||||
protected InverseColorMap inverseMap = null;
|
||||
private final static int ALPHA_THRESHOLD = 0x80;
|
||||
|
||||
private int mWhiteIndex = -1;
|
||||
private int whiteIndex = -1;
|
||||
private final static int WHITE = 0x00FFFFFF;
|
||||
private final static int RGB_MASK = 0x00FFFFFF;
|
||||
|
||||
@@ -60,37 +61,36 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
||||
* {@code IndexColorModel}.
|
||||
*
|
||||
* @param pColorModel the colormodel to create from
|
||||
* @param pColorModel the color model to create from.
|
||||
* @throws IllegalArgumentException if {@code pColorModel} is {@code null}
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(IndexColorModel pColorModel) {
|
||||
this(pColorModel, getRGBs(pColorModel));
|
||||
public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) {
|
||||
this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel));
|
||||
}
|
||||
|
||||
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
||||
// times. What is wrong with protected?!
|
||||
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
||||
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(),
|
||||
pRGBs, 0,
|
||||
ImageUtil.getTransferType(pColorModel),
|
||||
pColorModel.getValidPixels());
|
||||
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels());
|
||||
|
||||
mRGBs = pRGBs;
|
||||
mMapSize = mRGBs.length;
|
||||
rgbs = pRGBs;
|
||||
mapSize = rgbs.length;
|
||||
|
||||
mInverseMap = new InverseColorMap(mRGBs);
|
||||
mWhiteIndex = getWhiteIndex();
|
||||
inverseMap = new InverseColorMap(rgbs);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a defensive copy of the RGB colormap in the given
|
||||
* Creates a defensive copy of the RGB color map in the given
|
||||
* {@code IndexColorModel}.
|
||||
*
|
||||
* @param pColorModel the indec colormodel to get RGB values from
|
||||
* @return the RGB colormap
|
||||
* @param pColorModel the indexed color model to get RGB values from
|
||||
* @return the RGB color map
|
||||
*/
|
||||
private static int[] getRGBs(IndexColorModel pColorModel) {
|
||||
int[] rgb = new int[pColorModel.getMapSize()];
|
||||
pColorModel.getRGBs(rgb);
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
@@ -111,15 +111,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, int[], int, boolean, int, int)
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs,
|
||||
int pStart, boolean pAlpha, int pTransparentIndex,
|
||||
int pTransferType) {
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, int[] pRGBs, int pStart, boolean pAlpha, int pTransparentIndex, int pTransferType) {
|
||||
super(pNumBits, pSize, pRGBs, pStart, pAlpha, pTransparentIndex, pTransferType);
|
||||
mRGBs = getRGBs(this);
|
||||
mMapSize = mRGBs.length;
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
|
||||
mWhiteIndex = getWhiteIndex();
|
||||
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,15 +136,13 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[], int)
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
|
||||
byte[] pReds, byte[] pGreens, byte[] pBlues,
|
||||
int pTransparentIndex) {
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues, int pTransparentIndex) {
|
||||
super(pNumBits, pSize, pReds, pGreens, pBlues, pTransparentIndex);
|
||||
mRGBs = getRGBs(this);
|
||||
mMapSize = mRGBs.length;
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
mInverseMap = new InverseColorMap(mRGBs, pTransparentIndex);
|
||||
mWhiteIndex = getWhiteIndex();
|
||||
inverseMap = new InverseColorMap(rgbs, pTransparentIndex);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,19 +160,18 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
*
|
||||
* @see IndexColorModel#IndexColorModel(int, int, byte[], byte[], byte[])
|
||||
*/
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize,
|
||||
byte[] pReds, byte[] pGreens, byte[] pBlues) {
|
||||
public InverseColorMapIndexColorModel(int pNumBits, int pSize, byte[] pReds, byte[] pGreens, byte[] pBlues) {
|
||||
super(pNumBits, pSize, pReds, pGreens, pBlues);
|
||||
mRGBs = getRGBs(this);
|
||||
mMapSize = mRGBs.length;
|
||||
rgbs = getRGBs(this);
|
||||
mapSize = rgbs.length;
|
||||
|
||||
mInverseMap = new InverseColorMap(mRGBs);
|
||||
mWhiteIndex = getWhiteIndex();
|
||||
inverseMap = new InverseColorMap(rgbs);
|
||||
whiteIndex = getWhiteIndex();
|
||||
}
|
||||
|
||||
private int getWhiteIndex() {
|
||||
for (int i = 0; i < mRGBs.length; i++) {
|
||||
int color = mRGBs[i];
|
||||
for (int i = 0; i < rgbs.length; i++) {
|
||||
int color = rgbs[i];
|
||||
if ((color & RGB_MASK) == WHITE) {
|
||||
return i;
|
||||
}
|
||||
@@ -244,7 +239,6 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
*
|
||||
*/
|
||||
public Object getDataElements(int rgb, Object pixel) {
|
||||
|
||||
int alpha = (rgb>>>24);
|
||||
|
||||
int pix;
|
||||
@@ -253,11 +247,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
}
|
||||
else {
|
||||
int color = rgb & RGB_MASK;
|
||||
if (color == WHITE && mWhiteIndex != -1) {
|
||||
pix = mWhiteIndex;
|
||||
if (color == WHITE && whiteIndex != -1) {
|
||||
pix = whiteIndex;
|
||||
}
|
||||
else {
|
||||
pix = mInverseMap.getIndexNearest(color);
|
||||
pix = inverseMap.getIndexNearest(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,8 +291,7 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
||||
shortObj[0] = (short) pix;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("This method has not been " +
|
||||
"implemented for transferType " + transferType);
|
||||
throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType);
|
||||
}
|
||||
return pixel;
|
||||
}
|
||||
+5
-5
@@ -54,11 +54,11 @@ final class MagickAccelerator {
|
||||
|
||||
private static final int RESAMPLE_OP = 0;
|
||||
|
||||
private static Class[] sNativeOp = new Class[1];
|
||||
private static Class[] nativeOp = new Class[1];
|
||||
|
||||
static {
|
||||
try {
|
||||
sNativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
|
||||
nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
System.err.println("Could not find class: " + e);
|
||||
@@ -94,8 +94,8 @@ final class MagickAccelerator {
|
||||
}
|
||||
|
||||
private static int getNativeOpIndex(Class pOpClass) {
|
||||
for (int i = 0; i < sNativeOp.length; i++) {
|
||||
if (pOpClass == sNativeOp[i]) {
|
||||
for (int i = 0; i < nativeOp.length; i++) {
|
||||
if (pOpClass == nativeOp[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ final class MagickAccelerator {
|
||||
switch (getNativeOpIndex(pOperation.getClass())) {
|
||||
case RESAMPLE_OP:
|
||||
ResampleOp resample = (ResampleOp) pOperation;
|
||||
result = resampleMagick(pInput, resample.mWidth, resample.mHeight, resample.mFilterType);
|
||||
result = resampleMagick(pInput, resample.width, resample.height, resample.filterType);
|
||||
|
||||
// NOTE: If output parameter is non-null, we have to return that
|
||||
// image, instead of result
|
||||
+62
@@ -32,6 +32,8 @@ import magick.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
@@ -160,7 +162,11 @@ public final class MagickUtil {
|
||||
image = rgbToBuffered(pImage, true);
|
||||
break;
|
||||
case ImageType.ColorSeparationType:
|
||||
image = cmykToBuffered(pImage, false);
|
||||
break;
|
||||
case ImageType.ColorSeparationMatteType:
|
||||
image = cmykToBuffered(pImage, true);
|
||||
break;
|
||||
case ImageType.OptimizeType:
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
|
||||
@@ -546,4 +552,60 @@ public final class MagickUtil {
|
||||
|
||||
return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile
|
||||
*
|
||||
* @param pImage the original {@code MagickImage}
|
||||
* @param pAlpha keep alpha channel
|
||||
* @return a new {@code BufferedImage}
|
||||
*
|
||||
* @throws MagickException if an exception occurs during conversion
|
||||
*
|
||||
* @see BufferedImage
|
||||
*/
|
||||
private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
|
||||
Dimension size = pImage.getDimension();
|
||||
int length = size.width * size.height;
|
||||
|
||||
// Retreive the ICC profile
|
||||
ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo());
|
||||
ColorSpace cs = new ICC_ColorSpace(profile);
|
||||
|
||||
int bands = cs.getNumComponents() + (pAlpha ? 1 : 0);
|
||||
|
||||
int[] bits = new int[bands];
|
||||
for (int i = 0; i < bands; i++) {
|
||||
bits[i] = 8;
|
||||
}
|
||||
|
||||
ColorModel cm = pAlpha ?
|
||||
new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) :
|
||||
new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
||||
|
||||
byte[] pixels = new byte[length * bands];
|
||||
|
||||
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
|
||||
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
|
||||
// TODO: handle more generic cases if profile is not CMYK
|
||||
// TODO: Test "ACMYK"
|
||||
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels);
|
||||
|
||||
// Init databuffer with array, to avoid allocation of empty array
|
||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
||||
|
||||
// TODO: build array from bands variable, here it just works for CMYK
|
||||
// The values has not been tested with an alpha picture actually...
|
||||
int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3};
|
||||
|
||||
WritableRaster raster =
|
||||
Raster.createInterleavedRaster(buffer, size.width, size.height,
|
||||
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
|
||||
|
||||
return new BufferedImage(cm, raster, pAlpha, null);
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ import java.awt.image.*;
|
||||
/**
|
||||
* Monochrome B/W color model.
|
||||
*
|
||||
* @author Harald Kuhr
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
public class MonochromeColorModel extends IndexColorModel {
|
||||
|
||||
+24
-24
@@ -48,37 +48,37 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
// TODO: support more raster types/color models
|
||||
// TODO: This is actually an implementation of Area Averaging, without the scale... Let's extract it...
|
||||
|
||||
final private int mPixelSizeX;
|
||||
final private int mPixelSizeY;
|
||||
final private int pixelSizeX;
|
||||
final private int pixelSizeY;
|
||||
|
||||
private Rectangle mSourceRegion;
|
||||
private Rectangle sourceRegion;
|
||||
|
||||
public PixelizeOp(final int pPixelSize) {
|
||||
this(pPixelSize, pPixelSize);
|
||||
}
|
||||
|
||||
public PixelizeOp(final int pPixelSizeX, final int pPixelSizeY) {
|
||||
mPixelSizeX = pPixelSizeX;
|
||||
mPixelSizeY = pPixelSizeY;
|
||||
pixelSizeX = pPixelSizeX;
|
||||
pixelSizeY = pPixelSizeY;
|
||||
}
|
||||
|
||||
public Rectangle getSourceRegion() {
|
||||
if (mSourceRegion == null) {
|
||||
if (sourceRegion == null) {
|
||||
return null;
|
||||
}
|
||||
return new Rectangle(mSourceRegion);
|
||||
return new Rectangle(sourceRegion);
|
||||
}
|
||||
|
||||
public void setSourceRegion(final Rectangle pSourceRegion) {
|
||||
if (pSourceRegion == null) {
|
||||
mSourceRegion = null;
|
||||
sourceRegion = null;
|
||||
}
|
||||
else {
|
||||
if (mSourceRegion == null) {
|
||||
mSourceRegion = new Rectangle(pSourceRegion);
|
||||
if (sourceRegion == null) {
|
||||
sourceRegion = new Rectangle(pSourceRegion);
|
||||
}
|
||||
else {
|
||||
mSourceRegion.setBounds(pSourceRegion);
|
||||
sourceRegion.setBounds(pSourceRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,11 +107,11 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
private WritableRaster filterImpl(Raster src, WritableRaster dest) {
|
||||
//System.out.println("src: " + src);
|
||||
//System.out.println("dest: " + dest);
|
||||
if (mSourceRegion != null) {
|
||||
int cx = mSourceRegion.x;
|
||||
int cy = mSourceRegion.y;
|
||||
int cw = mSourceRegion.width;
|
||||
int ch = mSourceRegion.height;
|
||||
if (sourceRegion != null) {
|
||||
int cx = sourceRegion.x;
|
||||
int cy = sourceRegion.y;
|
||||
int cw = sourceRegion.width;
|
||||
int ch = sourceRegion.height;
|
||||
|
||||
boolean same = src == dest;
|
||||
dest = dest.createWritableChild(cx, cy, cw, ch, 0, 0, null);
|
||||
@@ -122,8 +122,8 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
final int width = src.getWidth();
|
||||
final int height = src.getHeight();
|
||||
int w = (width + mPixelSizeX - 1) / mPixelSizeX;
|
||||
int h = (height + mPixelSizeY - 1) / mPixelSizeY;
|
||||
int w = (width + pixelSizeX - 1) / pixelSizeX;
|
||||
int h = (height + pixelSizeY - 1) / pixelSizeY;
|
||||
|
||||
final boolean oddX = width % w != 0;
|
||||
final boolean oddY = height % h != 0;
|
||||
@@ -156,23 +156,23 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
if (!oddY || y + 1 < h) {
|
||||
scanH = mPixelSizeY;
|
||||
scanH = pixelSizeY;
|
||||
}
|
||||
else {
|
||||
scanH = height - (y * mPixelSizeY);
|
||||
scanH = height - (y * pixelSizeY);
|
||||
}
|
||||
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (!oddX || x + 1 < w) {
|
||||
scanW = mPixelSizeX;
|
||||
scanW = pixelSizeX;
|
||||
}
|
||||
else {
|
||||
scanW = width - (x * mPixelSizeX);
|
||||
scanW = width - (x * pixelSizeX);
|
||||
}
|
||||
final int pixelCount = scanW * scanH;
|
||||
final int pixelLength = pixelCount * dataElements;
|
||||
|
||||
data = src.getDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
|
||||
data = src.getDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
|
||||
|
||||
// NOTE: These are not neccessarily ARGB..
|
||||
double valueA = 0.0;
|
||||
@@ -277,7 +277,7 @@ public class PixelizeOp implements BufferedImageOp, RasterOp {
|
||||
|
||||
}
|
||||
|
||||
dest.setDataElements(x * mPixelSizeX, y * mPixelSizeY, scanW, scanH, data);
|
||||
dest.setDataElements(x * pixelSizeX, y * pixelSizeY, scanW, scanH, data);
|
||||
}
|
||||
}
|
||||
/*/
|
||||
+112
-122
@@ -52,15 +52,12 @@
|
||||
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.*;
|
||||
|
||||
|
||||
/**
|
||||
* Resamples (scales) a {@code BufferedImage} to a new width and height, using
|
||||
* high performance and high quality algorithms.
|
||||
@@ -138,7 +135,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
// MagickAccelerator to work consistently (see magick.FilterType).
|
||||
|
||||
/**
|
||||
* Undefined interpolation, filter method will use default filter
|
||||
* Undefined interpolation, filter method will use default filter.
|
||||
*/
|
||||
public final static int FILTER_UNDEFINED = 0;
|
||||
/**
|
||||
@@ -194,11 +191,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
/**
|
||||
* Mitchell interpolation. High quality.
|
||||
*/
|
||||
public final static int FILTER_MITCHELL = 12;// IM default scale with palette or alpha, or scale up
|
||||
public final static int FILTER_MITCHELL = 12; // IM default scale with palette or alpha, or scale up
|
||||
/**
|
||||
* Lanczos interpolation. High quality.
|
||||
*/
|
||||
public final static int FILTER_LANCZOS = 13;// IM default
|
||||
public final static int FILTER_LANCZOS = 13; // IM default
|
||||
/**
|
||||
* Blackman-Bessel interpolation. High quality.
|
||||
*/
|
||||
@@ -291,25 +288,23 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
|
||||
// Member variables
|
||||
// Package access, to allow access from MagickAccelerator
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
int mFilterType;
|
||||
private static final boolean TRANSFORM_OP_BICUBIC_SUPPORT = SystemUtil.isFieldAvailable(AffineTransformOp.class.getName(), "TYPE_BICUBIC");
|
||||
int filterType;
|
||||
|
||||
/**
|
||||
* RendereingHints.Key implementation, works only with Value values.
|
||||
*/
|
||||
// TODO: Move to abstract class AbstractBufferedImageOp?
|
||||
static class Key extends RenderingHints.Key {
|
||||
|
||||
static int sIndex = 10000;
|
||||
|
||||
private final String mName;
|
||||
private final String name;
|
||||
|
||||
public Key(final String pName) {
|
||||
super(sIndex++);
|
||||
mName = pName;
|
||||
name = pName;
|
||||
}
|
||||
|
||||
public boolean isCompatibleValue(Object pValue) {
|
||||
@@ -317,36 +312,35 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return mName;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RenderingHints value implementaion, works with Key keys.
|
||||
* RenderingHints value implementation, works with Key keys.
|
||||
*/
|
||||
// TODO: Extract abstract Value class, and move to AbstractBufferedImageOp
|
||||
static final class Value {
|
||||
final private RenderingHints.Key mKey;
|
||||
final private String mName;
|
||||
final private int mType;
|
||||
final private RenderingHints.Key key;
|
||||
final private String name;
|
||||
final private int type;
|
||||
|
||||
public Value(final RenderingHints.Key pKey, final String pName, final int pType) {
|
||||
mKey = pKey;
|
||||
mName = pName;
|
||||
validateFilterType(pType);
|
||||
mType = pType;// TODO: test for duplicates
|
||||
key = pKey;
|
||||
name = pName;
|
||||
type = validateFilterType(pType);
|
||||
}
|
||||
|
||||
public boolean isCompatibleKey(Key pKey) {
|
||||
return pKey == mKey;
|
||||
return pKey == key;
|
||||
}
|
||||
|
||||
public int getFilterType() {
|
||||
return mType;
|
||||
return type;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return mName;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,11 +348,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
* Creates a {@code ResampleOp} that will resample input images to the
|
||||
* given width and height, using the default interpolation filter.
|
||||
*
|
||||
* @param pWidth width of the resampled image
|
||||
* @param pHeight height of the resampled image
|
||||
* @param width width of the re-sampled image
|
||||
* @param height height of the re-sampled image
|
||||
*/
|
||||
public ResampleOp(int pWidth, int pHeight) {
|
||||
this(pWidth, pHeight, FILTER_UNDEFINED);
|
||||
public ResampleOp(int width, int height) {
|
||||
this(width, height, FILTER_UNDEFINED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,41 +388,40 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
* </ul>
|
||||
* Other hints have no effect on this filter.
|
||||
*
|
||||
* @param pWidth width of the resampled image
|
||||
* @param pHeight height of the resampled image
|
||||
* @param pHints rendering hints, affecting interpolation algorithm
|
||||
* @param width width of the re-sampled image
|
||||
* @param height height of the re-sampled image
|
||||
* @param hints rendering hints, affecting interpolation algorithm
|
||||
* @see #KEY_RESAMPLE_INTERPOLATION
|
||||
* @see RenderingHints#KEY_INTERPOLATION
|
||||
* @see RenderingHints#KEY_RENDERING
|
||||
* @see RenderingHints#KEY_COLOR_RENDERING
|
||||
*/
|
||||
public ResampleOp(int pWidth, int pHeight, RenderingHints pHints) {
|
||||
this(pWidth, pHeight, getFilterType(pHints));
|
||||
public ResampleOp(int width, int height, RenderingHints hints) {
|
||||
this(width, height, getFilterType(hints));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ResampleOp} that will resample input images to the
|
||||
* given width and height, using the given interpolation filter.
|
||||
*
|
||||
* @param pWidth width of the resampled image
|
||||
* @param pHeight height of the resampled image
|
||||
* @param pFilterType interpolation filter algorithm
|
||||
* @param width width of the re-sampled image
|
||||
* @param height height of the re-sampled image
|
||||
* @param filterType interpolation filter algorithm
|
||||
* @see <a href="#field_summary">filter type constants</a>
|
||||
*/
|
||||
public ResampleOp(int pWidth, int pHeight, int pFilterType) {
|
||||
if (pWidth <= 0 || pHeight <= 0) {
|
||||
public ResampleOp(int width, int height, int filterType) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
// NOTE: w/h == 0 makes the Magick DLL crash and the JVM dies.. :-P
|
||||
throw new IllegalArgumentException("width and height must be positive");
|
||||
}
|
||||
|
||||
mWidth = pWidth;
|
||||
mHeight = pHeight;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
validateFilterType(pFilterType);
|
||||
mFilterType = pFilterType;
|
||||
this.filterType = validateFilterType(filterType);
|
||||
}
|
||||
|
||||
private static void validateFilterType(int pFilterType) {
|
||||
private static int validateFilterType(int pFilterType) {
|
||||
switch (pFilterType) {
|
||||
case FILTER_UNDEFINED:
|
||||
case FILTER_POINT:
|
||||
@@ -446,7 +439,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
case FILTER_LANCZOS:
|
||||
case FILTER_BLACKMAN_BESSEL:
|
||||
case FILTER_BLACKMAN_SINC:
|
||||
break;
|
||||
return pFilterType;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown filter type: " + pFilterType);
|
||||
}
|
||||
@@ -471,25 +464,21 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
return value != null ? ((Value) value).getFilterType() : FILTER_UNDEFINED;
|
||||
}
|
||||
else
|
||||
if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|
||||
else if (RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))
|
||||
|| (!pHints.containsKey(RenderingHints.KEY_INTERPOLATION)
|
||||
&& (RenderingHints.VALUE_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
||||
|| RenderingHints.VALUE_COLOR_RENDER_SPEED.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))))) {
|
||||
// Nearest neighbour, or prioritze speed
|
||||
// Nearest neighbour, or prioritize speed
|
||||
return FILTER_POINT;
|
||||
}
|
||||
else
|
||||
if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||
else if (RenderingHints.VALUE_INTERPOLATION_BILINEAR.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||
// Triangle equals bi-linear interpolation
|
||||
return FILTER_TRIANGLE;
|
||||
}
|
||||
else
|
||||
if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||
else if (RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(pHints.get(RenderingHints.KEY_INTERPOLATION))) {
|
||||
return FILTER_QUADRATIC;// No idea if this is correct..?
|
||||
}
|
||||
else
|
||||
if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
||||
else if (RenderingHints.VALUE_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_RENDERING))
|
||||
|| RenderingHints.VALUE_COLOR_RENDER_QUALITY.equals(pHints.get(RenderingHints.KEY_COLOR_RENDERING))) {
|
||||
// Prioritize quality
|
||||
return FILTER_MITCHELL;
|
||||
@@ -500,83 +489,87 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples (scales) the image to the size, and using the algorithm
|
||||
* Re-samples (scales) the image to the size, and using the algorithm
|
||||
* specified in the constructor.
|
||||
*
|
||||
* @param pInput The {@code BufferedImage} to be filtered
|
||||
* @param pOutput The {@code BufferedImage} in which to store the resampled
|
||||
* @param input The {@code BufferedImage} to be filtered
|
||||
* @param output The {@code BufferedImage} in which to store the resampled
|
||||
* image
|
||||
* @return The resampled {@code BufferedImage}.
|
||||
* @throws NullPointerException if {@code pInput} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code pInput == pOutput}.
|
||||
* @return The re-sampled {@code BufferedImage}.
|
||||
* @throws NullPointerException if {@code input} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code input == output}.
|
||||
* @see #ResampleOp(int,int,int)
|
||||
*/
|
||||
public final BufferedImage filter(final BufferedImage pInput, final BufferedImage pOutput) {
|
||||
if (pInput == null) {
|
||||
public final BufferedImage filter(final BufferedImage input, final BufferedImage output) {
|
||||
if (input == null) {
|
||||
throw new NullPointerException("Input == null");
|
||||
}
|
||||
if (pInput == pOutput) {
|
||||
if (input == output) {
|
||||
throw new IllegalArgumentException("Output image cannot be the same as the input image");
|
||||
}
|
||||
|
||||
InterpolationFilter filter;
|
||||
|
||||
|
||||
// Special case for POINT, TRIANGLE and QUADRATIC filter, as standard
|
||||
// Java implementation is very fast (possibly H/W accellerated)
|
||||
switch (mFilterType) {
|
||||
// Java implementation is very fast (possibly H/W accelerated)
|
||||
switch (filterType) {
|
||||
case FILTER_POINT:
|
||||
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||
case FILTER_TRIANGLE:
|
||||
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
|
||||
case FILTER_QUADRATIC:
|
||||
if (TRANSFORM_OP_BICUBIC_SUPPORT) {
|
||||
return fastResample(pInput, pOutput, mWidth, mHeight, 3); // AffineTransformOp.TYPE_BICUBIC
|
||||
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
|
||||
return fastResample(input, output, width, height, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||
}
|
||||
// Fall through
|
||||
// Else fall through
|
||||
case FILTER_TRIANGLE:
|
||||
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
|
||||
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
|
||||
}
|
||||
// Else fall through
|
||||
case FILTER_QUADRATIC:
|
||||
if (input.getType() != BufferedImage.TYPE_CUSTOM) {
|
||||
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BICUBIC);
|
||||
}
|
||||
// Else fall through
|
||||
default:
|
||||
filter = createFilter(mFilterType);
|
||||
filter = createFilter(filterType);
|
||||
// NOTE: Workaround for filter throwing exceptions when input or output is less than support...
|
||||
if (Math.min(pInput.getWidth(), pInput.getHeight()) <= filter.support() || Math.min(mWidth, mHeight) <= filter.support()) {
|
||||
return fastResample(pInput, pOutput, mWidth, mHeight, AffineTransformOp.TYPE_BILINEAR);
|
||||
if (Math.min(input.getWidth(), input.getHeight()) <= filter.support() || Math.min(width, height) <= filter.support()) {
|
||||
return fastResample(input, output, width, height, AffineTransformOp.TYPE_BILINEAR);
|
||||
}
|
||||
// Fall through
|
||||
}
|
||||
|
||||
|
||||
// Try to use native ImageMagick code
|
||||
BufferedImage result = MagickAccelerator.filter(this, pInput, pOutput);
|
||||
BufferedImage result = MagickAccelerator.filter(this, input, output);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Otherwise, continue in pure Java mode
|
||||
|
||||
// TODO: What if pOutput != null and wrong size? Create new? Render on only a part? Document?
|
||||
// TODO: What if output != null and wrong size? Create new? Render on only a part? Document?
|
||||
|
||||
// If filter type != POINT or BOX an input has IndexColorModel, convert
|
||||
// to true color, with alpha reflecting that of the original colormodel.
|
||||
BufferedImage input;
|
||||
// to true color, with alpha reflecting that of the original color model.
|
||||
BufferedImage temp;
|
||||
ColorModel cm;
|
||||
if (mFilterType != FILTER_BOX && (cm = pInput.getColorModel()) instanceof IndexColorModel) {
|
||||
// TODO: OPTIMIZE: If colormodel has only b/w or gray, we could skip color info
|
||||
input = ImageUtil.toBuffered(pInput, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
|
||||
if (filterType != FILTER_POINT && filterType != FILTER_BOX && (cm = input.getColorModel()) instanceof IndexColorModel) {
|
||||
// TODO: OPTIMIZE: If color model has only b/w or gray, we could skip color info
|
||||
temp = ImageUtil.toBuffered(input, cm.hasAlpha() ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
|
||||
}
|
||||
else {
|
||||
input = pInput;
|
||||
temp = input;
|
||||
}
|
||||
|
||||
// Create or convert output to a suitable image
|
||||
// TODO: OPTIMIZE: Don't really need to convert all types to same as input
|
||||
result = pOutput != null ? /*pOutput*/ ImageUtil.toBuffered(pOutput, input.getType()) : createCompatibleDestImage(input, null);
|
||||
result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
|
||||
|
||||
resample(input, result, filter);
|
||||
resample(temp, result, filter);
|
||||
|
||||
// If pOutput != null and needed to be converted, draw it back
|
||||
if (pOutput != null && pOutput != result) {
|
||||
//pOutput.setData(output.getRaster());
|
||||
ImageUtil.drawOnto(pOutput, result);
|
||||
result = pOutput;
|
||||
// If output != null and needed to be converted, draw it back
|
||||
if (output != null && output != result) {
|
||||
//output.setData(output.getRaster());
|
||||
ImageUtil.drawOnto(output, result);
|
||||
result = output;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -672,8 +665,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
*/
|
||||
|
||||
private static BufferedImage fastResample(final BufferedImage pInput, final BufferedImage pOutput, final int pWidth, final int pHeight, final int pType) {
|
||||
BufferedImage temp = pInput;
|
||||
private static BufferedImage fastResample(final BufferedImage input, final BufferedImage output, final int width, final int height, final int type) {
|
||||
BufferedImage temp = input;
|
||||
|
||||
double xScale;
|
||||
double yScale;
|
||||
@@ -681,20 +674,20 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
AffineTransform transform;
|
||||
AffineTransformOp scale;
|
||||
|
||||
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
||||
if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
||||
// Initially scale so all remaining operations will halve the image
|
||||
if (pWidth < pInput.getWidth() || pHeight < pInput.getHeight()) {
|
||||
int w = pWidth;
|
||||
int h = pHeight;
|
||||
while (w < pInput.getWidth() / 2) {
|
||||
if (width < input.getWidth() || height < input.getHeight()) {
|
||||
int w = width;
|
||||
int h = height;
|
||||
while (w < input.getWidth() / 2) {
|
||||
w *= 2;
|
||||
}
|
||||
while (h < pInput.getHeight() / 2) {
|
||||
while (h < input.getHeight() / 2) {
|
||||
h *= 2;
|
||||
}
|
||||
|
||||
xScale = w / (double) pInput.getWidth();
|
||||
yScale = h / (double) pInput.getHeight();
|
||||
xScale = w / (double) input.getWidth();
|
||||
yScale = h / (double) input.getHeight();
|
||||
|
||||
//System.out.println("First scale by x=" + xScale + ", y=" + yScale);
|
||||
|
||||
@@ -704,12 +697,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
}
|
||||
|
||||
scale = null;// NOTE: This resets!
|
||||
scale = null; // NOTE: This resets!
|
||||
|
||||
xScale = pWidth / (double) temp.getWidth();
|
||||
yScale = pHeight / (double) temp.getHeight();
|
||||
xScale = width / (double) temp.getWidth();
|
||||
yScale = height / (double) temp.getHeight();
|
||||
|
||||
if (pType > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
||||
if (type > AffineTransformOp.TYPE_NEAREST_NEIGHBOR) {
|
||||
// TODO: Test skipping first scale (above), and instead scale once
|
||||
// more here, and a little less than .5 each time...
|
||||
// That would probably make the scaling smoother...
|
||||
@@ -740,17 +733,15 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
|
||||
temp = scale.filter(temp, null);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println("Rest to scale by x=" + xScale + ", y=" + yScale);
|
||||
|
||||
transform = AffineTransform.getScaleInstance(xScale, yScale);
|
||||
scale = new AffineTransformOp(transform, pType);
|
||||
|
||||
return scale.filter(temp, pOutput);
|
||||
scale = new AffineTransformOp(transform, type);
|
||||
|
||||
return scale.filter(temp, output);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -760,7 +751,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
* @see <a href="#field_summary">filter type constants</a>
|
||||
*/
|
||||
public int getFilterType() {
|
||||
return mFilterType;
|
||||
return filterType;
|
||||
}
|
||||
|
||||
private static InterpolationFilter createFilter(int pFilterType) {
|
||||
@@ -770,7 +761,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
}
|
||||
|
||||
switch (pFilterType) {
|
||||
//case FILTER_POINT: // Should never happen
|
||||
case FILTER_POINT:
|
||||
return new PointFilter();
|
||||
case FILTER_BOX:
|
||||
return new BoxFilter();
|
||||
case FILTER_TRIANGLE:
|
||||
@@ -815,14 +807,13 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
// If indexcolormodel, we probably don't want to use that...
|
||||
// NOTE: Either BOTH or NONE of the images must have ALPHA
|
||||
|
||||
return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, mWidth, mHeight),
|
||||
return new BufferedImage(cm, ImageUtil.createCompatibleWritableRaster(pInput, cm, width, height),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
|
||||
}
|
||||
|
||||
public RenderingHints getRenderingHints() {
|
||||
Object value;
|
||||
switch (mFilterType) {
|
||||
switch (filterType) {
|
||||
case FILTER_UNDEFINED:
|
||||
return null;
|
||||
case FILTER_POINT:
|
||||
@@ -871,14 +862,14 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
value = VALUE_INTERPOLATION_BLACKMAN_SINC;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown filter type: " + mFilterType);
|
||||
throw new IllegalStateException("Unknown filter type: " + filterType);
|
||||
}
|
||||
|
||||
return new RenderingHints(KEY_RESAMPLE_INTERPOLATION, value);
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds2D(BufferedImage src) {
|
||||
return new Rectangle(mWidth, mHeight);
|
||||
return new Rectangle(width, height);
|
||||
}
|
||||
|
||||
public Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
|
||||
@@ -1439,10 +1430,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
resample()
|
||||
|
||||
Resizes bitmaps while resampling them.
|
||||
Returns -1 if error, 0 if success.
|
||||
*/
|
||||
private BufferedImage resample(BufferedImage pSource, BufferedImage pDest, InterpolationFilter pFilter) {
|
||||
// TODO: Don't work... Could fix by creating a temporary image in filter method
|
||||
final int dstWidth = pDest.getWidth();
|
||||
final int dstHeight = pDest.getHeight();
|
||||
|
||||
@@ -1451,7 +1440,8 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
|
||||
/* create intermediate column to hold horizontal dst column zoom */
|
||||
final ColorModel cm = pSource.getColorModel();
|
||||
final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
|
||||
// final WritableRaster work = cm.createCompatibleWritableRaster(1, srcHeight);
|
||||
final WritableRaster work = ImageUtil.createCompatibleWritableRaster(pSource, cm, 1, srcHeight);
|
||||
|
||||
double xscale = (double) dstWidth / (double) srcWidth;
|
||||
double yscale = (double) dstHeight / (double) srcHeight;
|
||||
@@ -1566,7 +1556,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
final WritableRaster out = pDest.getRaster();
|
||||
|
||||
// TODO: This is not optimal for non-byte-packed rasters...
|
||||
// (What? Maybe I implemented the fix, but forgot to remove the qTODO?)
|
||||
// (What? Maybe I implemented the fix, but forgot to remove the TODO?)
|
||||
final int numChannels = raster.getNumBands();
|
||||
final int[] channelMax = new int[numChannels];
|
||||
for (int k = 0; k < numChannels; k++) {
|
||||
@@ -1575,7 +1565,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
|
||||
|
||||
for (int xx = 0; xx < dstWidth; xx++) {
|
||||
ContributorList contribX = calcXContrib(xscale, fwidth, srcWidth, pFilter, xx);
|
||||
/* Apply horz filter to make dst column in tmp. */
|
||||
/* Apply horiz filter to make dst column in tmp. */
|
||||
for (int k = 0; k < srcHeight; k++) {
|
||||
for (int channel = 0; channel < numChannels; channel++) {
|
||||
|
||||
+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 $
|
||||
*/
|
||||
public class SubsamplingFilter extends ReplicateScaleFilter {
|
||||
private int mXSub;
|
||||
private int mYSub;
|
||||
private int xSub;
|
||||
private int ySub;
|
||||
|
||||
/**
|
||||
* Creates a {@code SubsamplingFilter}.
|
||||
@@ -62,16 +62,16 @@ public class SubsamplingFilter extends ReplicateScaleFilter {
|
||||
throw new IllegalArgumentException("Subsampling factors must be positive.");
|
||||
}
|
||||
|
||||
mXSub = pXSub;
|
||||
mYSub = pYSub;
|
||||
xSub = pXSub;
|
||||
ySub = pYSub;
|
||||
}
|
||||
|
||||
/** {@code ImageFilter} implementation, do not invoke. */
|
||||
public void setDimensions(int pWidth, int pHeight) {
|
||||
destWidth = (pWidth + mXSub - 1) / mXSub;
|
||||
destHeight = (pHeight + mYSub - 1) / mYSub;
|
||||
destWidth = (pWidth + xSub - 1) / xSub;
|
||||
destHeight = (pHeight + ySub - 1) / ySub;
|
||||
|
||||
//System.out.println("Subsampling: " + mXSub + "," + mYSub + "-> " + destWidth + ", " + destHeight);
|
||||
//System.out.println("Subsampling: " + xSub + "," + ySub + "-> " + destWidth + ", " + destHeight);
|
||||
super.setDimensions(pWidth, pHeight);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,6 +4,6 @@
|
||||
* See the class {@link com.twelvemonkeys.image.ImageUtil}.
|
||||
*
|
||||
* @version 1.0
|
||||
* @author <a href="mailto:harald@escenic.com">Harald Kuhr</a>
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.image;
|
||||
+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;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
*
|
||||
* @author $author wmhakur$
|
||||
* @version $id: $
|
||||
* To change this template use Options | File Templates.
|
||||
*/
|
||||
public class ImageUtilTestCase extends TestCase {
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ImageUtilTestCase {
|
||||
|
||||
private final static String IMAGE_NAME = "/sunflower.jpg";
|
||||
private BufferedImage mOriginal;
|
||||
private BufferedImage mImage;
|
||||
private Image mScaled;
|
||||
private BufferedImage original;
|
||||
private BufferedImage image;
|
||||
private Image scaled;
|
||||
|
||||
public ImageUtilTestCase() throws Exception {
|
||||
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||
|
||||
// Read image from class path
|
||||
InputStream is = getClass().getResourceAsStream(IMAGE_NAME);
|
||||
mOriginal = ImageIO.read(is);
|
||||
original = ImageIO.read(is);
|
||||
|
||||
assertNotNull(mOriginal);
|
||||
assertNotNull(original);
|
||||
}
|
||||
|
||||
/*
|
||||
public void setUp() throws Exception {
|
||||
mImage = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
mScaled = mImage.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||
image = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB);
|
||||
scaled = image.getScaledInstance(5, 5, Image.SCALE_FAST);
|
||||
|
||||
// Read image from class path
|
||||
InputStream is = ClassLoader.getSystemResourceAsStream(IMAGE_NAME);
|
||||
mOriginal = ImageIO.read(is);
|
||||
original = ImageIO.read(is);
|
||||
|
||||
assertNotNull(mOriginal);
|
||||
assertNotNull(original);
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
mOriginal = null;
|
||||
original = null;
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testToBufferedImageNull() {
|
||||
BufferedImage img = null;
|
||||
boolean threwRuntimeException = false;
|
||||
@@ -73,6 +65,7 @@ public class ImageUtilTestCase extends TestCase {
|
||||
assertTrue(threwRuntimeException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToBufferedImageTypeNull() {
|
||||
BufferedImage img = null;
|
||||
boolean threwRuntimeException = false;
|
||||
@@ -90,54 +83,58 @@ public class ImageUtilTestCase extends TestCase {
|
||||
assertTrue(threwRuntimeException);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImageIsNotBufferedImage() {
|
||||
// Should not be a buffered image
|
||||
assertFalse(
|
||||
"FOR SOME IMPLEMENTATIONS THIS MIGHT FAIL!\nIn that case, testToBufferedImage() will fail too.",
|
||||
mScaled instanceof BufferedImage
|
||||
scaled instanceof BufferedImage
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToBufferedImage() {
|
||||
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) mImage);
|
||||
BufferedImage bufferedScaled = ImageUtil.toBuffered(mScaled);
|
||||
BufferedImage sameAsImage = ImageUtil.toBuffered((RenderedImage) image);
|
||||
BufferedImage bufferedScaled = ImageUtil.toBuffered(scaled);
|
||||
|
||||
// Should be no need to convert
|
||||
assertSame(mImage, sameAsImage);
|
||||
assertSame(image, sameAsImage);
|
||||
|
||||
// Should have same dimensions
|
||||
assertEquals(mScaled.getWidth(null), bufferedScaled.getWidth());
|
||||
assertEquals(mScaled.getHeight(null), bufferedScaled.getHeight());
|
||||
assertEquals(scaled.getWidth(null), bufferedScaled.getWidth());
|
||||
assertEquals(scaled.getHeight(null), bufferedScaled.getHeight());
|
||||
|
||||
// Hmmm...
|
||||
assertTrue(new Integer(42).equals(bufferedScaled.getProperty("lucky-number"))
|
||||
|| bufferedScaled.getPropertyNames() == null
|
||||
|| bufferedScaled.getPropertyNames().length == 0);
|
||||
|| bufferedScaled.getPropertyNames() == null
|
||||
|| bufferedScaled.getPropertyNames().length == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToBufferedImageType() {
|
||||
// Assumes mImage is TYPE_INT_ARGB
|
||||
BufferedImage converted = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_INDEXED);
|
||||
BufferedImage convertedToo = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_BYTE_BINARY);
|
||||
// Assumes image is TYPE_INT_ARGB
|
||||
BufferedImage converted = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_INDEXED);
|
||||
BufferedImage convertedToo = ImageUtil.toBuffered(image, BufferedImage.TYPE_BYTE_BINARY);
|
||||
|
||||
// Should not be the same
|
||||
assertNotSame(mImage, converted);
|
||||
assertNotSame(mImage, convertedToo);
|
||||
assertNotSame(image, converted);
|
||||
assertNotSame(image, convertedToo);
|
||||
|
||||
// Correct type
|
||||
assertTrue(converted.getType() == BufferedImage.TYPE_BYTE_INDEXED);
|
||||
assertTrue(convertedToo.getType() == BufferedImage.TYPE_BYTE_BINARY);
|
||||
|
||||
// Should have same dimensions
|
||||
assertEquals(mImage.getWidth(), converted.getWidth());
|
||||
assertEquals(mImage.getHeight(), converted.getHeight());
|
||||
assertEquals(image.getWidth(), converted.getWidth());
|
||||
assertEquals(image.getHeight(), converted.getHeight());
|
||||
|
||||
assertEquals(mImage.getWidth(), convertedToo.getWidth());
|
||||
assertEquals(mImage.getHeight(), convertedToo.getHeight());
|
||||
assertEquals(image.getWidth(), convertedToo.getWidth());
|
||||
assertEquals(image.getHeight(), convertedToo.getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrightness() {
|
||||
final BufferedImage original = mOriginal;
|
||||
final BufferedImage original = this.original;
|
||||
assertNotNull(original);
|
||||
|
||||
final BufferedImage notBrightened = ImageUtil.toBuffered(ImageUtil.brightness(original, 0f));
|
||||
@@ -181,7 +178,7 @@ public class ImageUtilTestCase extends TestCase {
|
||||
final BufferedImage brightenedMaxNegative = ImageUtil.toBuffered(ImageUtil.brightness(original, -2f));
|
||||
for (int y = 0; y < brightenedMaxNegative.getHeight(); y++) {
|
||||
for (int x = 0; x < brightenedMaxNegative.getWidth(); x++) {
|
||||
assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF);
|
||||
assertEquals(0x0, brightenedMaxNegative.getRGB(x, y) & 0x00FFFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,9 +212,9 @@ public class ImageUtilTestCase extends TestCase {
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContrast() {
|
||||
final BufferedImage original = mOriginal;
|
||||
final BufferedImage original = this.original;
|
||||
|
||||
assertNotNull(original);
|
||||
|
||||
@@ -273,7 +270,6 @@ public class ImageUtilTestCase extends TestCase {
|
||||
else {
|
||||
assertTrue("Contrast should be increased or same", oB <= cB && cB <= dB);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// Assumed: Only primary colors (w/b/r/g/b/c/y/m)
|
||||
@@ -337,7 +333,7 @@ public class ImageUtilTestCase extends TestCase {
|
||||
int r = rgb >> 16 & 0xFF;
|
||||
int g = rgb >> 8 & 0xFF;
|
||||
int b = rgb & 0xFF;
|
||||
assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 &&b == 127);
|
||||
assertTrue("Minimum contrast should be all gray", r == 127 && g == 127 && b == 127);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,8 +365,9 @@ public class ImageUtilTestCase extends TestCase {
|
||||
*/
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharpen() {
|
||||
final BufferedImage original = mOriginal;
|
||||
final BufferedImage original = this.original;
|
||||
|
||||
assertNotNull(original);
|
||||
|
||||
@@ -390,10 +387,10 @@ public class ImageUtilTestCase extends TestCase {
|
||||
final BufferedImage sharpenedDefault = ImageUtil.sharpen(original, 0.3f);
|
||||
final BufferedImage sharpenedMore = ImageUtil.sharpen(original, 1.3f);
|
||||
|
||||
long diffOriginal = 0;
|
||||
long diffSharpened = 0;
|
||||
long diffDefault = 0;
|
||||
long diffMore = 0;
|
||||
// long diffOriginal = 0;
|
||||
// long diffSharpened = 0;
|
||||
// long diffDefault = 0;
|
||||
// long diffMore = 0;
|
||||
|
||||
long absDiffOriginal = 0;
|
||||
long absDiffSharpened = 0;
|
||||
@@ -412,10 +409,10 @@ public class ImageUtilTestCase extends TestCase {
|
||||
int pdRGB = 0x00FFFFFF & sharpenedDefault.getRGB(x - 1, y);
|
||||
int pmRGB = 0x00FFFFFF & sharpenedMore.getRGB(x - 1, y);
|
||||
|
||||
diffOriginal += poRGB - oRGB;
|
||||
diffSharpened += psRGB - sRGB;
|
||||
diffDefault += pdRGB - dRGB;
|
||||
diffMore += pmRGB - mRGB;
|
||||
// diffOriginal += poRGB - oRGB;
|
||||
// diffSharpened += psRGB - sRGB;
|
||||
// diffDefault += pdRGB - dRGB;
|
||||
// diffMore += pmRGB - mRGB;
|
||||
|
||||
absDiffOriginal += Math.abs(poRGB - oRGB);
|
||||
absDiffSharpened += Math.abs(psRGB - sRGB);
|
||||
@@ -424,10 +421,6 @@ public class ImageUtilTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
//*
|
||||
showEm(original, notSharpened, sharpened, sharpenedDefault, sharpenedMore, "sharpen");
|
||||
//*/
|
||||
|
||||
// assertEquals("Difference should not change", diffOriginal, diffSharpened);
|
||||
assertTrue("Abs difference should increase", absDiffOriginal < absDiffSharpened);
|
||||
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
||||
@@ -438,64 +431,9 @@ public class ImageUtilTestCase extends TestCase {
|
||||
assertTrue("Abs difference should increase", absDiffSharpened < absDiffMore);
|
||||
}
|
||||
|
||||
private void showEm(final BufferedImage pOriginal, final BufferedImage pNotSharpened, final BufferedImage pSharpened, final BufferedImage pSharpenedDefault, final BufferedImage pSharpenedMore, final String pTitle) {
|
||||
if (pOriginal != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
public void run() {
|
||||
JFrame frame = new JFrame("Sunflower - " + pTitle);
|
||||
frame.setSize(pOriginal.getWidth() * 4, pOriginal.getHeight() * 2);
|
||||
|
||||
Canvas canvas = new Canvas() {
|
||||
public void paint(Graphics g) {
|
||||
// Draw original for comparison
|
||||
g.drawImage(pOriginal, 0, 0, null);
|
||||
|
||||
// This should look like original
|
||||
g.drawImage(pNotSharpened, 0, pOriginal.getHeight(), null);
|
||||
|
||||
// Different versions
|
||||
g.drawImage(pSharpened, pOriginal.getWidth(), 0, null);
|
||||
g.drawImage(pSharpenedDefault, pOriginal.getWidth() * 2, 0, null);
|
||||
g.drawImage(pSharpenedMore, pOriginal.getWidth() * 3, 0, null);
|
||||
}
|
||||
};
|
||||
|
||||
frame.getContentPane().add(canvas);
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
synchronized (ImageUtilTestCase.this) {
|
||||
ImageUtilTestCase.this.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
synchronized (ImageUtilTestCase.this) {
|
||||
try {
|
||||
ImageUtilTestCase.this.wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlur() {
|
||||
final BufferedImage original = mOriginal;
|
||||
final BufferedImage original = this.original;
|
||||
|
||||
assertNotNull(original);
|
||||
|
||||
@@ -515,17 +453,16 @@ public class ImageUtilTestCase extends TestCase {
|
||||
final BufferedImage blurredDefault = ImageUtil.blur(original, 1.5f);
|
||||
final BufferedImage blurredMore = ImageUtil.blur(original, 3f);
|
||||
|
||||
long diffOriginal = 0;
|
||||
long diffBlurred = 0;
|
||||
long diffDefault = 0;
|
||||
long diffMore = 0;
|
||||
// long diffOriginal = 0;
|
||||
// long diffBlurred = 0;
|
||||
// long diffDefault = 0;
|
||||
// long diffMore = 0;
|
||||
|
||||
long absDiffOriginal = 0;
|
||||
long absDiffBlurred = 0;
|
||||
long absDiffDefault = 0;
|
||||
long absDiffMore = 0;
|
||||
|
||||
|
||||
for (int y = 0; y < original.getHeight(); y++) {
|
||||
for (int x = 1; x < original.getWidth(); x++) {
|
||||
int oRGB = 0x00FFFFFF & original.getRGB(x, y);
|
||||
@@ -538,10 +475,10 @@ public class ImageUtilTestCase extends TestCase {
|
||||
int pdRGB = 0x00FFFFFF & blurredDefault.getRGB(x - 1, y);
|
||||
int pmRGB = 0x00FFFFFF & blurredMore.getRGB(x - 1, y);
|
||||
|
||||
diffOriginal += poRGB - oRGB;
|
||||
diffBlurred += pbRGB - bRGB;
|
||||
diffDefault += pdRGB - dRGB;
|
||||
diffMore += pmRGB - mRGB;
|
||||
// diffOriginal += poRGB - oRGB;
|
||||
// diffBlurred += pbRGB - bRGB;
|
||||
// diffDefault += pdRGB - dRGB;
|
||||
// diffMore += pmRGB - mRGB;
|
||||
|
||||
absDiffOriginal += Math.abs(poRGB - oRGB);
|
||||
absDiffBlurred += Math.abs(pbRGB - bRGB);
|
||||
@@ -550,8 +487,6 @@ public class ImageUtilTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
showEm(original, notBlurred, blurred, blurredDefault, blurredMore, "blur");
|
||||
|
||||
// assertEquals("Difference should not change", diffOriginal, diffBlurred);
|
||||
assertTrue(String.format("Abs difference should decrease: %s <= %s", absDiffOriginal, absDiffBlurred), absDiffOriginal > absDiffBlurred);
|
||||
// assertEquals("Difference should not change", diffOriginal, diffDefault);
|
||||
@@ -562,8 +497,9 @@ public class ImageUtilTestCase extends TestCase {
|
||||
assertTrue("Abs difference should decrease", absDiffBlurred > absDiffMore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexImage() {
|
||||
BufferedImage sunflower = mOriginal;
|
||||
BufferedImage sunflower = original;
|
||||
|
||||
assertNotNull(sunflower);
|
||||
|
||||
@@ -571,4 +507,4 @@ public class ImageUtilTestCase extends TestCase {
|
||||
assertNotNull("Image was null", image);
|
||||
assertTrue(image.getColorModel() instanceof IndexColorModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-2
@@ -1,6 +1,7 @@
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -9,6 +10,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ResampleOpTestCase
|
||||
*
|
||||
@@ -16,7 +19,7 @@ import java.util.List;
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/image/ResampleOpTestCase.java#1 $
|
||||
*/
|
||||
public class ResampleOpTestCase extends TestCase {
|
||||
public class ResampleOpTestCase {
|
||||
|
||||
protected BufferedImage createImage(final int pWidth, final int pHeigth) {
|
||||
return createImage(pWidth, pHeigth, BufferedImage.TYPE_INT_ARGB);
|
||||
@@ -36,6 +39,7 @@ public class ResampleOpTestCase extends TestCase {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateImage() {
|
||||
// Sanity test the create method
|
||||
BufferedImage image = createImage(79, 84);
|
||||
@@ -94,170 +98,225 @@ public class ResampleOpTestCase extends TestCase {
|
||||
}
|
||||
|
||||
// 1x1
|
||||
@Test
|
||||
public void testResample1x1Point() {
|
||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Box() {
|
||||
assertResample(createImage(1, 1), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Triangle() {
|
||||
assertResample(createImage(1, 1), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Lanczos() {
|
||||
assertResample(createImage(1, 1), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Gaussian() {
|
||||
assertResample(createImage(1, 1), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample1x1Sinc() {
|
||||
assertResample(createImage(1, 1), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 2x2
|
||||
@Test
|
||||
public void testResample2x2Point() {
|
||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Box() {
|
||||
assertResample(createImage(2, 2), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Triangle() {
|
||||
assertResample(createImage(2, 2), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Lanczos() {
|
||||
assertResample(createImage(2, 2), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Gaussian() {
|
||||
assertResample(createImage(2, 2), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample2x2Sinc() {
|
||||
assertResample(createImage(2, 2), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 3x3
|
||||
@Test
|
||||
public void testResample3x3Point() {
|
||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Box() {
|
||||
assertResample(createImage(3, 3), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Triangle() {
|
||||
assertResample(createImage(3, 3), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Lanczos() {
|
||||
assertResample(createImage(3, 3), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Gaussian() {
|
||||
assertResample(createImage(3, 3), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample3x3Sinc() {
|
||||
assertResample(createImage(3, 3), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 4x4
|
||||
@Test
|
||||
public void testResample4x4Point() {
|
||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Box() {
|
||||
assertResample(createImage(4, 4), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Triangle() {
|
||||
assertResample(createImage(4, 4), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Lanczos() {
|
||||
assertResample(createImage(4, 4), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Gaussian() {
|
||||
assertResample(createImage(4, 4), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample4x4Sinc() {
|
||||
assertResample(createImage(4, 4), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 20x20
|
||||
@Test
|
||||
public void testResample20x20Point() {
|
||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Box() {
|
||||
assertResample(createImage(20, 20), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Triangle() {
|
||||
assertResample(createImage(20, 20), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Lanczos() {
|
||||
assertResample(createImage(20, 20), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Gaussian() {
|
||||
assertResample(createImage(20, 20), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample20x20Sinc() {
|
||||
assertResample(createImage(20, 20), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// 200x160
|
||||
@Test
|
||||
public void testResample200x160Point() {
|
||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Box() {
|
||||
assertResample(createImage(200, 160), 10, 11, ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Triangle() {
|
||||
assertResample(createImage(200, 160), 19, 13, ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Lanczos() {
|
||||
assertResample(createImage(200, 160), 7, 49, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Gaussian() {
|
||||
assertResample(createImage(200, 160), 11, 34, ResampleOp.FILTER_GAUSSIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResample200x160Sinc() {
|
||||
assertResample(createImage(200, 160), 2, 8, ResampleOp.FILTER_BLACKMAN_SINC);
|
||||
}
|
||||
|
||||
// Test 10x10 -> 15x5 with different algorithms and types
|
||||
@Test
|
||||
public void testResamplePoint() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_POINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleBox() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_BOX);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleTriangle() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_TRIANGLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResampleLanczos() {
|
||||
assertResampleBufferedImageTypes(ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
|
||||
@Ignore("Not for general unit testing")
|
||||
@Test
|
||||
public void testTime() {
|
||||
int iterations = 1000;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
assertResample(createImage(50, 50), 33, 33, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
assertResample(createImage(512, 512), 145, 145, ResampleOp.FILTER_LANCZOS);
|
||||
}
|
||||
long end = System.currentTimeMillis();
|
||||
System.out.printf("time: %d ms, avg %s ms%n", end - start, (end - start) / (double) iterations);
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
@@ -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.1</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 {
|
||||
/** The backing stream */
|
||||
protected final InputStream mStream;
|
||||
protected final InputStream stream;
|
||||
|
||||
/** The stream positon in the backing stream (mStream) */
|
||||
protected long mStreamPosition;
|
||||
/** The stream positon in the backing stream (stream) */
|
||||
protected long streamPosition;
|
||||
|
||||
private StreamCache mCache;
|
||||
private StreamCache cache;
|
||||
|
||||
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
|
||||
Validate.notNull(pStream, "stream");
|
||||
Validate.notNull(pCache, "cache");
|
||||
|
||||
mStream = pStream;
|
||||
mCache = pCache;
|
||||
stream = pStream;
|
||||
cache = pCache;
|
||||
}
|
||||
|
||||
protected final StreamCache getCache() {
|
||||
return mCache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long avail = mStreamPosition - mPosition + mStream.available();
|
||||
long avail = streamPosition - position + stream.available();
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||
}
|
||||
|
||||
@@ -43,26 +43,26 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
checkOpen();
|
||||
int read;
|
||||
|
||||
if (mPosition == mStreamPosition) {
|
||||
if (position == streamPosition) {
|
||||
// TODO: Read more bytes here!
|
||||
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
|
||||
// Read a byte from the stream
|
||||
read = mStream.read();
|
||||
read = stream.read();
|
||||
|
||||
if (read >= 0) {
|
||||
mStreamPosition++;
|
||||
mCache.write(read);
|
||||
streamPosition++;
|
||||
cache.write(read);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ..or read byte from the cache
|
||||
syncPosition();
|
||||
read = mCache.read();
|
||||
read = cache.read();
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (read != -1) {
|
||||
mPosition++;
|
||||
position++;
|
||||
}
|
||||
|
||||
return read;
|
||||
@@ -73,32 +73,32 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
checkOpen();
|
||||
int length;
|
||||
|
||||
if (mPosition == mStreamPosition) {
|
||||
if (position == streamPosition) {
|
||||
// Read bytes from the stream
|
||||
length = mStream.read(pBytes, pOffset, pLength);
|
||||
length = stream.read(pBytes, pOffset, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
mStreamPosition += length;
|
||||
mCache.write(pBytes, pOffset, length);
|
||||
streamPosition += length;
|
||||
cache.write(pBytes, pOffset, length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ...or read bytes from the cache
|
||||
syncPosition();
|
||||
length = mCache.read(pBytes, pOffset, pLength);
|
||||
length = cache.read(pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (length > 0) {
|
||||
mPosition += length;
|
||||
position += length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
protected final void syncPosition() throws IOException {
|
||||
if (mCache.getPosition() != mPosition) {
|
||||
mCache.seek(mPosition); // Assure EOF is correctly thrown
|
||||
if (cache.getPosition() != position) {
|
||||
cache.seek(position); // Assure EOF is correctly thrown
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +111,14 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
public abstract boolean isCachedFile();
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
if (mStreamPosition < pPosition) {
|
||||
if (streamPosition < pPosition) {
|
||||
// Make sure we append at end of cache
|
||||
if (mCache.getPosition() != mStreamPosition) {
|
||||
mCache.seek(mStreamPosition);
|
||||
if (cache.getPosition() != streamPosition) {
|
||||
cache.seek(streamPosition);
|
||||
}
|
||||
|
||||
// Read diff from stream into cache
|
||||
long left = pPosition - mStreamPosition;
|
||||
long left = pPosition - streamPosition;
|
||||
|
||||
// TODO: Use fixed buffer, instead of allocating here...
|
||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||
@@ -126,11 +126,11 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
|
||||
while (left > 0) {
|
||||
int length = buffer.length < left ? buffer.length : (int) left;
|
||||
int read = mStream.read(buffer, 0, length);
|
||||
int read = stream.read(buffer, 0, length);
|
||||
|
||||
if (read > 0) {
|
||||
mCache.write(buffer, 0, read);
|
||||
mStreamPosition += read;
|
||||
cache.write(buffer, 0, read);
|
||||
streamPosition += read;
|
||||
left -= read;
|
||||
}
|
||||
else if (read < 0) {
|
||||
@@ -138,27 +138,27 @@ abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mStreamPosition >= pPosition) {
|
||||
else /*if (streamPosition >= pPosition) */ {
|
||||
// Seek backwards into the cache
|
||||
mCache.seek(pPosition);
|
||||
cache.seek(pPosition);
|
||||
}
|
||||
|
||||
// System.out.println("pPosition: " + pPosition);
|
||||
// System.out.println("mPosition: " + mPosition);
|
||||
// System.out.println("mStreamPosition: " + mStreamPosition);
|
||||
// System.out.println("mCache.mPosition: " + mCache.getPosition());
|
||||
// System.out.println("position: " + position);
|
||||
// System.out.println("streamPosition: " + streamPosition);
|
||||
// System.out.println("cache.position: " + cache.getPosition());
|
||||
|
||||
// NOTE: If mPosition == pPosition then we're good to go
|
||||
// NOTE: If position == pPosition then we're good to go
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
mCache.flush(pPosition);
|
||||
cache.flush(pPosition);
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mCache.flush(mPosition);
|
||||
mCache = null;
|
||||
mStream.close();
|
||||
cache.flush(position);
|
||||
cache = null;
|
||||
stream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
+43
-41
@@ -46,15 +46,16 @@ import java.util.List;
|
||||
*/
|
||||
public class CompoundReader extends Reader {
|
||||
|
||||
private Reader mCurrent;
|
||||
private List<Reader> mReaders;
|
||||
protected final Object mLock;
|
||||
private Reader current;
|
||||
private List<Reader> readers;
|
||||
|
||||
protected final boolean mMarkSupported;
|
||||
protected final Object finalLock;
|
||||
|
||||
private int mCurrentReader;
|
||||
private int mMarkedReader;
|
||||
private int mMark;
|
||||
protected final boolean markSupported;
|
||||
|
||||
private int currentReader;
|
||||
private int markedReader;
|
||||
private int mark;
|
||||
private int mNext;
|
||||
|
||||
/**
|
||||
@@ -71,10 +72,10 @@ public class CompoundReader extends Reader {
|
||||
public CompoundReader(final Iterator<Reader> pReaders) {
|
||||
super(Validate.notNull(pReaders, "readers"));
|
||||
|
||||
mLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
||||
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
||||
// reference can't change, only it's elements
|
||||
|
||||
mReaders = new ArrayList<Reader>();
|
||||
readers = new ArrayList<Reader>();
|
||||
|
||||
boolean markSupported = true;
|
||||
while (pReaders.hasNext()) {
|
||||
@@ -82,25 +83,25 @@ public class CompoundReader extends Reader {
|
||||
if (reader == null) {
|
||||
throw new NullPointerException("readers cannot contain null-elements");
|
||||
}
|
||||
mReaders.add(reader);
|
||||
readers.add(reader);
|
||||
markSupported = markSupported && reader.markSupported();
|
||||
}
|
||||
mMarkSupported = markSupported;
|
||||
this.markSupported = markSupported;
|
||||
|
||||
mCurrent = nextReader();
|
||||
current = nextReader();
|
||||
}
|
||||
|
||||
protected final Reader nextReader() {
|
||||
if (mCurrentReader >= mReaders.size()) {
|
||||
mCurrent = new EmptyReader();
|
||||
if (currentReader >= readers.size()) {
|
||||
current = new EmptyReader();
|
||||
}
|
||||
else {
|
||||
mCurrent = mReaders.get(mCurrentReader++);
|
||||
current = readers.get(currentReader++);
|
||||
}
|
||||
|
||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||
mNext = 0;
|
||||
return mCurrent;
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,17 +110,18 @@ public class CompoundReader extends Reader {
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
protected final void ensureOpen() throws IOException {
|
||||
if (mReaders == null) {
|
||||
if (readers == null) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
// Close all readers
|
||||
for (Reader reader : mReaders) {
|
||||
for (Reader reader : readers) {
|
||||
reader.close();
|
||||
}
|
||||
mReaders = null;
|
||||
|
||||
readers = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,46 +132,46 @@ public class CompoundReader extends Reader {
|
||||
|
||||
// TODO: It would be nice if we could actually close some readers now
|
||||
|
||||
synchronized (mLock) {
|
||||
synchronized (finalLock) {
|
||||
ensureOpen();
|
||||
mMark = mNext;
|
||||
mMarkedReader = mCurrentReader;
|
||||
mark = mNext;
|
||||
markedReader = currentReader;
|
||||
|
||||
mCurrent.mark(pReadLimit);
|
||||
current.mark(pReadLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
synchronized (mLock) {
|
||||
synchronized (finalLock) {
|
||||
ensureOpen();
|
||||
|
||||
if (mCurrentReader != mMarkedReader) {
|
||||
if (currentReader != markedReader) {
|
||||
// Reset any reader before this
|
||||
for (int i = mCurrentReader; i >= mMarkedReader; i--) {
|
||||
mReaders.get(i).reset();
|
||||
for (int i = currentReader; i >= markedReader; i--) {
|
||||
readers.get(i).reset();
|
||||
}
|
||||
|
||||
mCurrentReader = mMarkedReader - 1;
|
||||
currentReader = markedReader - 1;
|
||||
nextReader();
|
||||
}
|
||||
mCurrent.reset();
|
||||
current.reset();
|
||||
|
||||
mNext = mMark;
|
||||
mNext = mark;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return mMarkSupported;
|
||||
return markSupported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read();
|
||||
synchronized (finalLock) {
|
||||
int read = current.read();
|
||||
|
||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
||||
if (read < 0 && currentReader < readers.size()) {
|
||||
nextReader();
|
||||
return read(); // In case of 0-length readers
|
||||
}
|
||||
@@ -181,10 +183,10 @@ public class CompoundReader extends Reader {
|
||||
}
|
||||
|
||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
||||
synchronized (finalLock) {
|
||||
int read = current.read(pBuffer, pOffset, pLength);
|
||||
|
||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
||||
if (read < 0 && currentReader < readers.size()) {
|
||||
nextReader();
|
||||
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
||||
}
|
||||
@@ -197,15 +199,15 @@ public class CompoundReader extends Reader {
|
||||
|
||||
@Override
|
||||
public boolean ready() throws IOException {
|
||||
return mCurrent.ready();
|
||||
return current.ready();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long pChars) throws IOException {
|
||||
synchronized (mLock) {
|
||||
long skipped = mCurrent.skip(pChars);
|
||||
synchronized (finalLock) {
|
||||
long skipped = current.skip(pChars);
|
||||
|
||||
if (skipped == 0 && mCurrentReader < mReaders.size()) {
|
||||
if (skipped == 0 && currentReader < readers.size()) {
|
||||
nextReader();
|
||||
return skip(pChars); // In case of 0-length readers
|
||||
}
|
||||
+21
-18
@@ -39,11 +39,12 @@ import java.io.ByteArrayInputStream;
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java#2 $
|
||||
* @version $Id: FastByteArrayOutputStream.java#2 $
|
||||
*/
|
||||
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
||||
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
/** Max grow size (unless if writing more than this ammount of bytes) */
|
||||
protected int mMaxGrowSize = 1024 * 1024; // 1 MB
|
||||
/** Max grow size (unless if writing more than this amount of bytes) */
|
||||
protected int maxGrowSize = 1024 * 1024; // 1 MB
|
||||
|
||||
/**
|
||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||
@@ -69,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte pBytes[], int pOffset, int pLength) {
|
||||
public void write(byte pBytes[], int pOffset, int pLength) {
|
||||
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
@@ -77,23 +78,24 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
else if (pLength == 0) {
|
||||
return;
|
||||
}
|
||||
int newcount = count + pLength;
|
||||
growIfNeeded(newcount);
|
||||
|
||||
int newCount = count + pLength;
|
||||
growIfNeeded(newCount);
|
||||
System.arraycopy(pBytes, pOffset, buf, count, pLength);
|
||||
count = newcount;
|
||||
count = newCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int pByte) {
|
||||
int newcount = count + 1;
|
||||
growIfNeeded(newcount);
|
||||
public void write(int pByte) {
|
||||
int newCount = count + 1;
|
||||
growIfNeeded(newCount);
|
||||
buf[count] = (byte) pByte;
|
||||
count = newcount;
|
||||
count = newCount;
|
||||
}
|
||||
|
||||
private void growIfNeeded(int pNewcount) {
|
||||
if (pNewcount > buf.length) {
|
||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount);
|
||||
private void growIfNeeded(int pNewCount) {
|
||||
if (pNewCount > buf.length) {
|
||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
|
||||
byte newBuf[] = new byte[newSize];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
buf = newBuf;
|
||||
@@ -109,9 +111,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
// Non-synchronized version of toByteArray
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
byte newbuf[] = new byte[count];
|
||||
System.arraycopy(buf, 0, newbuf, 0, count);
|
||||
return newbuf;
|
||||
byte newBuf[] = new byte[count];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +124,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
* <p/>
|
||||
* Note that care needs to be taken to avoid writes to
|
||||
* this output stream after the input stream is created.
|
||||
* Failing to do so, may result in unpredictable behviour.
|
||||
* Failing to do so, may result in unpredictable behaviour.
|
||||
*
|
||||
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
|
||||
*/
|
||||
+12
-95
@@ -48,16 +48,7 @@ import java.io.*;
|
||||
*/
|
||||
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
|
||||
|
||||
// private final InputStream mStream;
|
||||
// private final RandomAccessFile mCache;
|
||||
private byte[] mBuffer;
|
||||
|
||||
/** The stream positon in the backing stream (mStream) */
|
||||
// private long mStreamPosition;
|
||||
|
||||
// TODO: getStreamPosition() should always be the same as
|
||||
// mCache.getFilePointer()
|
||||
// otherwise there's some inconsistency here... Enforce this?
|
||||
private byte[] buffer;
|
||||
|
||||
/**
|
||||
* Creates a {@code FileCacheSeekableStream} reading from the given
|
||||
@@ -118,7 +109,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
||||
super(pStream, new FileCache(pFile));
|
||||
|
||||
// TODO: Allow for custom buffer sizes?
|
||||
mBuffer = new byte[1024];
|
||||
buffer = new byte[1024];
|
||||
}
|
||||
|
||||
public final boolean isCachedMemory() {
|
||||
@@ -132,39 +123,19 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
||||
@Override
|
||||
protected void closeImpl() throws IOException {
|
||||
super.closeImpl();
|
||||
mBuffer = null;
|
||||
buffer = null;
|
||||
}
|
||||
/*
|
||||
public final boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// InputStream overrides
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long avail = mStreamPosition - mPosition + mStream.available();
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||
}
|
||||
|
||||
public void closeImpl() throws IOException {
|
||||
mStream.close();
|
||||
mCache.close();
|
||||
|
||||
// TODO: Delete cache file here?
|
||||
// ThreadPool.invokeLater(new DeleteFileAction(mCacheFile));
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkOpen();
|
||||
|
||||
int read;
|
||||
if (mPosition == mStreamPosition) {
|
||||
if (position == streamPosition) {
|
||||
// Read ahead into buffer, for performance
|
||||
read = readAhead(mBuffer, 0, mBuffer.length);
|
||||
read = readAhead(buffer, 0, buffer.length);
|
||||
if (read >= 0) {
|
||||
read = mBuffer[0] & 0xff;
|
||||
read = buffer[0] & 0xff;
|
||||
}
|
||||
|
||||
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
|
||||
@@ -179,7 +150,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (read != -1) {
|
||||
mPosition++;
|
||||
position++;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -189,7 +160,7 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
||||
checkOpen();
|
||||
|
||||
int length;
|
||||
if (mPosition == mStreamPosition) {
|
||||
if (position == streamPosition) {
|
||||
// Read bytes from the stream
|
||||
length = readAhead(pBytes, pOffset, pLength);
|
||||
|
||||
@@ -198,83 +169,29 @@ public final class FileCacheSeekableStream extends AbstractCachedSeekableStream
|
||||
else {
|
||||
// ...or read bytes from the cache
|
||||
syncPosition();
|
||||
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, mStreamPosition - mPosition));
|
||||
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, streamPosition - position));
|
||||
|
||||
//System.out.println("Read " + length + " byte from cache");
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (length > 0) {
|
||||
mPosition += length;
|
||||
position += length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
int length;
|
||||
length = mStream.read(pBytes, pOffset, pLength);
|
||||
length = stream.read(pBytes, pOffset, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
mStreamPosition += length;
|
||||
streamPosition += length;
|
||||
getCache().write(pBytes, pOffset, length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/*
|
||||
private void syncPosition() throws IOException {
|
||||
if (mCache.getFilePointer() != mPosition) {
|
||||
mCache.seek(mPosition); // Assure EOF is correctly thrown
|
||||
}
|
||||
}
|
||||
|
||||
// Seekable overrides
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
// TODO: Implement
|
||||
// For now, it's probably okay to do nothing, this is just for
|
||||
// performance (as long as people follow spec, not behaviour)
|
||||
}
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
if (mStreamPosition < pPosition) {
|
||||
// Make sure we append at end of cache
|
||||
if (mCache.getFilePointer() != mStreamPosition) {
|
||||
mCache.seek(mStreamPosition);
|
||||
}
|
||||
|
||||
// Read diff from stream into cache
|
||||
long left = pPosition - mStreamPosition;
|
||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||
byte[] buffer = new byte[bufferLen];
|
||||
|
||||
while (left > 0) {
|
||||
int length = buffer.length < left ? buffer.length : (int) left;
|
||||
int read = mStream.read(buffer, 0, length);
|
||||
|
||||
if (read > 0) {
|
||||
mCache.write(buffer, 0, read);
|
||||
mStreamPosition += read;
|
||||
left -= read;
|
||||
}
|
||||
else if (read < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mStreamPosition >= pPosition) {
|
||||
// Seek backwards into the cache
|
||||
mCache.seek(pPosition);
|
||||
}
|
||||
|
||||
// System.out.println("pPosition: " + pPosition);
|
||||
// System.out.println("mStreamPosition: " + mStreamPosition);
|
||||
// System.out.println("mCache.getFilePointer(): " + mCache.getFilePointer());
|
||||
|
||||
// NOTE: If mPosition == pPosition then we're good to go
|
||||
}
|
||||
*/
|
||||
|
||||
final static class FileCache extends StreamCache {
|
||||
private RandomAccessFile mCacheFile;
|
||||
|
||||
+3
-3
@@ -87,7 +87,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long length = mRandomAccess.length() - mPosition;
|
||||
long length = mRandomAccess.length() - position;
|
||||
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
||||
|
||||
int read = mRandomAccess.read();
|
||||
if (read >= 0) {
|
||||
mPosition++;
|
||||
position++;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public final class FileSeekableStream extends SeekableInputStream {
|
||||
|
||||
int read = mRandomAccess.read(pBytes, pOffset, pLength);
|
||||
if (read > 0) {
|
||||
mPosition += read;
|
||||
position += read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
+17
-17
@@ -38,7 +38,7 @@ import java.io.InputStreamReader;
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSystem.java#1 $
|
||||
* @version $Id: FileSystem.java#1 $
|
||||
*/
|
||||
abstract class FileSystem {
|
||||
abstract long getFreeSpace(File pPath);
|
||||
@@ -57,21 +57,21 @@ abstract class FileSystem {
|
||||
//System.out.println("os = " + os);
|
||||
|
||||
os = os.toLowerCase();
|
||||
if (os.indexOf("windows") != -1) {
|
||||
if (os.contains("windows")) {
|
||||
return new Win32FileSystem();
|
||||
}
|
||||
else if (os.indexOf("linux") != -1 ||
|
||||
os.indexOf("sun os") != -1 ||
|
||||
os.indexOf("sunos") != -1 ||
|
||||
os.indexOf("solaris") != -1 ||
|
||||
os.indexOf("mpe/ix") != -1 ||
|
||||
os.indexOf("hp-ux") != -1 ||
|
||||
os.indexOf("aix") != -1 ||
|
||||
os.indexOf("freebsd") != -1 ||
|
||||
os.indexOf("irix") != -1 ||
|
||||
os.indexOf("digital unix") != -1 ||
|
||||
os.indexOf("unix") != -1 ||
|
||||
os.indexOf("mac os x") != -1) {
|
||||
else if (os.contains("linux") ||
|
||||
os.contains("sun os") ||
|
||||
os.contains("sunos") ||
|
||||
os.contains("solaris") ||
|
||||
os.contains("mpe/ix") ||
|
||||
os.contains("hp-ux") ||
|
||||
os.contains("aix") ||
|
||||
os.contains("freebsd") ||
|
||||
os.contains("irix") ||
|
||||
os.contains("digital unix") ||
|
||||
os.contains("unix") ||
|
||||
os.contains("mac os x")) {
|
||||
return new UnixFileSystem();
|
||||
}
|
||||
else {
|
||||
@@ -80,10 +80,10 @@ abstract class FileSystem {
|
||||
}
|
||||
|
||||
private static class UnknownFileSystem extends FileSystem {
|
||||
private final String mOSName;
|
||||
private final String osName;
|
||||
|
||||
UnknownFileSystem(String pOSName) {
|
||||
mOSName = pOSName;
|
||||
osName = pOSName;
|
||||
}
|
||||
|
||||
long getFreeSpace(File pPath) {
|
||||
@@ -95,7 +95,7 @@ abstract class FileSystem {
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return "Unknown (" + mOSName + ")";
|
||||
return "Unknown (" + osName + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
-36
@@ -79,7 +79,7 @@ public final class FileUtil {
|
||||
|
||||
/*
|
||||
* Method main for test only.
|
||||
*/
|
||||
*
|
||||
public static void main0(String[] pArgs) {
|
||||
if (pArgs.length != 2) {
|
||||
System.out.println("usage: java Copy in out");
|
||||
@@ -94,6 +94,7 @@ public final class FileUtil {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
//*/
|
||||
|
||||
// Avoid instances/constructor showing up in API doc
|
||||
private FileUtil() {}
|
||||
@@ -186,6 +187,7 @@ public final class FileUtil {
|
||||
if (!pOverWrite && pToFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
|
||||
@@ -202,7 +204,8 @@ public final class FileUtil {
|
||||
close(in);
|
||||
close(out);
|
||||
}
|
||||
return true; // If we got here, everything's probably okay.. ;-)
|
||||
|
||||
return true; // If we got here, everything is probably okay.. ;-)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,6 +310,8 @@ public final class FileUtil {
|
||||
Validate.notNull(pFrom, "from");
|
||||
Validate.notNull(pTo, "to");
|
||||
|
||||
// TODO: Consider using file channels for faster copy where possible
|
||||
|
||||
// Use buffer size two times byte array, to avoid i/o bottleneck
|
||||
// TODO: Consider letting the client decide as this is sometimes not a good thing!
|
||||
InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
|
||||
@@ -322,31 +327,9 @@ public final class FileUtil {
|
||||
// Flush out stream, to write any remaining buffered data
|
||||
out.flush();
|
||||
|
||||
return true; // If we got here, everything's probably okay.. ;-)
|
||||
return true; // If we got here, everything is probably okay.. ;-)
|
||||
}
|
||||
|
||||
/*
|
||||
// Consider using the example from
|
||||
// http://developer.java.sun.com/developer/Books/performance/ch04.pdf
|
||||
// Test if this is really faster. And what about a lot of concurrence?
|
||||
// Have a pool of buffers? :-)
|
||||
|
||||
static final int BUFF_SIZE = 100000;
|
||||
static final byte[] buffer = new byte[BUFF_SIZE];
|
||||
|
||||
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
while (true) {
|
||||
synchronized (buffer) {
|
||||
int amountRead = in.read(buffer);
|
||||
if (amountRead == -1) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, amountRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the file (type) extension of the given file.
|
||||
* A file extension is the part of the filename, after the last occurence
|
||||
@@ -568,6 +551,7 @@ public final class FileUtil {
|
||||
if (!pFile.exists()) {
|
||||
throw new FileNotFoundException(pFile.toString());
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[(int) pFile.length()];
|
||||
InputStream in = null;
|
||||
|
||||
@@ -586,6 +570,7 @@ public final class FileUtil {
|
||||
finally {
|
||||
close(in);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@@ -597,7 +582,7 @@ public final class FileUtil {
|
||||
* @throws IOException if an i/o error occurs during read.
|
||||
*/
|
||||
public static byte[] read(InputStream pInput) throws IOException {
|
||||
// Create bytearray
|
||||
// Create byte array
|
||||
ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
|
||||
|
||||
// Copy from stream to byte array
|
||||
@@ -685,28 +670,28 @@ public final class FileUtil {
|
||||
// a file array, which may throw OutOfMemoryExceptions for
|
||||
// large directories/in low memory situations
|
||||
class DeleteFilesVisitor implements Visitor<File> {
|
||||
private int mFailedCount = 0;
|
||||
private IOException mException = null;
|
||||
private int failedCount = 0;
|
||||
private IOException exception = null;
|
||||
|
||||
public void visit(final File pFile) {
|
||||
try {
|
||||
if (!delete(pFile, true)) {
|
||||
mFailedCount++;
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
mFailedCount++;
|
||||
if (mException == null) {
|
||||
mException = e;
|
||||
failedCount++;
|
||||
if (exception == null) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean succeeded() throws IOException {
|
||||
if (mException != null) {
|
||||
throw mException;
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
return mFailedCount == 0;
|
||||
return failedCount == 0;
|
||||
}
|
||||
}
|
||||
DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
|
||||
@@ -886,6 +871,8 @@ public final class FileUtil {
|
||||
return folder.listFiles();
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use regexp
|
||||
|
||||
FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
|
||||
return folder.listFiles(filter);
|
||||
}
|
||||
@@ -1029,6 +1016,7 @@ public final class FileUtil {
|
||||
* @return a human readable string representation
|
||||
*/
|
||||
public static String toHumanReadableSize(final long pSizeInBytes) {
|
||||
// TODO: Rewrite to use String.format?
|
||||
if (pSizeInBytes < 1024L) {
|
||||
return pSizeInBytes + " Bytes";
|
||||
}
|
||||
@@ -1053,7 +1041,7 @@ public final class FileUtil {
|
||||
private static ThreadLocal<NumberFormat> sNumberFormat = new ThreadLocal<NumberFormat>() {
|
||||
protected NumberFormat initialValue() {
|
||||
NumberFormat format = NumberFormat.getNumberInstance();
|
||||
// TODO: Consider making this locale/platfor specific, OR a method parameter...
|
||||
// TODO: Consider making this locale/platform specific, OR a method parameter...
|
||||
// format.setMaximumFractionDigits(2);
|
||||
format.setMaximumFractionDigits(0);
|
||||
return format;
|
||||
@@ -1075,6 +1063,7 @@ public final class FileUtil {
|
||||
*
|
||||
* @see com.twelvemonkeys.util.Visitor
|
||||
*/
|
||||
@SuppressWarnings({"ResultOfMethodCallIgnored"})
|
||||
public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor<File> pVisitor) {
|
||||
Validate.notNull(pDirectory, "directory");
|
||||
Validate.notNull(pVisitor, "visitor");
|
||||
+25
-22
@@ -56,13 +56,16 @@ import java.io.FilenameFilter;
|
||||
* @see File#list(java.io.FilenameFilter) java.io.File.list
|
||||
* @see FilenameFilter java.io.FilenameFilter
|
||||
* @see WildcardStringParser
|
||||
* @deprecated
|
||||
*/
|
||||
public class FilenameMaskFilter implements FilenameFilter {
|
||||
|
||||
// TODO: Rewrite to use regexp, or create new class
|
||||
|
||||
// Members
|
||||
private String[] mFilenameMasksForInclusion;
|
||||
private String[] mFilenameMasksForExclusion;
|
||||
private boolean mInclusion = true;
|
||||
private String[] filenameMasksForInclusion;
|
||||
private String[] filenameMasksForExclusion;
|
||||
private boolean inclusion = true;
|
||||
|
||||
|
||||
/**
|
||||
@@ -127,29 +130,29 @@ public class FilenameMaskFilter implements FilenameFilter {
|
||||
* @param pFilenameMasksForInclusion the filename masks to include
|
||||
*/
|
||||
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
|
||||
mFilenameMasksForInclusion = pFilenameMasksForInclusion;
|
||||
filenameMasksForInclusion = pFilenameMasksForInclusion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current inclusion masks
|
||||
*/
|
||||
public String[] getFilenameMasksForInclusion() {
|
||||
return mFilenameMasksForInclusion.clone();
|
||||
return filenameMasksForInclusion.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pFilenameMasksForExclusion the filename masks to exclude
|
||||
*/
|
||||
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
|
||||
mFilenameMasksForExclusion = pFilenameMasksForExclusion;
|
||||
mInclusion = false;
|
||||
filenameMasksForExclusion = pFilenameMasksForExclusion;
|
||||
inclusion = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current exclusion masks
|
||||
*/
|
||||
public String[] getFilenameMasksForExclusion() {
|
||||
return mFilenameMasksForExclusion.clone();
|
||||
return filenameMasksForExclusion.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,8 +167,8 @@ public class FilenameMaskFilter implements FilenameFilter {
|
||||
WildcardStringParser parser;
|
||||
|
||||
// Check each filename string mask whether the file is to be accepted
|
||||
if (mInclusion) { // Inclusion
|
||||
for (String mask : mFilenameMasksForInclusion) {
|
||||
if (inclusion) { // Inclusion
|
||||
for (String mask : filenameMasksForInclusion) {
|
||||
parser = new WildcardStringParser(mask);
|
||||
if (parser.parseString(pName)) {
|
||||
|
||||
@@ -181,7 +184,7 @@ public class FilenameMaskFilter implements FilenameFilter {
|
||||
}
|
||||
else {
|
||||
// Exclusion
|
||||
for (String mask : mFilenameMasksForExclusion) {
|
||||
for (String mask : filenameMasksForExclusion) {
|
||||
parser = new WildcardStringParser(mask);
|
||||
if (parser.parseString(pName)) {
|
||||
|
||||
@@ -204,32 +207,32 @@ public class FilenameMaskFilter implements FilenameFilter {
|
||||
StringBuilder retVal = new StringBuilder();
|
||||
int i;
|
||||
|
||||
if (mInclusion) {
|
||||
if (inclusion) {
|
||||
// Inclusion
|
||||
if (mFilenameMasksForInclusion == null) {
|
||||
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!");
|
||||
if (filenameMasksForInclusion == null) {
|
||||
retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
|
||||
}
|
||||
else {
|
||||
retVal.append(mFilenameMasksForInclusion.length);
|
||||
retVal.append(filenameMasksForInclusion.length);
|
||||
retVal.append(" filename mask(s) - ");
|
||||
for (i = 0; i < mFilenameMasksForInclusion.length; i++) {
|
||||
for (i = 0; i < filenameMasksForInclusion.length; i++) {
|
||||
retVal.append("\"");
|
||||
retVal.append(mFilenameMasksForInclusion[i]);
|
||||
retVal.append(filenameMasksForInclusion[i]);
|
||||
retVal.append("\", \"");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Exclusion
|
||||
if (mFilenameMasksForExclusion == null) {
|
||||
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!");
|
||||
if (filenameMasksForExclusion == null) {
|
||||
retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
|
||||
}
|
||||
else {
|
||||
retVal.append(mFilenameMasksForExclusion.length);
|
||||
retVal.append(filenameMasksForExclusion.length);
|
||||
retVal.append(" exclusion filename mask(s) - ");
|
||||
for (i = 0; i < mFilenameMasksForExclusion.length; i++) {
|
||||
for (i = 0; i < filenameMasksForExclusion.length; i++) {
|
||||
retVal.append("\"");
|
||||
retVal.append(mFilenameMasksForExclusion[i]);
|
||||
retVal.append(filenameMasksForExclusion[i]);
|
||||
retVal.append("\", \"");
|
||||
}
|
||||
}
|
||||
+41
-17
@@ -38,6 +38,8 @@
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
@@ -63,7 +65,8 @@ import java.io.*;
|
||||
* @see java.io.DataOutput
|
||||
*
|
||||
* @author Elliotte Rusty Harold
|
||||
* @version 1.0.3, 28 December 2002
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version 2
|
||||
*/
|
||||
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
|
||||
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
|
||||
@@ -75,10 +78,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public LittleEndianDataInputStream(final InputStream pStream) {
|
||||
super(pStream);
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
super(Validate.notNull(pStream, "stream"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,9 +93,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
*/
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = in.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
@@ -110,9 +112,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
*/
|
||||
public byte readByte() throws IOException {
|
||||
int b = in.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return (byte) b;
|
||||
|
||||
}
|
||||
@@ -128,9 +132,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
*/
|
||||
public int readUnsignedByte() throws IOException {
|
||||
int b = in.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -146,12 +152,14 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
public short readShort() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
|
||||
// only need to test last byte read
|
||||
// if byte1 is -1 so is byte2
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
||||
|
||||
return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,10 +174,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
public int readUnsignedShort() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
||||
|
||||
return (byte2 << 8) + byte1;
|
||||
}
|
||||
|
||||
@@ -185,10 +194,12 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
public char readChar() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
||||
|
||||
return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24));
|
||||
}
|
||||
|
||||
|
||||
@@ -210,8 +221,9 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
if (byte4 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte4 << 24) + ((byte3 << 24) >>> 8)
|
||||
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
||||
|
||||
return (byte4 << 24) | ((byte3 << 24) >>> 8)
|
||||
| ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,11 +248,11 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
if (byte8 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
||||
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
|
||||
|
||||
return (byte8 << 56) | ((byte7 << 56) >>> 8)
|
||||
| ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24)
|
||||
| ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40)
|
||||
| ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,16 +272,17 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
public String readUTF() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
int numbytes = (byte1 << 8) + byte2;
|
||||
char result[] = new char[numbytes];
|
||||
int numread = 0;
|
||||
int numchars = 0;
|
||||
|
||||
while (numread < numbytes) {
|
||||
|
||||
int c1 = readUnsignedByte();
|
||||
int c2, c3;
|
||||
|
||||
@@ -281,27 +294,34 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
}
|
||||
else if (test == 12 || test == 13) { // two bytes
|
||||
numread += 2;
|
||||
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
c2 = readUnsignedByte();
|
||||
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||
}
|
||||
else if (test == 14) { // three bytes
|
||||
numread += 3;
|
||||
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
c2 = readUnsignedByte();
|
||||
c3 = readUnsignedByte();
|
||||
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char)
|
||||
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
|
||||
result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
}
|
||||
else { // malformed
|
||||
throw new UTFDataFormatException();
|
||||
@@ -396,12 +416,16 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (count < pLength) {
|
||||
int read = in.read(pBytes, pOffset + count, pLength - count);
|
||||
|
||||
if (read < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
count += read;
|
||||
}
|
||||
}
|
||||
+26
-21
@@ -38,6 +38,8 @@
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
@@ -69,7 +71,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
/**
|
||||
* The number of bytes written so far to the little endian output stream.
|
||||
*/
|
||||
protected int mWritten;
|
||||
protected int bytesWritten;
|
||||
|
||||
/**
|
||||
* Creates a new little endian output stream and chains it to the
|
||||
@@ -79,10 +81,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public LittleEndianDataOutputStream(OutputStream pStream) {
|
||||
super(pStream);
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
super(Validate.notNull(pStream, "stream"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +92,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
*/
|
||||
public synchronized void write(int pByte) throws IOException {
|
||||
out.write(pByte);
|
||||
mWritten++;
|
||||
bytesWritten++;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,10 +104,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
* @param pLength the number of bytes to write.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public synchronized void write(byte[] pBytes, int pOffset, int pLength)
|
||||
throws IOException {
|
||||
public synchronized void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
out.write(pBytes, pOffset, pLength);
|
||||
mWritten += pLength;
|
||||
bytesWritten += pLength;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +135,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
*/
|
||||
public void writeByte(int pByte) throws IOException {
|
||||
out.write(pByte);
|
||||
mWritten++;
|
||||
bytesWritten++;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +148,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
public void writeShort(int pShort) throws IOException {
|
||||
out.write(pShort & 0xFF);
|
||||
out.write((pShort >>> 8) & 0xFF);
|
||||
mWritten += 2;
|
||||
bytesWritten += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,7 +161,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
public void writeChar(int pChar) throws IOException {
|
||||
out.write(pChar & 0xFF);
|
||||
out.write((pChar >>> 8) & 0xFF);
|
||||
mWritten += 2;
|
||||
bytesWritten += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +176,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
out.write((pInt >>> 8) & 0xFF);
|
||||
out.write((pInt >>> 16) & 0xFF);
|
||||
out.write((pInt >>> 24) & 0xFF);
|
||||
mWritten += 4;
|
||||
bytesWritten += 4;
|
||||
|
||||
}
|
||||
|
||||
@@ -198,7 +196,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
out.write((int) (pLong >>> 40) & 0xFF);
|
||||
out.write((int) (pLong >>> 48) & 0xFF);
|
||||
out.write((int) (pLong >>> 56) & 0xFF);
|
||||
mWritten += 8;
|
||||
bytesWritten += 8;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,10 +233,12 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
*/
|
||||
public void writeBytes(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
out.write((byte) pString.charAt(i));
|
||||
}
|
||||
mWritten += length;
|
||||
|
||||
bytesWritten += length;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,12 +253,14 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
*/
|
||||
public void writeChars(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = pString.charAt(i);
|
||||
out.write(c & 0xFF);
|
||||
out.write((c >>> 8) & 0xFF);
|
||||
}
|
||||
mWritten += length * 2;
|
||||
|
||||
bytesWritten += length * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +284,7 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
numbytes++;
|
||||
}
|
||||
@@ -299,8 +302,10 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
|
||||
out.write((numbytes >>> 8) & 0xFF);
|
||||
out.write(numbytes & 0xFF);
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
out.write(c);
|
||||
}
|
||||
@@ -308,16 +313,16 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
out.write(0xE0 | ((c >> 12) & 0x0F));
|
||||
out.write(0x80 | ((c >> 6) & 0x3F));
|
||||
out.write(0x80 | (c & 0x3F));
|
||||
mWritten += 2;
|
||||
bytesWritten += 2;
|
||||
}
|
||||
else {
|
||||
out.write(0xC0 | ((c >> 6) & 0x1F));
|
||||
out.write(0x80 | (c & 0x3F));
|
||||
mWritten += 1;
|
||||
bytesWritten += 1;
|
||||
}
|
||||
}
|
||||
|
||||
mWritten += numchars + 2;
|
||||
bytesWritten += numchars + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,9 +331,9 @@ public class LittleEndianDataOutputStream extends FilterOutputStream implements
|
||||
* possible that this number is temporarily less than the actual
|
||||
* number of bytes written.)
|
||||
* @return the value of the {@code written} field.
|
||||
* @see #mWritten
|
||||
* @see #bytesWritten
|
||||
*/
|
||||
public int size() {
|
||||
return mWritten;
|
||||
return bytesWritten;
|
||||
}
|
||||
}
|
||||
+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 $
|
||||
*/
|
||||
public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
private RandomAccessFile mFile;
|
||||
private RandomAccessFile file;
|
||||
|
||||
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
|
||||
this(FileUtil.resolve(pName), pMode);
|
||||
}
|
||||
|
||||
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
|
||||
mFile = new RandomAccessFile(pFile, pMode);
|
||||
file = new RandomAccessFile(pFile, pMode);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
mFile.close();
|
||||
file.close();
|
||||
}
|
||||
|
||||
public FileChannel getChannel() {
|
||||
return mFile.getChannel();
|
||||
return file.getChannel();
|
||||
}
|
||||
|
||||
public FileDescriptor getFD() throws IOException {
|
||||
return mFile.getFD();
|
||||
return file.getFD();
|
||||
}
|
||||
|
||||
public long getFilePointer() throws IOException {
|
||||
return mFile.getFilePointer();
|
||||
return file.getFilePointer();
|
||||
}
|
||||
|
||||
public long length() throws IOException {
|
||||
return mFile.length();
|
||||
return file.length();
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
return mFile.read();
|
||||
return file.read();
|
||||
}
|
||||
|
||||
public int read(final byte[] b) throws IOException {
|
||||
return mFile.read(b);
|
||||
return file.read(b);
|
||||
}
|
||||
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
return mFile.read(b, off, len);
|
||||
return file.read(b, off, len);
|
||||
}
|
||||
|
||||
public void readFully(final byte[] b) throws IOException {
|
||||
mFile.readFully(b);
|
||||
file.readFully(b);
|
||||
}
|
||||
|
||||
public void readFully(final byte[] b, final int off, final int len) throws IOException {
|
||||
mFile.readFully(b, off, len);
|
||||
file.readFully(b, off, len);
|
||||
}
|
||||
|
||||
public String readLine() throws IOException {
|
||||
return mFile.readLine();
|
||||
return file.readLine();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,10 +121,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = mFile.read();
|
||||
int b = file.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
@@ -138,10 +140,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public byte readByte() throws IOException {
|
||||
int b = mFile.read();
|
||||
int b = file.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return (byte) b;
|
||||
|
||||
}
|
||||
@@ -156,10 +160,12 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedByte() throws IOException {
|
||||
int b = mFile.read();
|
||||
int b = file.read();
|
||||
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -173,13 +179,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public short readShort() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte1 = file.read();
|
||||
int byte2 = file.read();
|
||||
|
||||
// only need to test last byte read
|
||||
// if byte1 is -1 so is byte2
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
@@ -193,11 +201,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedShort() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte1 = file.read();
|
||||
int byte2 = file.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
||||
return (byte2 << 8) + byte1;
|
||||
}
|
||||
@@ -212,11 +222,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public char readChar() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte1 = file.read();
|
||||
int byte2 = file.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
||||
}
|
||||
|
||||
@@ -231,16 +243,16 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readInt() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte3 = mFile.read();
|
||||
int byte4 = mFile.read();
|
||||
int byte1 = file.read();
|
||||
int byte2 = file.read();
|
||||
int byte3 = file.read();
|
||||
int byte4 = file.read();
|
||||
|
||||
if (byte4 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte4 << 24) + ((byte3 << 24) >>> 8)
|
||||
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
||||
|
||||
return (byte4 << 24) + ((byte3 << 24) >>> 8) + ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,18 +265,19 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public long readLong() throws IOException {
|
||||
long byte1 = mFile.read();
|
||||
long byte2 = mFile.read();
|
||||
long byte3 = mFile.read();
|
||||
long byte4 = mFile.read();
|
||||
long byte5 = mFile.read();
|
||||
long byte6 = mFile.read();
|
||||
long byte7 = mFile.read();
|
||||
long byte8 = mFile.read();
|
||||
long byte1 = file.read();
|
||||
long byte2 = file.read();
|
||||
long byte3 = file.read();
|
||||
long byte4 = file.read();
|
||||
long byte5 = file.read();
|
||||
long byte6 = file.read();
|
||||
long byte7 = file.read();
|
||||
long byte8 = file.read();
|
||||
|
||||
if (byte8 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
||||
@@ -287,11 +300,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public String readUTF() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte1 = file.read();
|
||||
int byte2 = file.read();
|
||||
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
int numbytes = (byte1 << 8) + byte2;
|
||||
char result[] = new char[numbytes];
|
||||
int numread = 0;
|
||||
@@ -310,27 +325,34 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
}
|
||||
else if (test == 12 || test == 13) { // two bytes
|
||||
numread += 2;
|
||||
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
c2 = readUnsignedByte();
|
||||
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||
}
|
||||
else if (test == 14) { // three bytes
|
||||
numread += 3;
|
||||
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
c2 = readUnsignedByte();
|
||||
c3 = readUnsignedByte();
|
||||
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char)
|
||||
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
|
||||
result[numchars++] = (char) (((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
}
|
||||
else { // malformed
|
||||
throw new UTFDataFormatException();
|
||||
@@ -378,27 +400,27 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* {@code 0} or if an I/O error occurs.
|
||||
*/
|
||||
public void seek(final long pos) throws IOException {
|
||||
mFile.seek(pos);
|
||||
file.seek(pos);
|
||||
}
|
||||
|
||||
public void setLength(final long newLength) throws IOException {
|
||||
mFile.setLength(newLength);
|
||||
file.setLength(newLength);
|
||||
}
|
||||
|
||||
public int skipBytes(final int n) throws IOException {
|
||||
return mFile.skipBytes(n);
|
||||
return file.skipBytes(n);
|
||||
}
|
||||
|
||||
public void write(final byte[] b) throws IOException {
|
||||
mFile.write(b);
|
||||
file.write(b);
|
||||
}
|
||||
|
||||
public void write(final byte[] b, final int off, final int len) throws IOException {
|
||||
mFile.write(b, off, len);
|
||||
file.write(b, off, len);
|
||||
}
|
||||
|
||||
public void write(final int b) throws IOException {
|
||||
mFile.write(b);
|
||||
file.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -425,7 +447,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeByte(int pByte) throws IOException {
|
||||
mFile.write(pByte);
|
||||
file.write(pByte);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,8 +458,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeShort(int pShort) throws IOException {
|
||||
mFile.write(pShort & 0xFF);
|
||||
mFile.write((pShort >>> 8) & 0xFF);
|
||||
file.write(pShort & 0xFF);
|
||||
file.write((pShort >>> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -448,8 +470,8 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeChar(int pChar) throws IOException {
|
||||
mFile.write(pChar & 0xFF);
|
||||
mFile.write((pChar >>> 8) & 0xFF);
|
||||
file.write(pChar & 0xFF);
|
||||
file.write((pChar >>> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,11 +482,10 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeInt(int pInt) throws IOException {
|
||||
mFile.write(pInt & 0xFF);
|
||||
mFile.write((pInt >>> 8) & 0xFF);
|
||||
mFile.write((pInt >>> 16) & 0xFF);
|
||||
mFile.write((pInt >>> 24) & 0xFF);
|
||||
|
||||
file.write(pInt & 0xFF);
|
||||
file.write((pInt >>> 8) & 0xFF);
|
||||
file.write((pInt >>> 16) & 0xFF);
|
||||
file.write((pInt >>> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -475,14 +496,14 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeLong(long pLong) throws IOException {
|
||||
mFile.write((int) pLong & 0xFF);
|
||||
mFile.write((int) (pLong >>> 8) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 16) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 24) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 32) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 40) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 48) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 56) & 0xFF);
|
||||
file.write((int) pLong & 0xFF);
|
||||
file.write((int) (pLong >>> 8) & 0xFF);
|
||||
file.write((int) (pLong >>> 16) & 0xFF);
|
||||
file.write((int) (pLong >>> 24) & 0xFF);
|
||||
file.write((int) (pLong >>> 32) & 0xFF);
|
||||
file.write((int) (pLong >>> 40) & 0xFF);
|
||||
file.write((int) (pLong >>> 48) & 0xFF);
|
||||
file.write((int) (pLong >>> 56) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -515,12 +536,13 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @param pString the {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeByte(int)
|
||||
* @see #mFile
|
||||
* @see #file
|
||||
*/
|
||||
public void writeBytes(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
mFile.write((byte) pString.charAt(i));
|
||||
file.write((byte) pString.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,14 +554,15 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
* @param pString a {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeChar(int)
|
||||
* @see #mFile
|
||||
* @see #file
|
||||
*/
|
||||
public void writeChars(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = pString.charAt(i);
|
||||
mFile.write(c & 0xFF);
|
||||
mFile.write((c >>> 8) & 0xFF);
|
||||
file.write(c & 0xFF);
|
||||
file.write((c >>> 8) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -564,6 +587,7 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
numbytes++;
|
||||
}
|
||||
@@ -579,21 +603,23 @@ public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
mFile.write((numbytes >>> 8) & 0xFF);
|
||||
mFile.write(numbytes & 0xFF);
|
||||
file.write((numbytes >>> 8) & 0xFF);
|
||||
file.write(numbytes & 0xFF);
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
mFile.write(c);
|
||||
file.write(c);
|
||||
}
|
||||
else if (c > 0x07FF) {
|
||||
mFile.write(0xE0 | ((c >> 12) & 0x0F));
|
||||
mFile.write(0x80 | ((c >> 6) & 0x3F));
|
||||
mFile.write(0x80 | (c & 0x3F));
|
||||
file.write(0xE0 | ((c >> 12) & 0x0F));
|
||||
file.write(0x80 | ((c >> 6) & 0x3F));
|
||||
file.write(0x80 | (c & 0x3F));
|
||||
}
|
||||
else {
|
||||
mFile.write(0xC0 | ((c >> 6) & 0x1F));
|
||||
mFile.write(0x80 | (c & 0x3F));
|
||||
file.write(0xC0 | ((c >> 6) & 0x1F));
|
||||
file.write(0x80 | (c & 0x3F));
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-31
@@ -65,13 +65,13 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
||||
final static class MemoryCache extends StreamCache {
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private final List<byte[]> mCache = new ArrayList<byte[]>();
|
||||
private long mLength;
|
||||
private long mPosition;
|
||||
private long mStart;
|
||||
private final List<byte[]> cache = new ArrayList<byte[]>();
|
||||
private long length;
|
||||
private long position;
|
||||
private long start;
|
||||
|
||||
private byte[] getBlock() throws IOException {
|
||||
final long currPos = mPosition - mStart;
|
||||
final long currPos = position - start;
|
||||
if (currPos < 0) {
|
||||
throw new IOException("StreamCache flushed before read position");
|
||||
}
|
||||
@@ -82,31 +82,31 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
||||
throw new IOException("Memory cache max size exceeded");
|
||||
}
|
||||
|
||||
if (index >= mCache.size()) {
|
||||
if (index >= cache.size()) {
|
||||
try {
|
||||
mCache.add(new byte[BLOCK_SIZE]);
|
||||
cache.add(new byte[BLOCK_SIZE]);
|
||||
// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
|
||||
// System.out.println("New total size: " + mCache.size() * BLOCK_SIZE + " (" + mCache.size() + " blocks)");
|
||||
// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)");
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
throw new IOException("No more memory for cache: " + mCache.size() * BLOCK_SIZE);
|
||||
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println("index: " + index);
|
||||
|
||||
return mCache.get((int) index);
|
||||
return cache.get((int) index);
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
||||
int idx = (int) (position % BLOCK_SIZE);
|
||||
buffer[idx] = (byte) pByte;
|
||||
mPosition++;
|
||||
position++;
|
||||
|
||||
if (mPosition > mLength) {
|
||||
mLength = mPosition;
|
||||
if (position > length) {
|
||||
length = position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,28 +115,28 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
||||
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
byte[] buffer = getBlock();
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
int index = (int) mPosition % BLOCK_SIZE;
|
||||
int index = (int) position % BLOCK_SIZE;
|
||||
if (index == 0) {
|
||||
buffer = getBlock();
|
||||
}
|
||||
buffer[index] = pBuffer[pOffset + i];
|
||||
|
||||
mPosition++;
|
||||
position++;
|
||||
}
|
||||
if (mPosition > mLength) {
|
||||
mLength = mPosition;
|
||||
if (position > length) {
|
||||
length = position;
|
||||
}
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (mPosition >= mLength) {
|
||||
if (position >= length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
||||
mPosition++;
|
||||
int idx = (int) (position % BLOCK_SIZE);
|
||||
position++;
|
||||
|
||||
return buffer[idx] & 0xff;
|
||||
}
|
||||
@@ -144,33 +144,33 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
||||
// TODO: OptimizeMe!!!
|
||||
@Override
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (mPosition >= mLength) {
|
||||
if (position >= length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int bufferPos = (int) (mPosition % BLOCK_SIZE);
|
||||
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||
|
||||
// Find maxIdx and simplify test in for-loop
|
||||
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), mLength - mPosition);
|
||||
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position);
|
||||
|
||||
int i;
|
||||
//for (i = 0; i < pLength && i < buffer.length - idx && i < mLength - mPosition; i++) {
|
||||
//for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) {
|
||||
for (i = 0; i < maxLen; i++) {
|
||||
pBytes[pOffset + i] = buffer[bufferPos + i];
|
||||
}
|
||||
|
||||
mPosition += i;
|
||||
position += i;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
if (pPosition < mStart) {
|
||||
if (pPosition < start) {
|
||||
throw new IOException("Seek before flush position");
|
||||
}
|
||||
mPosition = pPosition;
|
||||
position = pPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,14 +178,14 @@ public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStrea
|
||||
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
|
||||
|
||||
for (int i = 0; i < firstPos; i++) {
|
||||
mCache.remove(0);
|
||||
cache.remove(0);
|
||||
}
|
||||
|
||||
mStart = pPosition;
|
||||
start = pPosition;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return mPosition;
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-14
@@ -50,13 +50,12 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
||||
// TODO: Package private SeekableDelegate?
|
||||
|
||||
// TODO: Both read and write must update stream position
|
||||
//private int mPosition = -1;
|
||||
//private int position = -1;
|
||||
|
||||
/** This random access stream, wrapped in an {@code InputStream} */
|
||||
SeekableInputStream mInputView = null;
|
||||
SeekableInputStream inputView = null;
|
||||
/** This random access stream, wrapped in an {@code OutputStream} */
|
||||
SeekableOutputStream mOutputView = null;
|
||||
|
||||
SeekableOutputStream outputView = null;
|
||||
|
||||
// TODO: Create an Input and an Output interface matching InputStream and OutputStream?
|
||||
public int read() throws IOException {
|
||||
@@ -119,10 +118,10 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
||||
* @return a {@code SeekableInputStream} reading from this stream
|
||||
*/
|
||||
public final SeekableInputStream asInputStream() {
|
||||
if (mInputView == null) {
|
||||
mInputView = new InputStreamView(this);
|
||||
if (inputView == null) {
|
||||
inputView = new InputStreamView(this);
|
||||
}
|
||||
return mInputView;
|
||||
return inputView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,15 +133,15 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
||||
* @return a {@code SeekableOutputStream} writing to this stream
|
||||
*/
|
||||
public final SeekableOutputStream asOutputStream() {
|
||||
if (mOutputView == null) {
|
||||
mOutputView = new OutputStreamView(this);
|
||||
if (outputView == null) {
|
||||
outputView = new OutputStreamView(this);
|
||||
}
|
||||
return mOutputView;
|
||||
return outputView;
|
||||
}
|
||||
|
||||
static final class InputStreamView extends SeekableInputStream {
|
||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
||||
// TODO: Consider adding synchonization (on stream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on stream?
|
||||
// - Or should it rather just work on the views?
|
||||
// - Allow multiple views?
|
||||
|
||||
@@ -190,8 +189,8 @@ public abstract class RandomAccessStream implements Seekable, DataInput, DataOut
|
||||
}
|
||||
|
||||
static final class OutputStreamView extends SeekableOutputStream {
|
||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
||||
// TODO: Consider adding synchonization (on stream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on stream?
|
||||
// - Or should it rather just work on the views?
|
||||
// - Allow multiple views?
|
||||
|
||||
+38
-26
@@ -43,15 +43,15 @@ import java.util.Stack;
|
||||
public abstract class SeekableInputStream extends InputStream implements Seekable {
|
||||
|
||||
// TODO: It's at the moment not possible to create subclasses outside this
|
||||
// package, as there's no access to mPosition. mPosition needs to be
|
||||
// package, as there's no access to position. position needs to be
|
||||
// updated from the read/read/read methods...
|
||||
|
||||
/** The stream position in this stream */
|
||||
long mPosition;
|
||||
long mFlushedPosition;
|
||||
boolean mClosed;
|
||||
long position;
|
||||
long flushedPosition;
|
||||
boolean closed;
|
||||
|
||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
||||
protected Stack<Long> markedPositions = new Stack<Long>();
|
||||
|
||||
/// InputStream overrides
|
||||
@Override
|
||||
@@ -69,17 +69,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
* @throws IOException if an I/O exception occurs during skip
|
||||
*/
|
||||
@Override
|
||||
public final long skip(long pLength) throws IOException {
|
||||
long pos = mPosition;
|
||||
if (pos + pLength < mFlushedPosition) {
|
||||
public final long skip(final long pLength) throws IOException {
|
||||
long pos = position;
|
||||
long wantedPosition = pos + pLength;
|
||||
if (wantedPosition < flushedPosition) {
|
||||
throw new IOException("position < flushedPosition");
|
||||
}
|
||||
|
||||
// Stop at stream length for compatibility, even though it's allowed
|
||||
// Stop at stream length for compatibility, even though it might be allowed
|
||||
// to seek past end of stream
|
||||
seek(Math.min(pos + pLength, pos + available()));
|
||||
int available = available();
|
||||
if (available > 0) {
|
||||
seek(Math.min(wantedPosition, pos + available));
|
||||
}
|
||||
// TODO: Add optimization for streams with known length!
|
||||
else {
|
||||
// Slow mode...
|
||||
int toSkip = (int) Math.max(Math.min(pLength, 512), -512);
|
||||
while (toSkip > 0 && read() >= 0) {
|
||||
toSkip--;
|
||||
}
|
||||
}
|
||||
|
||||
return mPosition - pos;
|
||||
return position - pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,7 +100,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
|
||||
// TODO: We don't really need to do this.. Is it a good idea?
|
||||
try {
|
||||
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition));
|
||||
flushBefore(Math.max(position - pLimit, flushedPosition));
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignore, as it's not really critical
|
||||
@@ -111,29 +123,29 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
|
||||
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||
// but it's kind of inconsistent with reset that throws IOException...
|
||||
if (pPosition < mFlushedPosition) {
|
||||
if (pPosition < flushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||
}
|
||||
|
||||
seekImpl(pPosition);
|
||||
mPosition = pPosition;
|
||||
position = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void mark() {
|
||||
mMarkedPositions.push(mPosition);
|
||||
markedPositions.push(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void reset() throws IOException {
|
||||
checkOpen();
|
||||
if (!mMarkedPositions.isEmpty()) {
|
||||
long newPos = mMarkedPositions.pop();
|
||||
if (!markedPositions.isEmpty()) {
|
||||
long newPos = markedPositions.pop();
|
||||
|
||||
// NOTE: This is correct according to javax.imageio (IOException),
|
||||
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
|
||||
if (newPos < mFlushedPosition) {
|
||||
if (newPos < flushedPosition) {
|
||||
throw new IOException("Previous marked position has been discarded");
|
||||
}
|
||||
|
||||
@@ -150,7 +162,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
}
|
||||
|
||||
public final void flushBefore(long pPosition) throws IOException {
|
||||
if (pPosition < mFlushedPosition) {
|
||||
if (pPosition < flushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||
}
|
||||
if (pPosition > getStreamPosition()) {
|
||||
@@ -158,7 +170,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
}
|
||||
checkOpen();
|
||||
flushBeforeImpl(pPosition);
|
||||
mFlushedPosition = pPosition;
|
||||
flushedPosition = pPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,21 +184,21 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void flush() throws IOException {
|
||||
flushBefore(mFlushedPosition);
|
||||
flushBefore(flushedPosition);
|
||||
}
|
||||
|
||||
public final long getFlushedPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mFlushedPosition;
|
||||
return flushedPosition;
|
||||
}
|
||||
|
||||
public final long getStreamPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mPosition;
|
||||
return position;
|
||||
}
|
||||
|
||||
protected final void checkOpen() throws IOException {
|
||||
if (mClosed) {
|
||||
if (closed) {
|
||||
throw new IOException("closed");
|
||||
}
|
||||
}
|
||||
@@ -194,7 +206,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
@Override
|
||||
public final void close() throws IOException {
|
||||
checkOpen();
|
||||
mClosed = true;
|
||||
closed = true;
|
||||
closeImpl();
|
||||
}
|
||||
|
||||
@@ -211,7 +223,7 @@ public abstract class SeekableInputStream extends InputStream implements Seekabl
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (!mClosed) {
|
||||
if (!closed) {
|
||||
try {
|
||||
close();
|
||||
}
|
||||
+17
-17
@@ -43,11 +43,11 @@ import java.util.Stack;
|
||||
*/
|
||||
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
|
||||
// TODO: Implement
|
||||
long mPosition;
|
||||
long mFlushedPosition;
|
||||
boolean mClosed;
|
||||
long position;
|
||||
long flushedPosition;
|
||||
boolean closed;
|
||||
|
||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
||||
protected Stack<Long> markedPositions = new Stack<Long>();
|
||||
|
||||
/// Outputstream overrides
|
||||
@Override
|
||||
@@ -63,28 +63,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
||||
|
||||
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||
// but it's inconsistent with reset that throws IOException...
|
||||
if (pPosition < mFlushedPosition) {
|
||||
if (pPosition < flushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||
}
|
||||
|
||||
seekImpl(pPosition);
|
||||
mPosition = pPosition;
|
||||
position = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void mark() {
|
||||
mMarkedPositions.push(mPosition);
|
||||
markedPositions.push(position);
|
||||
}
|
||||
|
||||
public final void reset() throws IOException {
|
||||
checkOpen();
|
||||
if (!mMarkedPositions.isEmpty()) {
|
||||
long newPos = mMarkedPositions.pop();
|
||||
if (!markedPositions.isEmpty()) {
|
||||
long newPos = markedPositions.pop();
|
||||
|
||||
// TODO: This is correct according to javax.imageio (IOException),
|
||||
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
|
||||
if (newPos < mFlushedPosition) {
|
||||
if (newPos < flushedPosition) {
|
||||
throw new IOException("Previous marked position has been discarded!");
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
||||
}
|
||||
|
||||
public final void flushBefore(long pPosition) throws IOException {
|
||||
if (pPosition < mFlushedPosition) {
|
||||
if (pPosition < flushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||
}
|
||||
if (pPosition > getStreamPosition()) {
|
||||
@@ -101,28 +101,28 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
||||
}
|
||||
checkOpen();
|
||||
flushBeforeImpl(pPosition);
|
||||
mFlushedPosition = pPosition;
|
||||
flushedPosition = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||
|
||||
@Override
|
||||
public final void flush() throws IOException {
|
||||
flushBefore(mFlushedPosition);
|
||||
flushBefore(flushedPosition);
|
||||
}
|
||||
|
||||
public final long getFlushedPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mFlushedPosition;
|
||||
return flushedPosition;
|
||||
}
|
||||
|
||||
public final long getStreamPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mPosition;
|
||||
return position;
|
||||
}
|
||||
|
||||
protected final void checkOpen() throws IOException {
|
||||
if (mClosed) {
|
||||
if (closed) {
|
||||
throw new IOException("closed");
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public abstract class SeekableOutputStream extends OutputStream implements Seeka
|
||||
@Override
|
||||
public final void close() throws IOException {
|
||||
checkOpen();
|
||||
mClosed = true;
|
||||
closed = true;
|
||||
closeImpl();
|
||||
}
|
||||
|
||||
+46
-44
@@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
@@ -42,13 +44,13 @@ import java.io.Reader;
|
||||
*/
|
||||
public class StringArrayReader extends StringReader {
|
||||
|
||||
private StringReader mCurrent;
|
||||
private String[] mStrings;
|
||||
protected final Object mLock;
|
||||
private int mCurrentSting;
|
||||
private int mMarkedString;
|
||||
private int mMark;
|
||||
private int mNext;
|
||||
private StringReader current;
|
||||
private String[] strings;
|
||||
protected final Object finalLock;
|
||||
private int currentSting;
|
||||
private int markedString;
|
||||
private int mark;
|
||||
private int next;
|
||||
|
||||
/**
|
||||
* Create a new string array reader.
|
||||
@@ -57,28 +59,28 @@ public class StringArrayReader extends StringReader {
|
||||
*/
|
||||
public StringArrayReader(final String[] pStrings) {
|
||||
super("");
|
||||
if (pStrings == null) {
|
||||
throw new NullPointerException("strings == null");
|
||||
}
|
||||
|
||||
mLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
|
||||
Validate.notNull(pStrings, "strings");
|
||||
|
||||
finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
|
||||
// reference can't change, only it's elements
|
||||
|
||||
mStrings = pStrings.clone(); // Defensive copy for content
|
||||
strings = pStrings.clone(); // Defensive copy for content
|
||||
nextReader();
|
||||
}
|
||||
|
||||
protected final Reader nextReader() {
|
||||
if (mCurrentSting >= mStrings.length) {
|
||||
mCurrent = new EmptyReader();
|
||||
if (currentSting >= strings.length) {
|
||||
current = new EmptyReader();
|
||||
}
|
||||
else {
|
||||
mCurrent = new StringReader(mStrings[mCurrentSting++]);
|
||||
current = new StringReader(strings[currentSting++]);
|
||||
}
|
||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||
mNext = 0;
|
||||
|
||||
// NOTE: Reset next for every reader, and record marked reader in mark/reset methods!
|
||||
next = 0;
|
||||
|
||||
return mCurrent;
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,15 +89,15 @@ public class StringArrayReader extends StringReader {
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
protected final void ensureOpen() throws IOException {
|
||||
if (mStrings == null) {
|
||||
if (strings == null) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
super.close();
|
||||
mStrings = null;
|
||||
mCurrent.close();
|
||||
strings = null;
|
||||
current.close();
|
||||
}
|
||||
|
||||
public void mark(int pReadLimit) throws IOException {
|
||||
@@ -103,29 +105,29 @@ public class StringArrayReader extends StringReader {
|
||||
throw new IllegalArgumentException("Read limit < 0");
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
synchronized (finalLock) {
|
||||
ensureOpen();
|
||||
mMark = mNext;
|
||||
mMarkedString = mCurrentSting;
|
||||
mark = next;
|
||||
markedString = currentSting;
|
||||
|
||||
mCurrent.mark(pReadLimit);
|
||||
current.mark(pReadLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
synchronized (mLock) {
|
||||
synchronized (finalLock) {
|
||||
ensureOpen();
|
||||
|
||||
if (mCurrentSting != mMarkedString) {
|
||||
mCurrentSting = mMarkedString - 1;
|
||||
if (currentSting != markedString) {
|
||||
currentSting = markedString - 1;
|
||||
nextReader();
|
||||
mCurrent.skip(mMark);
|
||||
current.skip(mark);
|
||||
}
|
||||
else {
|
||||
mCurrent.reset();
|
||||
current.reset();
|
||||
}
|
||||
|
||||
mNext = mMark;
|
||||
next = mark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,49 +136,49 @@ public class StringArrayReader extends StringReader {
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read();
|
||||
synchronized (finalLock) {
|
||||
int read = current.read();
|
||||
|
||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
||||
if (read < 0 && currentSting < strings.length) {
|
||||
nextReader();
|
||||
return read(); // In case of empty strings
|
||||
}
|
||||
|
||||
mNext++;
|
||||
next++;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
||||
synchronized (finalLock) {
|
||||
int read = current.read(pBuffer, pOffset, pLength);
|
||||
|
||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
||||
if (read < 0 && currentSting < strings.length) {
|
||||
nextReader();
|
||||
return read(pBuffer, pOffset, pLength); // In case of empty strings
|
||||
}
|
||||
|
||||
mNext += read;
|
||||
next += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean ready() throws IOException {
|
||||
return mCurrent.ready();
|
||||
return current.ready();
|
||||
}
|
||||
|
||||
public long skip(long pChars) throws IOException {
|
||||
synchronized (mLock) {
|
||||
long skipped = mCurrent.skip(pChars);
|
||||
synchronized (finalLock) {
|
||||
long skipped = current.skip(pChars);
|
||||
|
||||
if (skipped == 0 && mCurrentSting < mStrings.length) {
|
||||
if (skipped == 0 && currentSting < strings.length) {
|
||||
nextReader();
|
||||
return skip(pChars);
|
||||
}
|
||||
|
||||
mNext += skipped;
|
||||
next += skipped;
|
||||
|
||||
return skipped;
|
||||
}
|
||||
+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 $
|
||||
*/
|
||||
public final class SubStream extends FilterInputStream {
|
||||
private long mLeft;
|
||||
private int mMarkLimit;
|
||||
private long bytesLeft;
|
||||
private int markLimit;
|
||||
|
||||
/**
|
||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
||||
@@ -54,7 +54,7 @@ public final class SubStream extends FilterInputStream {
|
||||
*/
|
||||
public SubStream(final InputStream pStream, final long pLength) {
|
||||
super(Validate.notNull(pStream, "stream"));
|
||||
mLeft = pLength;
|
||||
bytesLeft = pLength;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,32 +64,32 @@ public final class SubStream extends FilterInputStream {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// NOTE: Do not close the underlying stream
|
||||
while (mLeft > 0) {
|
||||
while (bytesLeft > 0) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
skip(mLeft);
|
||||
skip(bytesLeft);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) Math.min(super.available(), mLeft);
|
||||
return (int) Math.min(super.available(), bytesLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int pReadLimit) {
|
||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
||||
mMarkLimit = pReadLimit;
|
||||
markLimit = pReadLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
super.reset();// This either succeeds or throws IOException
|
||||
mLeft += mMarkLimit;
|
||||
bytesLeft += markLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (mLeft-- <= 0) {
|
||||
if (bytesLeft-- <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return super.read();
|
||||
@@ -102,12 +102,12 @@ public final class SubStream extends FilterInputStream {
|
||||
|
||||
@Override
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (mLeft <= 0) {
|
||||
if (bytesLeft <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||
mLeft = read < 0 ? 0 : mLeft - read;
|
||||
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
||||
return read;
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ public final class SubStream extends FilterInputStream {
|
||||
* @return the maximum number of bytes to read
|
||||
*/
|
||||
private long findMaxLen(long pLength) {
|
||||
if (mLeft < pLength) {
|
||||
return (int) Math.max(mLeft, 0);
|
||||
if (bytesLeft < pLength) {
|
||||
return (int) Math.max(bytesLeft, 0);
|
||||
}
|
||||
else {
|
||||
return pLength;
|
||||
@@ -129,7 +129,7 @@ public final class SubStream extends FilterInputStream {
|
||||
@Override
|
||||
public long skip(long pLength) throws IOException {
|
||||
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
||||
mLeft -= skipped;
|
||||
bytesLeft -= skipped;
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
+25
-24
@@ -50,7 +50,8 @@ final class Win32Lnk extends File {
|
||||
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
|
||||
};
|
||||
|
||||
private final File mTarget;
|
||||
private final File target;
|
||||
|
||||
private static final int FLAG_ITEM_ID_LIST = 0x01;
|
||||
private static final int FLAG_FILE_LOC_INFO = 0x02;
|
||||
private static final int FLAG_DESC_STRING = 0x04;
|
||||
@@ -65,10 +66,10 @@ final class Win32Lnk extends File {
|
||||
File target = parse(this);
|
||||
if (target == this) {
|
||||
// NOTE: This is a workaround
|
||||
// mTarget = this causes infinite loops in some methods
|
||||
// target = this causes infinite loops in some methods
|
||||
target = new File(pPath);
|
||||
}
|
||||
mTarget = target;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
Win32Lnk(final File pPath) throws IOException {
|
||||
@@ -336,24 +337,24 @@ final class Win32Lnk extends File {
|
||||
*/
|
||||
|
||||
public File getTarget() {
|
||||
return mTarget;
|
||||
return target;
|
||||
}
|
||||
|
||||
// java.io.File overrides below
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return mTarget.isDirectory();
|
||||
return target.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return mTarget.canRead();
|
||||
return target.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
return mTarget.canWrite();
|
||||
return target.canWrite();
|
||||
}
|
||||
|
||||
// NOTE: equals is implemented using compareto == 0
|
||||
@@ -362,7 +363,7 @@ final class Win32Lnk extends File {
|
||||
// TODO: Verify this
|
||||
// Probably not a good idea, as it IS NOT THE SAME file
|
||||
// It's probably better to not override
|
||||
return mTarget.compareTo(pathname);
|
||||
return target.compareTo(pathname);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -375,7 +376,7 @@ final class Win32Lnk extends File {
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return mTarget.exists();
|
||||
return target.exists();
|
||||
}
|
||||
|
||||
// A .lnk may be absolute
|
||||
@@ -385,12 +386,12 @@ final class Win32Lnk extends File {
|
||||
// Theses should be resolved according to the API (for Unix).
|
||||
@Override
|
||||
public File getCanonicalFile() throws IOException {
|
||||
return mTarget.getCanonicalFile();
|
||||
return target.getCanonicalFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() throws IOException {
|
||||
return mTarget.getCanonicalPath();
|
||||
return target.getCanonicalPath();
|
||||
}
|
||||
|
||||
//public String getName() {
|
||||
@@ -402,47 +403,47 @@ final class Win32Lnk extends File {
|
||||
// public boolean isAbsolute() {
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return mTarget.isFile();
|
||||
return target.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden() {
|
||||
return mTarget.isHidden();
|
||||
return target.isHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return mTarget.lastModified();
|
||||
return target.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mTarget.length();
|
||||
return target.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list() {
|
||||
return mTarget.list();
|
||||
return target.list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(final FilenameFilter filter) {
|
||||
return mTarget.list(filter);
|
||||
return target.list(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles() {
|
||||
return Win32File.wrap(mTarget.listFiles());
|
||||
return Win32File.wrap(target.listFiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FileFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
return Win32File.wrap(target.listFiles(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FilenameFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
return Win32File.wrap(target.listFiles(filter));
|
||||
}
|
||||
|
||||
// Makes no sense, does it?
|
||||
@@ -454,19 +455,19 @@ final class Win32Lnk extends File {
|
||||
|
||||
@Override
|
||||
public boolean setLastModified(long time) {
|
||||
return mTarget.setLastModified(time);
|
||||
return target.setLastModified(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly() {
|
||||
return mTarget.setReadOnly();
|
||||
return target.setReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mTarget.equals(this)) {
|
||||
if (target.equals(this)) {
|
||||
return super.toString();
|
||||
}
|
||||
return super.toString() + " -> " + mTarget.toString();
|
||||
return super.toString() + " -> " + target.toString();
|
||||
}
|
||||
}
|
||||
+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 $
|
||||
*/
|
||||
public class WriterOutputStream extends OutputStream {
|
||||
protected Writer mWriter;
|
||||
final protected Decoder mDecoder;
|
||||
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024);
|
||||
private volatile boolean mIsFlushing = false; // Ugly but critical...
|
||||
protected Writer writer;
|
||||
final protected Decoder decoder;
|
||||
final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
|
||||
|
||||
private volatile boolean isFlushing = false; // Ugly but critical...
|
||||
|
||||
private static final boolean NIO_AVAILABLE = isNIOAvailable();
|
||||
|
||||
@@ -71,8 +72,8 @@ public class WriterOutputStream extends OutputStream {
|
||||
}
|
||||
|
||||
public WriterOutputStream(final Writer pWriter, final String pCharset) {
|
||||
mWriter = pWriter;
|
||||
mDecoder = getDecoder(pCharset);
|
||||
writer = pWriter;
|
||||
decoder = getDecoder(pCharset);
|
||||
}
|
||||
|
||||
public WriterOutputStream(final Writer pWriter) {
|
||||
@@ -94,14 +95,14 @@ public class WriterOutputStream extends OutputStream {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
mWriter.close();
|
||||
mWriter = null;
|
||||
writer.close();
|
||||
writer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
flushBuffer();
|
||||
mWriter.flush();
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,22 +116,22 @@ public class WriterOutputStream extends OutputStream {
|
||||
@Override
|
||||
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
flushBuffer();
|
||||
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength);
|
||||
decoder.decodeTo(writer, pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(int pByte) {
|
||||
// TODO: Is it possible to know if this is a good place in the stream to
|
||||
// flush? It might be in the middle of a multi-byte encoded character..
|
||||
mBufferStream.write(pByte);
|
||||
bufferStream.write(pByte);
|
||||
}
|
||||
|
||||
private void flushBuffer() throws IOException {
|
||||
if (!mIsFlushing && mBufferStream.size() > 0) {
|
||||
mIsFlushing = true;
|
||||
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
|
||||
mBufferStream.reset();
|
||||
mIsFlushing = false;
|
||||
if (!isFlushing && bufferStream.size() > 0) {
|
||||
isFlushing = true;
|
||||
bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
|
||||
bufferStream.reset();
|
||||
isFlushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +139,7 @@ public class WriterOutputStream extends OutputStream {
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
int iterations = 1000000;
|
||||
|
||||
byte[] bytes = "åøæÅØÆ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
|
||||
byte[] bytes = "������ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
|
||||
|
||||
Decoder d;
|
||||
long start;
|
||||
+39
-39
@@ -31,6 +31,7 @@ package com.twelvemonkeys.io.enc;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
@@ -41,86 +42,85 @@ import java.io.InputStream;
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
abstract class AbstractRLEDecoder implements Decoder {
|
||||
protected final byte[] mRow;
|
||||
protected final int mWidth;
|
||||
protected int mSrcX;
|
||||
protected int mSrcY;
|
||||
protected int mDstX;
|
||||
protected int mDstY;
|
||||
protected final byte[] row;
|
||||
protected final int width;
|
||||
protected int srcX;
|
||||
protected int srcY;
|
||||
protected int dstX;
|
||||
protected int dstY;
|
||||
|
||||
/**
|
||||
* Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas,
|
||||
* Creates an RLEDecoder. As RLE encoded BMPs may contain x and y deltas,
|
||||
* etc, we need to know height and width of the image.
|
||||
*
|
||||
* @param pWidth width of the image
|
||||
* @param pHeight heigth of the image
|
||||
* @param pHeight height of the image
|
||||
*/
|
||||
AbstractRLEDecoder(int pWidth, int pHeight) {
|
||||
mWidth = pWidth;
|
||||
int bytesPerRow = mWidth;
|
||||
AbstractRLEDecoder(final int pWidth, final int pHeight) {
|
||||
width = pWidth;
|
||||
int bytesPerRow = width;
|
||||
int mod = bytesPerRow % 4;
|
||||
|
||||
if (mod != 0) {
|
||||
bytesPerRow += 4 - mod;
|
||||
}
|
||||
|
||||
mRow = new byte[bytesPerRow];
|
||||
row = new byte[bytesPerRow];
|
||||
|
||||
mSrcX = 0;
|
||||
mSrcY = pHeight - 1;
|
||||
srcX = 0;
|
||||
srcY = pHeight - 1;
|
||||
|
||||
mDstX = mSrcX;
|
||||
mDstY = mSrcY;
|
||||
dstX = srcX;
|
||||
dstY = srcY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes one full row of image data.
|
||||
*
|
||||
* @param pStream the input stream containint RLE data
|
||||
* @param pStream the input stream containing RLE data
|
||||
*
|
||||
* @throws IOException if an I/O related exception ocurs while reading
|
||||
* @throws IOException if an I/O related exception occurs while reading
|
||||
*/
|
||||
protected abstract void decodeRow(InputStream pStream) throws IOException;
|
||||
protected abstract void decodeRow(final InputStream pStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Decodes as much data as possible, from the stream into the buffer.
|
||||
*
|
||||
* @param pStream the input stream containing RLE data
|
||||
* @param pBuffer tge buffer to decode the data to
|
||||
* @param stream the input stream containing RLE data
|
||||
* @param buffer the buffer to decode the data to
|
||||
*
|
||||
* @return the number of bytes decoded from the stream, to the buffer
|
||||
*
|
||||
* @throws IOException if an I/O related exception ocurs while reading
|
||||
*/
|
||||
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
|
||||
int decoded = 0;
|
||||
|
||||
while (decoded < pBuffer.length && mDstY >= 0) {
|
||||
public final int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
while (buffer.hasRemaining() && dstY >= 0) {
|
||||
// NOTE: Decode only full rows, don't decode if y delta
|
||||
if (mDstX == 0 && mSrcY == mDstY) {
|
||||
decodeRow(pStream);
|
||||
if (dstX == 0 && srcY == dstY) {
|
||||
decodeRow(stream);
|
||||
}
|
||||
|
||||
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded);
|
||||
System.arraycopy(mRow, mDstX, pBuffer, decoded, length);
|
||||
mDstX += length;
|
||||
decoded += length;
|
||||
int length = Math.min(row.length - dstX, buffer.remaining());
|
||||
// System.arraycopy(row, dstX, buffer, decoded, length);
|
||||
buffer.put(row, 0, length);
|
||||
dstX += length;
|
||||
// decoded += length;
|
||||
|
||||
if (mDstX == mRow.length) {
|
||||
mDstX = 0;
|
||||
mDstY--;
|
||||
if (dstX == row.length) {
|
||||
dstX = 0;
|
||||
dstY--;
|
||||
|
||||
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
|
||||
// gap with zero-bytes
|
||||
if (mDstY > mSrcY) {
|
||||
for (int i = 0; i < mRow.length; i++) {
|
||||
mRow[i] = 0x00;
|
||||
if (dstY > srcY) {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
row[i] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ abstract class AbstractRLEDecoder implements Decoder {
|
||||
*
|
||||
* @throws EOFException if {@code pByte} is negative
|
||||
*/
|
||||
protected static int checkEOF(int pByte) throws EOFException {
|
||||
protected static int checkEOF(final int pByte) throws EOFException {
|
||||
if (pByte < 0) {
|
||||
throw new EOFException("Premature end of file");
|
||||
}
|
||||
+25
-38
@@ -28,9 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* {@code Decoder} implementation for standard base64 encoding.
|
||||
@@ -47,7 +47,7 @@ public final class Base64Decoder implements Decoder {
|
||||
/**
|
||||
* This array maps the characters to their 6 bit values
|
||||
*/
|
||||
final static char[] PEM_ARRAY = {
|
||||
final static byte[] PEM_ARRAY = {
|
||||
//0 1 2 3 4 5 6 7
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
|
||||
@@ -61,9 +61,7 @@ public final class Base64Decoder implements Decoder {
|
||||
|
||||
final static byte[] PEM_CONVERT_ARRAY;
|
||||
|
||||
private byte[] mDecodeBuffer = new byte[4];
|
||||
private ByteArrayOutputStream mWrapped;
|
||||
private Object mWrappedObject;
|
||||
private byte[] decodeBuffer = new byte[4];
|
||||
|
||||
static {
|
||||
PEM_CONVERT_ARRAY = new byte[256];
|
||||
@@ -93,7 +91,7 @@ public final class Base64Decoder implements Decoder {
|
||||
return pLength;
|
||||
}
|
||||
|
||||
protected boolean decodeAtom(final InputStream pInput, final OutputStream pOutput, final int pLength)
|
||||
protected boolean decodeAtom(final InputStream pInput, final ByteBuffer pOutput, final int pLength)
|
||||
throws IOException {
|
||||
|
||||
byte byte0 = -1;
|
||||
@@ -116,8 +114,8 @@ public final class Base64Decoder implements Decoder {
|
||||
}
|
||||
} while (read == 10 || read == 13);
|
||||
|
||||
mDecodeBuffer[0] = (byte) read;
|
||||
read = readFully(pInput, mDecodeBuffer, 1, pLength - 1);
|
||||
decodeBuffer[0] = (byte) read;
|
||||
read = readFully(pInput, decodeBuffer, 1, pLength - 1);
|
||||
|
||||
if (read == -1) {
|
||||
return false;
|
||||
@@ -125,38 +123,38 @@ public final class Base64Decoder implements Decoder {
|
||||
|
||||
int length = pLength;
|
||||
|
||||
if (length > 3 && mDecodeBuffer[3] == 61) {
|
||||
if (length > 3 && decodeBuffer[3] == 61) {
|
||||
length = 3;
|
||||
}
|
||||
|
||||
if (length > 2 && mDecodeBuffer[2] == 61) {
|
||||
if (length > 2 && decodeBuffer[2] == 61) {
|
||||
length = 2;
|
||||
}
|
||||
|
||||
switch (length) {
|
||||
case 4:
|
||||
byte3 = PEM_CONVERT_ARRAY[mDecodeBuffer[3] & 255];
|
||||
byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
|
||||
// fall through
|
||||
case 3:
|
||||
byte2 = PEM_CONVERT_ARRAY[mDecodeBuffer[2] & 255];
|
||||
byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
|
||||
// fall through
|
||||
case 2:
|
||||
byte1 = PEM_CONVERT_ARRAY[mDecodeBuffer[1] & 255];
|
||||
byte0 = PEM_CONVERT_ARRAY[mDecodeBuffer[0] & 255];
|
||||
byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
|
||||
byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
|
||||
// fall through
|
||||
default:
|
||||
switch (length) {
|
||||
case 2:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
break;
|
||||
case 3:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
break;
|
||||
case 4:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63));
|
||||
pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -166,34 +164,23 @@ public final class Base64Decoder implements Decoder {
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeBuffer(final InputStream pInput, final ByteArrayOutputStream pOutput, final int pLength) throws IOException {
|
||||
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
do {
|
||||
int k = 72;
|
||||
int i;
|
||||
|
||||
for (i = 0; i + 4 < k; i += 4) {
|
||||
if(!decodeAtom(pInput, pOutput, 4)) {
|
||||
if(!decodeAtom(stream, buffer, 4)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decodeAtom(pInput, pOutput, k - i)) {
|
||||
if (!decodeAtom(stream, buffer, k - i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (pOutput.size() + 54 < pLength); // 72 char lines should produce no more than 54 bytes
|
||||
}
|
||||
while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes
|
||||
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mWrappedObject != pBuffer) {
|
||||
// NOTE: Array not cloned in FastByteArrayOutputStream
|
||||
mWrapped = new FastByteArrayOutputStream(pBuffer);
|
||||
mWrappedObject = pBuffer;
|
||||
}
|
||||
|
||||
mWrapped.reset(); // NOTE: This only resets count to 0
|
||||
decodeBuffer(pStream, mWrapped, pBuffer.length);
|
||||
|
||||
return mWrapped.size();
|
||||
return buffer.position();
|
||||
}
|
||||
}
|
||||
+30
-36
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* {@code Encoder} implementation for standard base64 encoding.
|
||||
@@ -44,15 +45,9 @@ import java.io.IOException;
|
||||
*/
|
||||
public class Base64Encoder implements Encoder {
|
||||
|
||||
public void encode(final OutputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength)
|
||||
public void encode(final OutputStream stream, final ByteBuffer buffer)
|
||||
throws IOException
|
||||
{
|
||||
if (pOffset < 0 || pOffset > pLength || pOffset > pBuffer.length) {
|
||||
throw new IndexOutOfBoundsException("offset outside [0...length]");
|
||||
}
|
||||
else if (pLength > pBuffer.length) {
|
||||
throw new IndexOutOfBoundsException("length > buffer length");
|
||||
}
|
||||
|
||||
// TODO: Implement
|
||||
// NOTE: This is impossible, given the current spec, as we need to either:
|
||||
@@ -61,48 +56,47 @@ public class Base64Encoder implements Encoder {
|
||||
// to ensure proper end of stream handling
|
||||
|
||||
int length;
|
||||
int offset = pOffset;
|
||||
|
||||
// TODO: Temp impl, will only work for single writes
|
||||
while ((pBuffer.length - offset) > 0) {
|
||||
while (buffer.hasRemaining()) {
|
||||
byte a, b, c;
|
||||
|
||||
if ((pBuffer.length - offset) > 2) {
|
||||
length = 3;
|
||||
}
|
||||
else {
|
||||
length = pBuffer.length - offset;
|
||||
}
|
||||
// if ((buffer.remaining()) > 2) {
|
||||
// length = 3;
|
||||
// }
|
||||
// else {
|
||||
// length = buffer.remaining();
|
||||
// }
|
||||
length = Math.min(3, buffer.remaining());
|
||||
|
||||
switch (length) {
|
||||
case 1:
|
||||
a = pBuffer[offset];
|
||||
a = buffer.get();
|
||||
b = 0;
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write('=');
|
||||
pStream.write('=');
|
||||
offset++;
|
||||
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
stream.write('=');
|
||||
stream.write('=');
|
||||
break;
|
||||
|
||||
case 2:
|
||||
a = pBuffer[offset];
|
||||
b = pBuffer[offset + 1];
|
||||
a = buffer.get();
|
||||
b = buffer.get();
|
||||
c = 0;
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
pStream.write('=');
|
||||
offset += offset + 2; // ???
|
||||
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
stream.write('=');
|
||||
break;
|
||||
|
||||
default:
|
||||
a = pBuffer[offset];
|
||||
b = pBuffer[offset + 1];
|
||||
c = pBuffer[offset + 2];
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
|
||||
offset = offset + 3;
|
||||
a = buffer.get();
|
||||
b = buffer.get();
|
||||
c = buffer.get();
|
||||
stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -31,7 +31,7 @@ package com.twelvemonkeys.io.enc;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown by {@code Decoder}s when encoded data can not be decocded.
|
||||
* Thrown by {@code Decoder}s when encoded data can not be decoded.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
+7
-6
@@ -28,8 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface for decoders.
|
||||
@@ -47,18 +48,18 @@ import java.io.IOException;
|
||||
public interface Decoder {
|
||||
|
||||
/**
|
||||
* Decodes up to {@code pBuffer.length} bytes from the given inputstream,
|
||||
* Decodes up to {@code buffer.length} bytes from the given input stream,
|
||||
* into the given buffer.
|
||||
*
|
||||
* @param pStream the inputstream to decode data from
|
||||
* @param pBuffer buffer to store the read data
|
||||
* @param stream the input stream to decode data from
|
||||
* @param buffer buffer to store the read data
|
||||
*
|
||||
* @return the total number of bytes read into the buffer, or {@code -1}
|
||||
* @return the total number of bytes read into the buffer, or {@code 0}
|
||||
* if there is no more data because the end of the stream has been reached.
|
||||
*
|
||||
* @throws DecodeException if encoded data is corrupt
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @throws java.io.EOFException if a premature end-of-file is encountered
|
||||
*/
|
||||
int decode(InputStream pStream, byte[] pBuffer) throws IOException;
|
||||
int decode(InputStream stream, ByteBuffer buffer) throws IOException;
|
||||
}
|
||||
+38
-53
@@ -28,9 +28,10 @@
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* An {@code InputStream} that provides on-the-fly decoding from an underlying
|
||||
@@ -43,11 +44,8 @@ import java.io.FilterInputStream;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
||||
*/
|
||||
public final class DecoderStream extends FilterInputStream {
|
||||
|
||||
protected int mBufferPos;
|
||||
protected int mBufferLimit;
|
||||
protected final byte[] mBuffer;
|
||||
protected final Decoder mDecoder;
|
||||
protected final ByteBuffer buffer;
|
||||
protected final Decoder decoder;
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
@@ -76,30 +74,24 @@ public final class DecoderStream extends FilterInputStream {
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||
super(pStream);
|
||||
mDecoder = pDecoder;
|
||||
mBuffer = new byte[pBufferSize];
|
||||
mBufferPos = 0;
|
||||
mBufferLimit = 0;
|
||||
|
||||
decoder = pDecoder;
|
||||
buffer = ByteBuffer.allocate(pBufferSize);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return mBufferLimit - mBufferPos + super.available();
|
||||
return buffer.remaining();
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (mBufferPos == mBufferLimit) {
|
||||
mBufferLimit = fill();
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return mBuffer[mBufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
public int read(final byte pBytes[]) throws IOException {
|
||||
return read(pBytes, 0, pBytes.length);
|
||||
return buffer.get() & 0xff;
|
||||
}
|
||||
|
||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
||||
@@ -115,8 +107,10 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
// End of file?
|
||||
if ((mBufferLimit - mBufferPos) < 0) {
|
||||
return -1;
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Read until we have read pLength bytes, or have reached EOF
|
||||
@@ -124,26 +118,20 @@ public final class DecoderStream extends FilterInputStream {
|
||||
int off = pOffset;
|
||||
|
||||
while (pLength > count) {
|
||||
int avail = mBufferLimit - mBufferPos;
|
||||
|
||||
if (avail <= 0) {
|
||||
mBufferLimit = fill();
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy as many bytes as possible
|
||||
int dstLen = Math.min(pLength - count, avail);
|
||||
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen);
|
||||
|
||||
mBufferPos += dstLen;
|
||||
int dstLen = Math.min(pLength - count, buffer.remaining());
|
||||
buffer.get(pBytes, off, dstLen);
|
||||
|
||||
// Update offset (rest)
|
||||
off += dstLen;
|
||||
|
||||
// Inrease count
|
||||
// Increase count
|
||||
count += dstLen;
|
||||
}
|
||||
|
||||
@@ -152,29 +140,25 @@ public final class DecoderStream extends FilterInputStream {
|
||||
|
||||
public long skip(final long pLength) throws IOException {
|
||||
// End of file?
|
||||
if (mBufferLimit - mBufferPos < 0) {
|
||||
return 0;
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
return 0; // Yes, 0, not -1
|
||||
}
|
||||
}
|
||||
|
||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
||||
long total = 0;
|
||||
|
||||
while (total < pLength) {
|
||||
int avail = mBufferLimit - mBufferPos;
|
||||
|
||||
if (avail == 0) {
|
||||
mBufferLimit = fill();
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Skipped can never be more than avail, which is
|
||||
// an int, so the cast is safe
|
||||
int skipped = (int) Math.min(pLength - total, avail);
|
||||
|
||||
mBufferPos += skipped; // Just skip these bytes
|
||||
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
||||
int skipped = (int) Math.min(pLength - total, buffer.remaining());
|
||||
buffer.position(buffer.position() + skipped);
|
||||
total += skipped;
|
||||
}
|
||||
|
||||
@@ -190,19 +174,20 @@ public final class DecoderStream extends FilterInputStream {
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected int fill() throws IOException {
|
||||
int read = mDecoder.decode(in, mBuffer);
|
||||
buffer.clear();
|
||||
int read = decoder.decode(in, buffer);
|
||||
|
||||
// TODO: Enforce this in test case, leave here to aid debugging
|
||||
if (read > mBuffer.length) {
|
||||
if (read > buffer.capacity()) {
|
||||
throw new AssertionError(
|
||||
String.format(
|
||||
"Decode beyond buffer (%d): %d (using %s decoder)",
|
||||
mBuffer.length, read, mDecoder.getClass().getName()
|
||||
buffer.capacity(), read, decoder.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
mBufferPos = 0;
|
||||
buffer.flip();
|
||||
|
||||
if (read == 0) {
|
||||
return -1;
|
||||
+7
-9
@@ -30,11 +30,12 @@ package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Interface for endcoders.
|
||||
* Interface for encoders.
|
||||
* An {@code Encoder} may be used with an {@code EncoderStream}, to perform
|
||||
* on-the-fly enoding to an {@code OutputStream}.
|
||||
* on-the-fly encoding to an {@code OutputStream}.
|
||||
* <p/>
|
||||
* Important note: Encoder implementations are typically not synchronized.
|
||||
*
|
||||
@@ -47,18 +48,15 @@ import java.io.OutputStream;
|
||||
public interface Encoder {
|
||||
|
||||
/**
|
||||
* Encodes up to {@code pBuffer.length} bytes into the given input stream,
|
||||
* Encodes up to {@code buffer.remaining()} bytes into the given input stream,
|
||||
* from the given buffer.
|
||||
*
|
||||
* @param pStream the outputstream to encode data to
|
||||
* @param pBuffer buffer to read data from
|
||||
* @param pOffset offset into the buffer array
|
||||
* @param pLength length of data in the buffer
|
||||
* @param stream the output stream to encode data to
|
||||
* @param buffer buffer to read data from
|
||||
*
|
||||
* @throws java.io.IOException if an I/O error occurs
|
||||
*/
|
||||
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength)
|
||||
throws IOException;
|
||||
void encode(OutputStream stream, ByteBuffer buffer) throws IOException;
|
||||
|
||||
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
|
||||
// void flush()?
|
||||
+22
-22
@@ -29,8 +29,9 @@
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* An {@code OutputStream} that provides on-the-fly encoding to an underlying
|
||||
@@ -43,12 +44,12 @@ import java.io.IOException;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
||||
*/
|
||||
public final class EncoderStream extends FilterOutputStream {
|
||||
// TODO: This class need a test case ASAP!!!
|
||||
|
||||
protected final Encoder mEncoder;
|
||||
private final boolean mFlushOnWrite;
|
||||
protected final Encoder encoder;
|
||||
private final boolean flushOnWrite;
|
||||
|
||||
protected int mBufferPos;
|
||||
protected final byte[] mBuffer;
|
||||
protected final ByteBuffer buffer;
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
@@ -73,11 +74,11 @@ public final class EncoderStream extends FilterOutputStream {
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
||||
super(pStream);
|
||||
|
||||
mEncoder = pEncoder;
|
||||
mFlushOnWrite = pFlushOnWrite;
|
||||
encoder = pEncoder;
|
||||
flushOnWrite = pFlushOnWrite;
|
||||
|
||||
mBuffer = new byte[1024];
|
||||
mBufferPos = 0;
|
||||
buffer = ByteBuffer.allocate(1024);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
@@ -91,12 +92,14 @@ public final class EncoderStream extends FilterOutputStream {
|
||||
}
|
||||
|
||||
private void encodeBuffer() throws IOException {
|
||||
if (mBufferPos != 0) {
|
||||
if (buffer.position() != 0) {
|
||||
buffer.flip();
|
||||
|
||||
// Make sure all remaining data in buffer is written to the stream
|
||||
mEncoder.encode(out, mBuffer, 0, mBufferPos);
|
||||
encoder.encode(out, buffer);
|
||||
|
||||
// Reset buffer
|
||||
mBufferPos = 0;
|
||||
buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,27 +112,24 @@ public final class EncoderStream extends FilterOutputStream {
|
||||
// that the encoder can't buffer. In that case, the encoder should probably
|
||||
// tell the EncoderStream how large buffer it prefers...
|
||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (!mFlushOnWrite && mBufferPos + pLength < mBuffer.length) {
|
||||
if (!flushOnWrite && pLength < buffer.remaining()) {
|
||||
// Buffer data
|
||||
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength);
|
||||
mBufferPos += pLength;
|
||||
buffer.put(pBytes, pOffset, pLength);
|
||||
}
|
||||
else {
|
||||
// Encode data already in the buffer
|
||||
if (mBufferPos != 0) {
|
||||
encodeBuffer();
|
||||
}
|
||||
encodeBuffer();
|
||||
|
||||
// Encode rest without buffering
|
||||
mEncoder.encode(out, pBytes, pOffset, pLength);
|
||||
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
if (mBufferPos >= mBuffer.length - 1) {
|
||||
encodeBuffer(); // Resets mBufferPos to 0
|
||||
if (!buffer.hasRemaining()) {
|
||||
encodeBuffer(); // Resets bufferPos to 0
|
||||
}
|
||||
|
||||
mBuffer[mBufferPos++] = (byte) pByte;
|
||||
buffer.put((byte) pByte);
|
||||
}
|
||||
}
|
||||
+30
-29
@@ -28,9 +28,10 @@
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Decoder implementation for 16 bit-chunked Apple PackBits-like run-length
|
||||
@@ -46,11 +47,11 @@ import java.io.EOFException;
|
||||
*/
|
||||
public final class PackBits16Decoder implements Decoder {
|
||||
// TODO: Refactor this into an option for the PackBitsDecoder?
|
||||
private final boolean mDisableNoop;
|
||||
private final boolean disableNoop;
|
||||
|
||||
private int mLeftOfRun;
|
||||
private boolean mSplitRun;
|
||||
private boolean mEOF;
|
||||
private int leftOfRun;
|
||||
private boolean splitRun;
|
||||
private boolean reachedEOF;
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
@@ -71,40 +72,40 @@ public final class PackBits16Decoder implements Decoder {
|
||||
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBits16Decoder(final boolean pDisableNoop) {
|
||||
mDisableNoop = pDisableNoop;
|
||||
disableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes bytes from the given input stream, to the given buffer.
|
||||
*
|
||||
* @param pStream the stream to decode from
|
||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||
* @param stream the stream to decode from
|
||||
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||
* bytes long
|
||||
* @return The number of bytes decoded
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mEOF) {
|
||||
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
if (reachedEOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
final int max = pBuffer.length;
|
||||
final int max = buffer.capacity();
|
||||
|
||||
while (read < max) {
|
||||
int n;
|
||||
|
||||
if (mSplitRun) {
|
||||
if (splitRun) {
|
||||
// Continue run
|
||||
n = mLeftOfRun;
|
||||
mSplitRun = false;
|
||||
n = leftOfRun;
|
||||
splitRun = false;
|
||||
}
|
||||
else {
|
||||
// Start new run
|
||||
int b = pStream.read();
|
||||
int b = stream.read();
|
||||
if (b < 0) {
|
||||
mEOF = true;
|
||||
reachedEOF = true;
|
||||
break;
|
||||
}
|
||||
n = (byte) b;
|
||||
@@ -112,13 +113,13 @@ public final class PackBits16Decoder implements Decoder {
|
||||
|
||||
// Split run at or before max
|
||||
if (n >= 0 && 2 * (n + 1) + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
else if (n < 0 && 2 * (-n + 1) + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -126,18 +127,18 @@ public final class PackBits16Decoder implements Decoder {
|
||||
if (n >= 0) {
|
||||
// Copy next n + 1 shorts literally
|
||||
int len = 2 * (n + 1);
|
||||
readFully(pStream, pBuffer, read, len);
|
||||
readFully(stream, buffer, len);
|
||||
read += len;
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (mDisableNoop || n != -128) {
|
||||
else if (disableNoop || n != -128) {
|
||||
// Replicate the next short -n + 1 times
|
||||
byte value1 = readByte(pStream);
|
||||
byte value2 = readByte(pStream);
|
||||
byte value1 = readByte(stream);
|
||||
byte value2 = readByte(stream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
pBuffer[read++] = value1;
|
||||
pBuffer[read++] = value2;
|
||||
buffer.put(value1);
|
||||
buffer.put(value2);
|
||||
}
|
||||
}
|
||||
// else NOOP (-128)
|
||||
@@ -160,7 +161,7 @@ public final class PackBits16Decoder implements Decoder {
|
||||
return (byte) read;
|
||||
}
|
||||
|
||||
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
private static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
@@ -168,7 +169,7 @@ public final class PackBits16Decoder implements Decoder {
|
||||
int read = 0;
|
||||
|
||||
while (read < pLength) {
|
||||
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
|
||||
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + read, pLength - read);
|
||||
|
||||
if (count < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
+42
-45
@@ -28,9 +28,10 @@
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.EOFException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Decoder implementation for Apple PackBits run-length encoding.
|
||||
@@ -63,11 +64,13 @@ import java.io.EOFException;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $
|
||||
*/
|
||||
public final class PackBitsDecoder implements Decoder {
|
||||
private final boolean mDisableNoop;
|
||||
// TODO: Look at ICNSImageReader#unpackbits... What is this weirdness?
|
||||
|
||||
private int mLeftOfRun;
|
||||
private boolean mSplitRun;
|
||||
private boolean mEOF;
|
||||
private final boolean disableNoop;
|
||||
|
||||
private int leftOfRun;
|
||||
private boolean splitRun;
|
||||
private boolean reachedEOF;
|
||||
|
||||
/** Creates a {@code PackBitsDecoder}. */
|
||||
public PackBitsDecoder() {
|
||||
@@ -78,80 +81,72 @@ public final class PackBitsDecoder implements Decoder {
|
||||
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
|
||||
* <p/>
|
||||
* As some implementations of PackBits-like encoders treat {@code -128} as length of
|
||||
* a compressed run, instead of a no-op, it's possible to disable no-ops
|
||||
* for compatibility.
|
||||
* Should be used with caution, even though, most known encoders never write
|
||||
* no-ops in the compressed streams.
|
||||
* a compressed run, instead of a no-op, it's possible to disable no-ops for compatibility.
|
||||
* Should be used with caution, even though, most known encoders never write no-ops in the compressed streams.
|
||||
*
|
||||
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBitsDecoder(final boolean pDisableNoop) {
|
||||
mDisableNoop = pDisableNoop;
|
||||
disableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes bytes from the given input stream, to the given buffer.
|
||||
*
|
||||
* @param pStream the stream to decode from
|
||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||
* bytes long
|
||||
* @param stream the stream to decode from
|
||||
* @param buffer a byte array, minimum 128 (or 129 if no-op is disabled) bytes long
|
||||
* @return The number of bytes decoded
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mEOF) {
|
||||
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
if (reachedEOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
final int max = pBuffer.length;
|
||||
|
||||
// TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream...
|
||||
while (read < max) {
|
||||
while (buffer.hasRemaining()) {
|
||||
int n;
|
||||
|
||||
if (mSplitRun) {
|
||||
if (splitRun) {
|
||||
// Continue run
|
||||
n = mLeftOfRun;
|
||||
mSplitRun = false;
|
||||
n = leftOfRun;
|
||||
splitRun = false;
|
||||
}
|
||||
else {
|
||||
// Start new run
|
||||
int b = pStream.read();
|
||||
int b = stream.read();
|
||||
if (b < 0) {
|
||||
mEOF = true;
|
||||
reachedEOF = true;
|
||||
break;
|
||||
}
|
||||
n = (byte) b;
|
||||
}
|
||||
|
||||
// Split run at or before max
|
||||
if (n >= 0 && n + 1 + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
if (n >= 0 && n + 1 > buffer.remaining()) {
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
else if (n < 0 && -n + 1 + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
else if (n < 0 && -n + 1 > buffer.remaining()) {
|
||||
leftOfRun = n;
|
||||
splitRun = true;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
if (n >= 0) {
|
||||
// Copy next n + 1 bytes literally
|
||||
readFully(pStream, pBuffer, read, n + 1);
|
||||
|
||||
read += n + 1;
|
||||
readFully(stream, buffer, n + 1);
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (mDisableNoop || n != -128) {
|
||||
else if (disableNoop || n != -128) {
|
||||
// Replicate the next byte -n + 1 times
|
||||
byte value = readByte(pStream);
|
||||
byte value = readByte(stream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
pBuffer[read++] = value;
|
||||
buffer.put(value);
|
||||
}
|
||||
}
|
||||
// else NOOP (-128)
|
||||
@@ -161,10 +156,10 @@ public final class PackBitsDecoder implements Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
private static byte readByte(final InputStream pStream) throws IOException {
|
||||
static byte readByte(final InputStream pStream) throws IOException {
|
||||
int read = pStream.read();
|
||||
|
||||
if (read < 0) {
|
||||
@@ -174,21 +169,23 @@ public final class PackBitsDecoder implements Decoder {
|
||||
return (byte) read;
|
||||
}
|
||||
|
||||
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
static void readFully(final InputStream pStream, final ByteBuffer pBuffer, final int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
throw new IndexOutOfBoundsException(String.format("Negative length: %d", pLength));
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
int total = 0;
|
||||
|
||||
while (read < pLength) {
|
||||
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
|
||||
while (total < pLength) {
|
||||
int count = pStream.read(pBuffer.array(), pBuffer.arrayOffset() + pBuffer.position() + total, pLength - total);
|
||||
|
||||
if (count < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
read += count;
|
||||
total += count;
|
||||
}
|
||||
|
||||
pBuffer.position(pBuffer.position() + total);
|
||||
}
|
||||
}
|
||||
+12
-6
@@ -30,6 +30,7 @@ package com.twelvemonkeys.io.enc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Encoder implementation for Apple PackBits run-length encoding.
|
||||
@@ -63,7 +64,7 @@ import java.io.IOException;
|
||||
*/
|
||||
public final class PackBitsEncoder implements Encoder {
|
||||
|
||||
final private byte[] mBuffer = new byte[128];
|
||||
final private byte[] buffer = new byte[128];
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsEncoder}.
|
||||
@@ -71,7 +72,12 @@ public final class PackBitsEncoder implements Encoder {
|
||||
public PackBitsEncoder() {
|
||||
}
|
||||
|
||||
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
|
||||
encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
|
||||
buffer.position(buffer.remaining());
|
||||
}
|
||||
|
||||
private void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
// NOTE: It's best to encode a 2 byte repeat
|
||||
// run as a replicate run except when preceded and followed by a
|
||||
// literal run, in which case it's best to merge the three into one
|
||||
@@ -86,7 +92,7 @@ public final class PackBitsEncoder implements Encoder {
|
||||
// Compressed run
|
||||
int run = 1;
|
||||
byte replicate = pBuffer[offset];
|
||||
while(run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
|
||||
while (run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
|
||||
offset++;
|
||||
run++;
|
||||
}
|
||||
@@ -101,17 +107,17 @@ public final class PackBitsEncoder implements Encoder {
|
||||
run = 0;
|
||||
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|
||||
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
|
||||
mBuffer[run++] = pBuffer[offset++];
|
||||
buffer[run++] = pBuffer[offset++];
|
||||
}
|
||||
|
||||
// If last byte, include it in literal run, if space
|
||||
if (offset == max && run > 0 && run < 128) {
|
||||
mBuffer[run++] = pBuffer[offset++];
|
||||
buffer[run++] = pBuffer[offset++];
|
||||
}
|
||||
|
||||
if (run > 0) {
|
||||
pStream.write(run - 1);
|
||||
pStream.write(mBuffer, 0, run);
|
||||
pStream.write(buffer, 0, run);
|
||||
}
|
||||
|
||||
// If last byte, and not space, start new literal run
|
||||
+20
-20
@@ -32,7 +32,7 @@ import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 4 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
|
||||
* Implements 4 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -41,7 +41,7 @@ import java.io.IOException;
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE4Decoder(int pWidth, int pHeight) {
|
||||
public RLE4Decoder(final int pWidth, final int pHeight) {
|
||||
super((pWidth + 1) / 2, pHeight);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (mSrcY >= 0) {
|
||||
while (srcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
@@ -58,20 +58,20 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (mSrcX != 0) {
|
||||
mSrcX = mRow.length;
|
||||
if (srcX != 0) {
|
||||
srcX = row.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
mSrcX = mRow.length;
|
||||
mSrcY = 0;
|
||||
srcX = row.length;
|
||||
srcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = mSrcX + pInput.read();
|
||||
deltaY = mSrcY - checkEOF(pInput.read());
|
||||
mSrcX = mRow.length;
|
||||
deltaX = srcX + pInput.read();
|
||||
deltaY = srcY - checkEOF(pInput.read());
|
||||
srcX = row.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
@@ -82,13 +82,13 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
||||
while (byte2 > 1) {
|
||||
int packed = checkEOF(pInput.read());
|
||||
mRow[mSrcX++] = (byte) packed;
|
||||
row[srcX++] = (byte) packed;
|
||||
byte2 -= 2;
|
||||
}
|
||||
if (byte2 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
int packed = checkEOF(pInput.read());
|
||||
mRow[mSrcX++] = (byte) (packed & 0xf0);
|
||||
row[srcX++] = (byte) (packed & 0xf0);
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
@@ -100,24 +100,24 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
// Encoded mode
|
||||
// Replicate the two samples in byte2 as many times as byte1 says
|
||||
while (byte1 > 1) {
|
||||
mRow[mSrcX++] = (byte) byte2;
|
||||
row[srcX++] = (byte) byte2;
|
||||
byte1 -= 2;
|
||||
}
|
||||
|
||||
if (byte1 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
mRow[mSrcX++] = (byte) (byte2 & 0xf0);
|
||||
row[srcX++] = (byte) (byte2 & 0xf0);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (mSrcX == mRow.length) {
|
||||
if (srcX == row.length) {
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
mSrcX = (deltaX + 1) / 2;
|
||||
srcX = (deltaX + 1) / 2;
|
||||
|
||||
if (deltaY > mSrcY) {
|
||||
mSrcY = deltaY;
|
||||
if (deltaY > srcY) {
|
||||
srcY = deltaY;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -125,8 +125,8 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
mSrcX = 0;
|
||||
mSrcY--;
|
||||
srcX = 0;
|
||||
srcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
+18
-18
@@ -32,7 +32,7 @@ import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 8 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
|
||||
* Implements 8 bit RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -41,7 +41,7 @@ import java.io.IOException;
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE8Decoder(int pWidth, int pHeight) {
|
||||
public RLE8Decoder(final int pWidth, final int pHeight) {
|
||||
super(pWidth, pHeight);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (mSrcY >= 0) {
|
||||
while (srcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
@@ -58,27 +58,27 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (mSrcX != 0) {
|
||||
mSrcX = mRow.length;
|
||||
if (srcX != 0) {
|
||||
srcX = row.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
mSrcX = mRow.length;
|
||||
mSrcY = 0;
|
||||
srcX = row.length;
|
||||
srcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = mSrcX + pInput.read();
|
||||
deltaY = mSrcY - checkEOF(pInput.read());
|
||||
mSrcX = mRow.length;
|
||||
deltaX = srcX + pInput.read();
|
||||
deltaY = srcY - checkEOF(pInput.read());
|
||||
srcX = row.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
// Copy the next byte2 (3..255) bytes from file to output
|
||||
boolean paddingByte = (byte2 % 2) != 0;
|
||||
while (byte2-- > 0) {
|
||||
mRow[mSrcX++] = (byte) checkEOF(pInput.read());
|
||||
row[srcX++] = (byte) checkEOF(pInput.read());
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
@@ -90,26 +90,26 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
// Replicate byte2 as many times as byte1 says
|
||||
byte value = (byte) byte2;
|
||||
while (byte1-- > 0) {
|
||||
mRow[mSrcX++] = value;
|
||||
row[srcX++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (mSrcX == mRow.length) {
|
||||
if (srcX == row.length) {
|
||||
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
mSrcX = deltaX;
|
||||
if (deltaY != mSrcY) {
|
||||
mSrcY = deltaY;
|
||||
srcX = deltaX;
|
||||
if (deltaY != srcY) {
|
||||
srcY = deltaY;
|
||||
break;
|
||||
}
|
||||
deltaX = 0;
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
mSrcX = 0;
|
||||
mSrcY--;
|
||||
srcX = 0;
|
||||
srcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
+196
-193
@@ -54,41 +54,44 @@ public final class CompoundDocument {
|
||||
// TODO: Write support...
|
||||
// TODO: Properties: http://support.microsoft.com/kb/186898
|
||||
|
||||
private static final byte[] MAGIC = new byte[]{
|
||||
static final byte[] MAGIC = new byte[]{
|
||||
(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
|
||||
(byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
|
||||
};
|
||||
public static final int HEADER_SIZE = 512;
|
||||
|
||||
private final DataInput mInput;
|
||||
|
||||
private UUID mUID;
|
||||
|
||||
private int mSectorSize;
|
||||
private int mShortSectorSize;
|
||||
|
||||
private int mDirectorySId;
|
||||
|
||||
private int mMinStreamSize;
|
||||
|
||||
private int mShortSATSID;
|
||||
private int mShortSATSize;
|
||||
|
||||
// Master Sector Allocation Table
|
||||
private int[] mMasterSAT;
|
||||
private int[] mSAT;
|
||||
private int[] mShortSAT;
|
||||
|
||||
private Entry mRootEntry;
|
||||
private SIdChain mShortStreamSIdChain;
|
||||
private SIdChain mDirectorySIdChain;
|
||||
|
||||
private static final int END_OF_CHAIN_SID = -2;
|
||||
private static final int FREE_SID = -1;
|
||||
private static final int END_OF_CHAIN_SID = -2;
|
||||
private static final int SAT_SECTOR_SID = -3; // Sector used by SAT
|
||||
private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT
|
||||
|
||||
public static final int HEADER_SIZE = 512;
|
||||
|
||||
/** The epoch offset of CompoundDocument time stamps */
|
||||
public final static long EPOCH_OFFSET = -11644477200000L;
|
||||
|
||||
private final DataInput input;
|
||||
|
||||
private UUID uUID;
|
||||
|
||||
private int sectorSize;
|
||||
private int shortSectorSize;
|
||||
|
||||
private int directorySId;
|
||||
|
||||
private int minStreamSize;
|
||||
|
||||
private int shortSATSId;
|
||||
private int shortSATSize;
|
||||
|
||||
// Master Sector Allocation Table
|
||||
private int[] masterSAT;
|
||||
private int[] SAT;
|
||||
private int[] shortSAT;
|
||||
|
||||
private Entry rootEntry;
|
||||
private SIdChain shortStreamSIdChain;
|
||||
private SIdChain directorySIdChain;
|
||||
|
||||
/**
|
||||
* Creates a (for now) read only {@code CompoundDocument}.
|
||||
*
|
||||
@@ -97,7 +100,7 @@ public final class CompoundDocument {
|
||||
* @throws IOException if an I/O exception occurs while reading the header
|
||||
*/
|
||||
public CompoundDocument(final File pFile) throws IOException {
|
||||
mInput = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
|
||||
input = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
@@ -118,7 +121,7 @@ public final class CompoundDocument {
|
||||
|
||||
// For testing only, consider exposing later
|
||||
CompoundDocument(final SeekableInputStream pInput) throws IOException {
|
||||
mInput = new SeekableLittleEndianDataInputStream(pInput);
|
||||
input = new SeekableLittleEndianDataInputStream(pInput);
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
@@ -134,7 +137,7 @@ public final class CompoundDocument {
|
||||
* @throws IOException if an I/O exception occurs while reading the header
|
||||
*/
|
||||
public CompoundDocument(final ImageInputStream pInput) throws IOException {
|
||||
mInput = pInput;
|
||||
input = pInput;
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
@@ -210,74 +213,81 @@ public final class CompoundDocument {
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
if (mMasterSAT != null) {
|
||||
if (masterSAT != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canRead(mInput, false)) {
|
||||
if (!canRead(input, false)) {
|
||||
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
|
||||
}
|
||||
|
||||
// UID (seems to be all 0s)
|
||||
mUID = new UUID(mInput.readLong(), mInput.readLong());
|
||||
uUID = new UUID(input.readLong(), input.readLong());
|
||||
// System.out.println("uUID: " + uUID);
|
||||
|
||||
/*int version = */mInput.readUnsignedShort();
|
||||
//System.out.println("version: " + version);
|
||||
/*int revision = */mInput.readUnsignedShort();
|
||||
//System.out.println("revision: " + revision);
|
||||
// int version =
|
||||
input.readUnsignedShort();
|
||||
// System.out.println("version: " + version);
|
||||
// int revision =
|
||||
input.readUnsignedShort();
|
||||
// System.out.println("revision: " + revision);
|
||||
|
||||
int byteOrder = mInput.readUnsignedShort();
|
||||
if (byteOrder != 0xfffe) {
|
||||
// Reversed, as I'm allready reading little-endian
|
||||
int byteOrder = input.readUnsignedShort();
|
||||
// System.out.printf("byteOrder: 0x%04x\n", byteOrder);
|
||||
if (byteOrder == 0xffff) {
|
||||
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
|
||||
}
|
||||
else if (byteOrder != 0xfffe) {
|
||||
// Reversed, as I'm already reading little-endian
|
||||
throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder));
|
||||
}
|
||||
|
||||
mSectorSize = 1 << mInput.readUnsignedShort();
|
||||
//System.out.println("sectorSize: " + mSectorSize + " bytes");
|
||||
mShortSectorSize = 1 << mInput.readUnsignedShort();
|
||||
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes");
|
||||
sectorSize = 1 << input.readUnsignedShort();
|
||||
// System.out.println("sectorSize: " + sectorSize + " bytes");
|
||||
shortSectorSize = 1 << input.readUnsignedShort();
|
||||
// System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
|
||||
|
||||
// Reserved
|
||||
if (mInput.skipBytes(10) != 10) {
|
||||
if (skipBytesFully(10) != 10) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
|
||||
int SATSize = mInput.readInt();
|
||||
//System.out.println("normalSATSize: " + mSATSize);
|
||||
int SATSize = input.readInt();
|
||||
// System.out.println("normalSATSize: " + SATSize);
|
||||
|
||||
mDirectorySId = mInput.readInt();
|
||||
//System.out.println("directorySId: " + mDirectorySId);
|
||||
directorySId = input.readInt();
|
||||
// System.out.println("directorySId: " + directorySId);
|
||||
|
||||
// Reserved
|
||||
if (mInput.skipBytes(4) != 4) {
|
||||
if (skipBytesFully(4) != 4) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
|
||||
mMinStreamSize = mInput.readInt();
|
||||
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes");
|
||||
minStreamSize = input.readInt();
|
||||
// System.out.println("minStreamSize: " + minStreamSize + " bytes");
|
||||
|
||||
mShortSATSID = mInput.readInt();
|
||||
//System.out.println("shortSATSID: " + mShortSATSID);
|
||||
mShortSATSize = mInput.readInt();
|
||||
//System.out.println("shortSATSize: " + mShortSATSize);
|
||||
int masterSATSId = mInput.readInt();
|
||||
//System.out.println("masterSATSId: " + mMasterSATSID);
|
||||
int masterSATSize = mInput.readInt();
|
||||
//System.out.println("masterSATSize: " + mMasterSATSize);
|
||||
shortSATSId = input.readInt();
|
||||
// System.out.println("shortSATSId: " + shortSATSId);
|
||||
shortSATSize = input.readInt();
|
||||
// System.out.println("shortSATSize: " + shortSATSize);
|
||||
int masterSATSId = input.readInt();
|
||||
// System.out.println("masterSATSId: " + masterSATSId);
|
||||
int masterSATSize = input.readInt();
|
||||
// System.out.println("masterSATSize: " + masterSATSize);
|
||||
|
||||
// Read masterSAT: 436 bytes, containing up to 109 SIDs
|
||||
//System.out.println("MSAT:");
|
||||
mMasterSAT = new int[SATSize];
|
||||
masterSAT = new int[SATSize];
|
||||
final int headerSIds = Math.min(SATSize, 109);
|
||||
for (int i = 0; i < headerSIds; i++) {
|
||||
mMasterSAT[i] = mInput.readInt();
|
||||
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]);
|
||||
masterSAT[i] = input.readInt();
|
||||
//System.out.println("\tSID(" + i + "): " + masterSAT[i]);
|
||||
}
|
||||
|
||||
if (masterSATSId == END_OF_CHAIN_SID) {
|
||||
// End of chain
|
||||
int freeSIdLength = 436 - (SATSize * 4);
|
||||
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) {
|
||||
if (skipBytesFully(freeSIdLength) != freeSIdLength) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
}
|
||||
@@ -288,17 +298,17 @@ public final class CompoundDocument {
|
||||
int index = headerSIds;
|
||||
for (int i = 0; i < masterSATSize; i++) {
|
||||
for (int j = 0; j < 127; j++) {
|
||||
int sid = mInput.readInt();
|
||||
int sid = input.readInt();
|
||||
switch (sid) {
|
||||
case FREE_SID:// Free
|
||||
break;
|
||||
default:
|
||||
mMasterSAT[index++] = sid;
|
||||
masterSAT[index++] = sid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int next = mInput.readInt();
|
||||
int next = input.readInt();
|
||||
if (next == END_OF_CHAIN_SID) {// End of chain
|
||||
break;
|
||||
}
|
||||
@@ -308,38 +318,53 @@ public final class CompoundDocument {
|
||||
}
|
||||
}
|
||||
|
||||
private int skipBytesFully(final int n) throws IOException {
|
||||
int toSkip = n;
|
||||
|
||||
while (toSkip > 0) {
|
||||
int skipped = input.skipBytes(n);
|
||||
if (skipped <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
toSkip -= skipped;
|
||||
}
|
||||
|
||||
return n - toSkip;
|
||||
}
|
||||
|
||||
private void readSAT() throws IOException {
|
||||
if (mSAT != null) {
|
||||
if (SAT != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int intsPerSector = mSectorSize / 4;
|
||||
final int intsPerSector = sectorSize / 4;
|
||||
|
||||
// Read the Sector Allocation Table
|
||||
mSAT = new int[mMasterSAT.length * intsPerSector];
|
||||
SAT = new int[masterSAT.length * intsPerSector];
|
||||
|
||||
for (int i = 0; i < mMasterSAT.length; i++) {
|
||||
seekToSId(mMasterSAT[i], FREE_SID);
|
||||
for (int i = 0; i < masterSAT.length; i++) {
|
||||
seekToSId(masterSAT[i], FREE_SID);
|
||||
|
||||
for (int j = 0; j < intsPerSector; j++) {
|
||||
int nextSID = mInput.readInt();
|
||||
int nextSID = input.readInt();
|
||||
int index = (j + (i * intsPerSector));
|
||||
|
||||
mSAT[index] = nextSID;
|
||||
SAT[index] = nextSID;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the short-stream Sector Allocation Table
|
||||
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID);
|
||||
mShortSAT = new int[mShortSATSize * intsPerSector];
|
||||
for (int i = 0; i < mShortSATSize; i++) {
|
||||
SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
|
||||
shortSAT = new int[shortSATSize * intsPerSector];
|
||||
for (int i = 0; i < shortSATSize; i++) {
|
||||
seekToSId(chain.get(i), FREE_SID);
|
||||
|
||||
for (int j = 0; j < intsPerSector; j++) {
|
||||
int nextSID = mInput.readInt();
|
||||
int nextSID = input.readInt();
|
||||
int index = (j + (i * intsPerSector));
|
||||
|
||||
mShortSAT[index] = nextSID;
|
||||
shortSAT[index] = nextSID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,7 +380,7 @@ public final class CompoundDocument {
|
||||
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
|
||||
SIdChain chain = new SIdChain();
|
||||
|
||||
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT;
|
||||
int[] sat = isShortStream(pStreamSize) ? shortSAT : SAT;
|
||||
|
||||
int sid = pSId;
|
||||
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
|
||||
@@ -367,7 +392,7 @@ public final class CompoundDocument {
|
||||
}
|
||||
|
||||
private boolean isShortStream(final long pStreamSize) {
|
||||
return pStreamSize != FREE_SID && pStreamSize < mMinStreamSize;
|
||||
return pStreamSize != FREE_SID && pStreamSize < minStreamSize;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,70 +406,76 @@ public final class CompoundDocument {
|
||||
long pos;
|
||||
|
||||
if (isShortStream(pStreamSize)) {
|
||||
// The short-stream is not continouos...
|
||||
// The short stream is not continuous...
|
||||
Entry root = getRootEntry();
|
||||
if (mShortStreamSIdChain == null) {
|
||||
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
|
||||
if (shortStreamSIdChain == null) {
|
||||
shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
|
||||
}
|
||||
|
||||
int shortPerStd = mSectorSize / mShortSectorSize;
|
||||
int offset = pSId / shortPerStd;
|
||||
int shortOffset = pSId - (offset * shortPerStd);
|
||||
|
||||
// System.err.println("pSId: " + pSId);
|
||||
int shortPerSId = sectorSize / shortSectorSize;
|
||||
// System.err.println("shortPerSId: " + shortPerSId);
|
||||
int offset = pSId / shortPerSId;
|
||||
// System.err.println("offset: " + offset);
|
||||
int shortOffset = pSId - (offset * shortPerSId);
|
||||
// System.err.println("shortOffset: " + shortOffset);
|
||||
// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset));
|
||||
|
||||
pos = HEADER_SIZE
|
||||
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize)
|
||||
+ (shortOffset * (long) mShortSectorSize);
|
||||
+ (shortStreamSIdChain.get(offset) * (long) sectorSize)
|
||||
+ (shortOffset * (long) shortSectorSize);
|
||||
// System.err.println("pos: " + pos);
|
||||
}
|
||||
else {
|
||||
pos = HEADER_SIZE + pSId * (long) mSectorSize;
|
||||
pos = HEADER_SIZE + pSId * (long) sectorSize;
|
||||
}
|
||||
|
||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
||||
((LittleEndianRandomAccessFile) mInput).seek(pos);
|
||||
if (input instanceof LittleEndianRandomAccessFile) {
|
||||
((LittleEndianRandomAccessFile) input).seek(pos);
|
||||
}
|
||||
else if (mInput instanceof ImageInputStream) {
|
||||
((ImageInputStream) mInput).seek(pos);
|
||||
else if (input instanceof ImageInputStream) {
|
||||
((ImageInputStream) input).seek(pos);
|
||||
}
|
||||
else {
|
||||
((SeekableLittleEndianDataInputStream) mInput).seek(pos);
|
||||
((SeekableLittleEndianDataInputStream) input).seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void seekToDId(final int pDId) throws IOException {
|
||||
if (mDirectorySIdChain == null) {
|
||||
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID);
|
||||
if (directorySIdChain == null) {
|
||||
directorySIdChain = getSIdChain(directorySId, FREE_SID);
|
||||
}
|
||||
|
||||
int dIdsPerSId = mSectorSize / Entry.LENGTH;
|
||||
int dIdsPerSId = sectorSize / Entry.LENGTH;
|
||||
|
||||
int sIdOffset = pDId / dIdsPerSId;
|
||||
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
|
||||
|
||||
int sId = mDirectorySIdChain.get(sIdOffset);
|
||||
int sId = directorySIdChain.get(sIdOffset);
|
||||
|
||||
seekToSId(sId, FREE_SID);
|
||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
||||
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput;
|
||||
if (input instanceof LittleEndianRandomAccessFile) {
|
||||
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
|
||||
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
else if (mInput instanceof ImageInputStream) {
|
||||
ImageInputStream input = (ImageInputStream) mInput;
|
||||
else if (input instanceof ImageInputStream) {
|
||||
ImageInputStream input = (ImageInputStream) this.input;
|
||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
else {
|
||||
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput;
|
||||
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
|
||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
|
||||
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
|
||||
SIdChain chain = getSIdChain(pStreamId, pStreamSize);
|
||||
|
||||
// TODO: Detach? Means, we have to copy to a byte buffer, or keep track of
|
||||
// positions, and seek back and forth (would be cool, but difficult)..
|
||||
int sectorSize = pStreamSize < mMinStreamSize ? mShortSectorSize : mSectorSize;
|
||||
int sectorSize = pStreamSize < minStreamSize ? shortSectorSize : this.sectorSize;
|
||||
|
||||
return new Stream(chain, pStreamSize, sectorSize, this);
|
||||
return new MemoryCacheSeekableStream(new Stream(chain, pStreamSize, sectorSize, this));
|
||||
}
|
||||
|
||||
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
|
||||
@@ -453,7 +484,7 @@ public final class CompoundDocument {
|
||||
byte[] bytes = new byte[Entry.LENGTH];
|
||||
|
||||
seekToDId(pDirectoryId);
|
||||
mInput.readFully(bytes);
|
||||
input.readFully(bytes);
|
||||
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
@@ -462,8 +493,8 @@ public final class CompoundDocument {
|
||||
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
|
||||
getDirectoryStreamForDId(pDirectoryId)
|
||||
));
|
||||
entry.mParent = pParent;
|
||||
entry.mDocument = this;
|
||||
entry.parent = pParent;
|
||||
entry.document = this;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -527,21 +558,23 @@ public final class CompoundDocument {
|
||||
}
|
||||
|
||||
public Entry getRootEntry() throws IOException {
|
||||
if (mRootEntry == null) {
|
||||
if (rootEntry == null) {
|
||||
readSAT();
|
||||
|
||||
mRootEntry = getEntry(0, null);
|
||||
rootEntry = getEntry(0, null);
|
||||
|
||||
if (mRootEntry.type != Entry.ROOT_STORAGE) {
|
||||
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type);
|
||||
if (rootEntry.type != Entry.ROOT_STORAGE) {
|
||||
throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type);
|
||||
}
|
||||
}
|
||||
return mRootEntry;
|
||||
|
||||
return rootEntry;
|
||||
}
|
||||
|
||||
// This is useless, as most documents on file have all-zero UUIDs...
|
||||
// @Override
|
||||
// public int hashCode() {
|
||||
// return mUID.hashCode();
|
||||
// return uUID.hashCode();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
@@ -555,7 +588,7 @@ public final class CompoundDocument {
|
||||
// }
|
||||
//
|
||||
// if (pOther.getClass() == getClass()) {
|
||||
// return mUID.equals(((CompoundDocument) pOther).mUID);
|
||||
// return uUID.equals(((CompoundDocument) pOther).uUID);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
@@ -565,7 +598,7 @@ public final class CompoundDocument {
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
|
||||
getClass().getSimpleName(), mUID, mSectorSize, mShortSectorSize, mDirectorySId, mMasterSAT.length
|
||||
getClass().getSimpleName(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.length
|
||||
);
|
||||
}
|
||||
|
||||
@@ -601,29 +634,29 @@ public final class CompoundDocument {
|
||||
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
|
||||
}
|
||||
|
||||
// TODO: Enforce stream length!
|
||||
static class Stream extends SeekableInputStream {
|
||||
private SIdChain mChain;
|
||||
int mNextSectorPos;
|
||||
byte[] mBuffer;
|
||||
int mBufferPos;
|
||||
static class Stream extends InputStream {
|
||||
private final SIdChain chain;
|
||||
private final CompoundDocument document;
|
||||
private final long length;
|
||||
|
||||
private final CompoundDocument mDocument;
|
||||
private final long mLength;
|
||||
private long streamPos;
|
||||
private int nextSectorPos;
|
||||
private byte[] buffer;
|
||||
private int bufferPos;
|
||||
|
||||
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) {
|
||||
mChain = pChain;
|
||||
mLength = pLength;
|
||||
public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
|
||||
this.chain = chain;
|
||||
this.length = streamSize;
|
||||
|
||||
mBuffer = new byte[pSectorSize];
|
||||
mBufferPos = mBuffer.length;
|
||||
this.buffer = new byte[sectorSize];
|
||||
this.bufferPos = buffer.length;
|
||||
|
||||
mDocument = pDocument;
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) Math.min(mBuffer.length - mBufferPos, mLength - getStreamPosition());
|
||||
return (int) Math.min(buffer.length - bufferPos, length - streamPos);
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
@@ -633,20 +666,23 @@ public final class CompoundDocument {
|
||||
}
|
||||
}
|
||||
|
||||
return mBuffer[mBufferPos++] & 0xff;
|
||||
streamPos++;
|
||||
|
||||
return buffer[bufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
private boolean fillBuffer() throws IOException {
|
||||
if (mNextSectorPos < mChain.length()) {
|
||||
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-)
|
||||
// TODO: We also need to sync other places...
|
||||
synchronized (mDocument) {
|
||||
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength);
|
||||
mDocument.mInput.readFully(mBuffer);
|
||||
if (streamPos < length && nextSectorPos < chain.length()) {
|
||||
// TODO: Sync on document.input here, and we are completely detached... :-)
|
||||
// TODO: Update: We also need to sync other places... :-P
|
||||
synchronized (document) {
|
||||
document.seekToSId(chain.get(nextSectorPos), length);
|
||||
document.input.readFully(buffer);
|
||||
}
|
||||
|
||||
mNextSectorPos++;
|
||||
mBufferPos = 0;
|
||||
nextSectorPos++;
|
||||
bufferPos = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -663,99 +699,66 @@ public final class CompoundDocument {
|
||||
|
||||
int toRead = Math.min(len, available());
|
||||
|
||||
System.arraycopy(mBuffer, mBufferPos, b, off, toRead);
|
||||
mBufferPos += toRead;
|
||||
System.arraycopy(buffer, bufferPos, b, off, toRead);
|
||||
bufferPos += toRead;
|
||||
streamPos += toRead;
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mBuffer = null;
|
||||
mChain = null;
|
||||
}
|
||||
|
||||
protected void seekImpl(final long pPosition) throws IOException {
|
||||
long pos = getStreamPosition();
|
||||
|
||||
if (pos - mBufferPos >= pPosition && pPosition <= pos + available()) {
|
||||
// Skip inside buffer only
|
||||
mBufferPos += (pPosition - pos);
|
||||
}
|
||||
else {
|
||||
// Skip outside buffer
|
||||
mNextSectorPos = (int) (pPosition / mBuffer.length);
|
||||
if (!fillBuffer()) {
|
||||
throw new EOFException();
|
||||
}
|
||||
mBufferPos = (int) (pPosition % mBuffer.length);
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) throws IOException {
|
||||
// No need to do anything here
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add test case for this class!!!
|
||||
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
|
||||
private final SeekableInputStream mSeekable;
|
||||
private final SeekableInputStream seekable;
|
||||
|
||||
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
|
||||
super(pInput);
|
||||
mSeekable = pInput;
|
||||
seekable = pInput;
|
||||
}
|
||||
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
mSeekable.seek(pPosition);
|
||||
seekable.seek(pPosition);
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return mSeekable.isCachedFile();
|
||||
return seekable.isCachedFile();
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return mSeekable.isCachedMemory();
|
||||
return seekable.isCachedMemory();
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return mSeekable.isCached();
|
||||
return seekable.isCached();
|
||||
}
|
||||
|
||||
public long getStreamPosition() throws IOException {
|
||||
return mSeekable.getStreamPosition();
|
||||
return seekable.getStreamPosition();
|
||||
}
|
||||
|
||||
public long getFlushedPosition() throws IOException {
|
||||
return mSeekable.getFlushedPosition();
|
||||
return seekable.getFlushedPosition();
|
||||
}
|
||||
|
||||
public void flushBefore(final long pPosition) throws IOException {
|
||||
mSeekable.flushBefore(pPosition);
|
||||
seekable.flushBefore(pPosition);
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
mSeekable.flush();
|
||||
seekable.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
mSeekable.reset();
|
||||
seekable.reset();
|
||||
}
|
||||
|
||||
public void mark() {
|
||||
mSeekable.mark();
|
||||
seekable.mark();
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-30
@@ -32,6 +32,7 @@ import com.twelvemonkeys.io.SeekableInputStream;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@@ -61,9 +62,9 @@ public final class Entry implements Comparable<Entry> {
|
||||
int startSId;
|
||||
int streamSize;
|
||||
|
||||
CompoundDocument mDocument;
|
||||
Entry mParent;
|
||||
SortedSet<Entry> mChildren;
|
||||
CompoundDocument document;
|
||||
Entry parent;
|
||||
SortedSet<Entry> children;
|
||||
|
||||
public final static int LENGTH = 128;
|
||||
|
||||
@@ -99,28 +100,26 @@ public final class Entry implements Comparable<Entry> {
|
||||
* @throws IOException if an i/o exception occurs during reading
|
||||
*/
|
||||
private void read(final DataInput pInput) throws IOException {
|
||||
char[] chars = new char[32];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = pInput.readChar();
|
||||
}
|
||||
byte[] bytes = new byte[64];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
// NOTE: Length is in bytes, including the null-terminator...
|
||||
int nameLength = pInput.readShort();
|
||||
name = new String(chars, 0, (nameLength - 1) / 2);
|
||||
//System.out.println("name: " + name);
|
||||
name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
|
||||
// System.out.println("name: " + name);
|
||||
|
||||
type = pInput.readByte();
|
||||
//System.out.println("type: " + type);
|
||||
// System.out.println("type: " + type);
|
||||
|
||||
nodeColor = pInput.readByte();
|
||||
//System.out.println("nodeColor: " + nodeColor);
|
||||
// System.out.println("nodeColor: " + nodeColor);
|
||||
|
||||
prevDId = pInput.readInt();
|
||||
//System.out.println("prevDID: " + prevDID);
|
||||
// System.out.println("prevDId: " + prevDId);
|
||||
nextDId = pInput.readInt();
|
||||
//System.out.println("nextDID: " + nextDID);
|
||||
// System.out.println("nextDId: " + nextDId);
|
||||
rootNodeDId = pInput.readInt();
|
||||
//System.out.println("rootNodeDID: " + rootNodeDID);
|
||||
// System.out.println("rootNodeDId: " + rootNodeDId);
|
||||
|
||||
// UID (16) + user flags (4), ignored
|
||||
if (pInput.skipBytes(20) != 20) {
|
||||
@@ -131,9 +130,9 @@ public final class Entry implements Comparable<Entry> {
|
||||
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
|
||||
|
||||
startSId = pInput.readInt();
|
||||
//System.out.println("startSID: " + startSID);
|
||||
// System.out.println("startSId: " + startSId);
|
||||
streamSize = pInput.readInt();
|
||||
//System.out.println("streamSize: " + streamSize);
|
||||
// System.out.println("streamSize: " + streamSize);
|
||||
|
||||
// Reserved
|
||||
pInput.readInt();
|
||||
@@ -186,11 +185,11 @@ public final class Entry implements Comparable<Entry> {
|
||||
* @see #length()
|
||||
*/
|
||||
public SeekableInputStream getInputStream() throws IOException {
|
||||
if (isDirectory()) {
|
||||
if (!isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mDocument.getInputStreamForSId(startSId, streamSize);
|
||||
return document.getInputStreamForSId(startSId, streamSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,9 +200,10 @@ public final class Entry implements Comparable<Entry> {
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
public long length() {
|
||||
if (isDirectory()) {
|
||||
if (!isFile()) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
return streamSize;
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ public final class Entry implements Comparable<Entry> {
|
||||
* the root {@code Entry}
|
||||
*/
|
||||
public Entry getParentEntry() {
|
||||
return mParent;
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +266,7 @@ public final class Entry implements Comparable<Entry> {
|
||||
|
||||
Entry dummy = new Entry();
|
||||
dummy.name = pName;
|
||||
dummy.mParent = this;
|
||||
dummy.parent = this;
|
||||
|
||||
SortedSet child = getChildEntries().tailSet(dummy);
|
||||
return (Entry) child.first();
|
||||
@@ -279,26 +279,26 @@ public final class Entry implements Comparable<Entry> {
|
||||
* @throws java.io.IOException if an I/O exception occurs
|
||||
*/
|
||||
public SortedSet<Entry> getChildEntries() throws IOException {
|
||||
if (mChildren == null) {
|
||||
if (children == null) {
|
||||
if (isFile() || rootNodeDId == -1) {
|
||||
mChildren = NO_CHILDREN;
|
||||
children = NO_CHILDREN;
|
||||
}
|
||||
else {
|
||||
// Start at root node in R/B tree, and raed to the left and right,
|
||||
// Start at root node in R/B tree, and read to the left and right,
|
||||
// re-build tree, according to the docs
|
||||
mChildren = mDocument.getEntries(rootNodeDId, this);
|
||||
children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
|
||||
}
|
||||
}
|
||||
|
||||
return mChildren;
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\"" + name + "\""
|
||||
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
|
||||
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "")
|
||||
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)"))
|
||||
+ (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
|
||||
+ (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.size()) : "(unknown)"))
|
||||
+ ", SId=" + startSId + ", length=" + streamSize + ")";
|
||||
}
|
||||
|
||||
@@ -312,8 +312,8 @@ public final class Entry implements Comparable<Entry> {
|
||||
}
|
||||
|
||||
Entry other = (Entry) pOther;
|
||||
return name.equals(other.name) && (mParent == other.mParent
|
||||
|| (mParent != null && mParent.equals(other.mParent)));
|
||||
return name.equals(other.name) && (parent == other.parent
|
||||
|| (parent != null && parent.equals(other.parent)));
|
||||
}
|
||||
|
||||
@Override
|
||||
+1
-1
@@ -37,7 +37,7 @@ import java.util.NoSuchElementException;
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $
|
||||
*/
|
||||
class SIdChain {
|
||||
final class SIdChain {
|
||||
int[] chain;
|
||||
int size = 0;
|
||||
int next = 0;
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.net;
|
||||
|
||||
import com.twelvemonkeys.lang.DateUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* HTTPUtil
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: HTTPUtil.java,v 1.0 08.09.13 13:57 haraldk Exp$
|
||||
*/
|
||||
public class HTTPUtil {
|
||||
/**
|
||||
* RFC 1123 date format, as recommended by RFC 2616 (HTTP/1.1), sec 3.3
|
||||
* NOTE: All date formats are private, to ensure synchronized access.
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_RFC1123_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
|
||||
static {
|
||||
HTTP_RFC1123_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 850 date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3
|
||||
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_RFC850_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z", Locale.US);
|
||||
/**
|
||||
* ANSI C asctime() date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3.
|
||||
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
|
||||
*/
|
||||
private static final SimpleDateFormat HTTP_ASCTIME_FORMAT = new SimpleDateFormat("EEE MMM d HH:mm:ss yy", Locale.US);
|
||||
|
||||
private static long sNext50YearWindowChange = DateUtil.currentTimeDay();
|
||||
static {
|
||||
HTTP_RFC850_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
HTTP_ASCTIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3:
|
||||
// - HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
|
||||
// which appears to be more than 50 years in the future is in fact
|
||||
// in the past (this helps solve the "year 2000" problem).
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
|
||||
private static void update50YearWindowIfNeeded() {
|
||||
// Avoid class synchronization
|
||||
long next = sNext50YearWindowChange;
|
||||
|
||||
if (next < System.currentTimeMillis()) {
|
||||
// Next check in one day
|
||||
next += DateUtil.DAY;
|
||||
sNext50YearWindowChange = next;
|
||||
|
||||
Date startDate = new Date(next - (50l * DateUtil.CALENDAR_YEAR));
|
||||
//System.out.println("next test: " + new Date(next) + ", 50 year start: " + startDate);
|
||||
synchronized (HTTP_RFC850_FORMAT) {
|
||||
HTTP_RFC850_FORMAT.set2DigitYearStart(startDate);
|
||||
}
|
||||
synchronized (HTTP_ASCTIME_FORMAT) {
|
||||
HTTP_ASCTIME_FORMAT.set2DigitYearStart(startDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HTTPUtil() {}
|
||||
|
||||
/**
|
||||
* Formats the time to a HTTP date, using the RFC 1123 format, as described
|
||||
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
|
||||
*
|
||||
* @param pTime the time
|
||||
* @return a {@code String} representation of the time
|
||||
*/
|
||||
public static String formatHTTPDate(long pTime) {
|
||||
return formatHTTPDate(new Date(pTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the time to a HTTP date, using the RFC 1123 format, as described
|
||||
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
|
||||
*
|
||||
* @param pTime the time
|
||||
* @return a {@code String} representation of the time
|
||||
*/
|
||||
public static String formatHTTPDate(Date pTime) {
|
||||
synchronized (HTTP_RFC1123_FORMAT) {
|
||||
return HTTP_RFC1123_FORMAT.format(pTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a HTTP date string into a {@code long} representing milliseconds
|
||||
* since January 1, 1970 GMT.
|
||||
* <p>
|
||||
* Use this method with headers that contain dates, such as
|
||||
* {@code If-Modified-Since} or {@code Last-Modified}.
|
||||
* <p>
|
||||
* The date string may be in either RFC 1123, RFC 850 or ANSI C asctime()
|
||||
* format, as described in
|
||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
|
||||
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>
|
||||
*
|
||||
* @param pDate the date to parse
|
||||
*
|
||||
* @return a {@code long} value representing the date, expressed as the
|
||||
* number of milliseconds since January 1, 1970 GMT,
|
||||
* @throws NumberFormatException if the date parameter is not parseable.
|
||||
* @throws IllegalArgumentException if the date paramter is {@code null}
|
||||
*/
|
||||
public static long parseHTTPDate(String pDate) throws NumberFormatException {
|
||||
return parseHTTPDateImpl(pDate).getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* ParseHTTPDate implementation
|
||||
*
|
||||
* @param pDate the date string to parse
|
||||
*
|
||||
* @return a {@code Date}
|
||||
* @throws NumberFormatException if the date parameter is not parseable.
|
||||
* @throws IllegalArgumentException if the date paramter is {@code null}
|
||||
*/
|
||||
private static Date parseHTTPDateImpl(final String pDate) throws NumberFormatException {
|
||||
if (pDate == null) {
|
||||
throw new IllegalArgumentException("date == null");
|
||||
}
|
||||
|
||||
if (StringUtil.isEmpty(pDate)) {
|
||||
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
}
|
||||
|
||||
DateFormat format;
|
||||
|
||||
if (pDate.indexOf('-') >= 0) {
|
||||
format = HTTP_RFC850_FORMAT;
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
else if (pDate.indexOf(',') < 0) {
|
||||
format = HTTP_ASCTIME_FORMAT;
|
||||
update50YearWindowIfNeeded();
|
||||
}
|
||||
else {
|
||||
format = HTTP_RFC1123_FORMAT;
|
||||
// NOTE: RFC1123 always uses 4-digit years
|
||||
}
|
||||
|
||||
Date date;
|
||||
try {
|
||||
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||
synchronized (format) {
|
||||
date = format.parse(pDate);
|
||||
}
|
||||
}
|
||||
catch (ParseException e) {
|
||||
NumberFormatException nfe = new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
nfe.initCause(e);
|
||||
throw nfe;
|
||||
}
|
||||
|
||||
if (date == null) {
|
||||
throw new NumberFormatException("Invalid HTTP date: \"" + pDate + "\"");
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
}
|
||||
+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_XML_DECLARATION = "xml-declaration";
|
||||
|
||||
private final LSSerializer mSerializer;
|
||||
private final LSOutput mOutput;
|
||||
private final LSSerializer serializer;
|
||||
private final LSOutput output;
|
||||
|
||||
private DOMSerializer() {
|
||||
DOMImplementationLS domImpl = Support.getImplementation();
|
||||
mSerializer = domImpl.createLSSerializer();
|
||||
mOutput = domImpl.createLSOutput();
|
||||
serializer = domImpl.createLSSerializer();
|
||||
output = domImpl.createLSOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,8 +71,8 @@ public final class DOMSerializer {
|
||||
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
|
||||
this();
|
||||
|
||||
mOutput.setByteStream(pStream);
|
||||
mOutput.setEncoding(pEncoding);
|
||||
output.setByteStream(pStream);
|
||||
output.setEncoding(pEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,17 +84,17 @@ public final class DOMSerializer {
|
||||
public DOMSerializer(final Writer pStream) {
|
||||
this();
|
||||
|
||||
mOutput.setCharacterStream(pStream);
|
||||
output.setCharacterStream(pStream);
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Is it useful?
|
||||
public void setNewLine(final String pNewLine) {
|
||||
mSerializer.setNewLine(pNewLine);
|
||||
serializer.setNewLine(pNewLine);
|
||||
}
|
||||
|
||||
public String getNewLine() {
|
||||
return mSerializer.getNewLine();
|
||||
return serializer.getNewLine();
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -107,18 +107,18 @@ public final class DOMSerializer {
|
||||
* @param pPrettyPrint {@code true} to enable pretty printing
|
||||
*/
|
||||
public void setPrettyPrint(final boolean pPrettyPrint) {
|
||||
DOMConfiguration configuration = mSerializer.getDomConfig();
|
||||
DOMConfiguration configuration = serializer.getDomConfig();
|
||||
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
|
||||
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getPrettyPrint() {
|
||||
return Boolean.TRUE.equals(mSerializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
|
||||
return Boolean.TRUE.equals(serializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private void setXMLDeclaration(boolean pXMLDeclaration) {
|
||||
mSerializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
|
||||
serializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +142,7 @@ public final class DOMSerializer {
|
||||
|
||||
private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
|
||||
setXMLDeclaration(pOmitDecl);
|
||||
mSerializer.write(pNode, mOutput);
|
||||
serializer.write(pNode, output);
|
||||
}
|
||||
|
||||
private static class Support {
|
||||
+24
-39
@@ -59,28 +59,25 @@ public class XMLSerializer {
|
||||
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
||||
// to be more compatible...
|
||||
|
||||
// TODO: Idea: Create a SerializationContext that stores attributes on
|
||||
// serialization, to keep the serialization thread-safe
|
||||
// Store preserveSpace attribute in this context, to avoid costly traversals
|
||||
// Store user options here too
|
||||
// TODO: Push/pop?
|
||||
|
||||
private final OutputStream mOutput;
|
||||
private final Charset mEncoding;
|
||||
private final SerializationContext mContext;
|
||||
private final OutputStream output;
|
||||
private final Charset encoding;
|
||||
private final SerializationContext context;
|
||||
|
||||
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
|
||||
mOutput = pOutput;
|
||||
mEncoding = Charset.forName(pEncoding);
|
||||
mContext = new SerializationContext();
|
||||
output = pOutput;
|
||||
encoding = Charset.forName(pEncoding);
|
||||
context = new SerializationContext();
|
||||
}
|
||||
|
||||
public final void setIndentation(String pIndent) {
|
||||
mContext.indent = pIndent != null ? pIndent : " ";
|
||||
public final XMLSerializer indentation(String pIndent) {
|
||||
// TODO: Verify that indent value is only whitespace?
|
||||
context.indent = pIndent != null ? pIndent : "\t";
|
||||
return this;
|
||||
}
|
||||
|
||||
public final void setStripComments(boolean pStrip) {
|
||||
mContext.stripComments = pStrip;
|
||||
public final XMLSerializer stripComments(boolean pStrip) {
|
||||
context.stripComments = pStrip;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,12 +98,12 @@ public class XMLSerializer {
|
||||
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
|
||||
*/
|
||||
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding));
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(output, encoding));
|
||||
try {
|
||||
if (pWriteXMLDeclaration) {
|
||||
writeXMLDeclaration(out);
|
||||
}
|
||||
writeXML(out, pRootNode, mContext.copy());
|
||||
writeXML(out, pRootNode, context.copy());
|
||||
}
|
||||
finally {
|
||||
out.flush();
|
||||
@@ -115,7 +112,7 @@ public class XMLSerializer {
|
||||
|
||||
private void writeXMLDeclaration(final PrintWriter pOut) {
|
||||
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
||||
pOut.print(mEncoding.name());
|
||||
pOut.print(encoding.name());
|
||||
pOut.println("\"?>");
|
||||
}
|
||||
|
||||
@@ -279,11 +276,7 @@ public class XMLSerializer {
|
||||
pos = appendAndEscape(pValue, pos, i, builder, ">");
|
||||
break;
|
||||
//case '\'':
|
||||
// pos = appendAndEscape(pString, pos, i, builder, "'");
|
||||
// break;
|
||||
//case '"':
|
||||
// pos = appendAndEscape(pString, pos, i, builder, """);
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -347,17 +340,6 @@ public class XMLSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
//StringBuilder builder = new StringBuilder(pValue.length() + 30);
|
||||
//
|
||||
//int start = 0;
|
||||
//while (end >= 0) {
|
||||
// builder.append(pValue.substring(start, end));
|
||||
// builder.append(""");
|
||||
// start = end + 1;
|
||||
// end = pValue.indexOf('"', start);
|
||||
//}
|
||||
//builder.append(pValue.substring(start));
|
||||
|
||||
builder.append(pValue.substring(pos));
|
||||
|
||||
return builder.toString();
|
||||
@@ -389,14 +371,14 @@ public class XMLSerializer {
|
||||
}
|
||||
|
||||
private static String validateCDataValue(final String pValue) {
|
||||
if (pValue.indexOf("]]>") >= 0) {
|
||||
if (pValue.contains("]]>")) {
|
||||
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
|
||||
}
|
||||
return pValue;
|
||||
}
|
||||
|
||||
private static String validateCommentValue(final String pValue) {
|
||||
if (pValue.indexOf("--") >= 0) {
|
||||
if (pValue.contains("--")) {
|
||||
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
|
||||
}
|
||||
return pValue;
|
||||
@@ -420,8 +402,6 @@ public class XMLSerializer {
|
||||
// even if the document was created using attributes instead of namespaces...
|
||||
// In that case, prefix will be null...
|
||||
|
||||
// TODO: Don't insert duplicate/unnecessary namesspace declarations
|
||||
|
||||
// Handle namespace
|
||||
String namespace = pNode.getNamespaceURI();
|
||||
if (namespace != null && !namespace.equals(pContext.defaultNamespace)) {
|
||||
@@ -570,6 +550,11 @@ public class XMLSerializer {
|
||||
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
|
||||
test.appendChild(pre);
|
||||
|
||||
Element pre2 = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "tight");
|
||||
pre2.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
|
||||
pre2.appendChild(document.createTextNode("no-space-around-me"));
|
||||
test.appendChild(pre2);
|
||||
|
||||
// Create serializer and output document
|
||||
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
|
||||
System.out.println("XMLSerializer:");
|
||||
@@ -612,7 +597,7 @@ public class XMLSerializer {
|
||||
}
|
||||
|
||||
static class SerializationContext implements Cloneable {
|
||||
String indent = " ";
|
||||
String indent = "\t";
|
||||
int level = 0;
|
||||
boolean preserveSpace = false;
|
||||
boolean stripComments = false;
|
||||
+1
-1
@@ -60,7 +60,7 @@ iff,ilbm=image/x-iff;image/iff
|
||||
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
||||
jpm=image/jpm
|
||||
png=image/png;image/x-png
|
||||
# NOTE: image/svg-xml is an old reccomendation, should not be used
|
||||
# NOTE: image/svg-xml is an old recommendation, should not be used
|
||||
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
|
||||
tga=image/targa;image/x-targa
|
||||
tif,tiff=image/tiff;image/x-tiff
|
||||
+6
-1
@@ -2,6 +2,7 @@ package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.CollectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.IOException;
|
||||
@@ -9,6 +10,8 @@ import java.io.StringReader;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* CompoundReaderTestCase
|
||||
* <p/>
|
||||
@@ -18,7 +21,6 @@ import java.util.ArrayList;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/CompoundReaderTestCase.java#2 $
|
||||
*/
|
||||
public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
||||
|
||||
protected Reader makeReader(String pInput) {
|
||||
// Split
|
||||
String[] input = StringUtil.toStringArray(pInput, " ");
|
||||
@@ -36,6 +38,7 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
||||
return new CompoundReader(readers.iterator());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullConstructor() {
|
||||
try {
|
||||
new CompoundReader(null);
|
||||
@@ -46,11 +49,13 @@ public class CompoundReaderTestCase extends ReaderAbstractTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyIteratorConstructor() throws IOException {
|
||||
Reader reader = new CompoundReader(CollectionUtil.iterator(new Reader[0]));
|
||||
assertEquals(-1, reader.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIteratorWithNullConstructor() throws IOException {
|
||||
try {
|
||||
new CompoundReader(CollectionUtil.iterator(new Reader[] {null}));
|
||||
+5
@@ -1,8 +1,12 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* FastByteArrayOutputStreamTestCase
|
||||
* <p/>
|
||||
@@ -16,6 +20,7 @@ public class FastByteArrayOutputStreamTestCase extends OutputStreamAbstractTestC
|
||||
return new FastByteArrayOutputStream(256);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInputStream() throws IOException {
|
||||
FastByteArrayOutputStream out = makeObject();
|
||||
|
||||
-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 $
|
||||
*/
|
||||
public class FileCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||
public FileCacheSeekableStreamTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||
try {
|
||||
return new FileCacheSeekableStream(pStream);
|
||||
+6
-4
@@ -1,7 +1,11 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* MemoryCacheSeekableStreamTestCase
|
||||
* <p/>
|
||||
@@ -10,10 +14,6 @@ import java.io.*;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/FileSeekableStreamTestCase.java#3 $
|
||||
*/
|
||||
public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||
public FileSeekableStreamTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||
try {
|
||||
return new FileSeekableStream(createFileWithContent(pStream));
|
||||
@@ -37,11 +37,13 @@ public class FileSeekableStreamTestCase extends SeekableInputStreamAbstractTestC
|
||||
return temp;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testCloseUnderlyingStream() throws IOException {
|
||||
// There is no underlying stream here...
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseUnderlyingFile() throws IOException {
|
||||
final boolean[] closed = new boolean[1];
|
||||
|
||||
+31
-11
@@ -17,12 +17,15 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* InputStreamAbstractTestCase
|
||||
* <p/>
|
||||
@@ -38,10 +41,6 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
final static private long SEED = 29487982745l;
|
||||
final static Random sRandom = new Random(SEED);
|
||||
|
||||
public InputStreamAbstractTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected final Object makeObject() {
|
||||
return makeInputStream();
|
||||
}
|
||||
@@ -71,11 +70,12 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws Exception {
|
||||
int size = 5;
|
||||
InputStream input = makeInputStream(makeOrderedArray(size));
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals("Check Size [" + i + "]", (size - i), input.available());
|
||||
assertTrue("Check Size [" + i + "]", (size - i) >= input.available());
|
||||
assertEquals("Check Value [" + i + "]", i, input.read());
|
||||
}
|
||||
assertEquals("Available after contents all read", 0, input.available());
|
||||
@@ -90,6 +90,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAvailable() throws Exception {
|
||||
InputStream input = makeInputStream(1);
|
||||
assertFalse("Unexpected EOF", input.read() < 0);
|
||||
@@ -100,6 +101,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
assertEquals("Available after End of File", 0, input.available());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadByteArray() throws Exception {
|
||||
byte[] bytes = new byte[10];
|
||||
byte[] data = makeOrderedArray(15);
|
||||
@@ -145,6 +147,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEOF() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(2));
|
||||
assertEquals("Read 1", 0, input.read());
|
||||
@@ -154,6 +157,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
assertEquals("Read 5", -1, input.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkResetUnsupported() throws IOException {
|
||||
InputStream input = makeInputStream(10);
|
||||
if (input.markSupported()) {
|
||||
@@ -163,7 +167,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
input.mark(100); // Should be a no-op
|
||||
|
||||
int read = input.read();
|
||||
assertEquals(0, read);
|
||||
assertTrue(read >= 0);
|
||||
|
||||
// TODO: According to InputStream#reset, it is allowed to do some
|
||||
// implementation specific reset, and still be correct...
|
||||
@@ -176,6 +180,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetNoMark() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(10));
|
||||
|
||||
@@ -196,6 +201,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkReset() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||
|
||||
@@ -226,6 +232,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetAfterReadLimit() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||
|
||||
@@ -257,6 +264,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetAfterReset() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||
|
||||
@@ -264,7 +272,8 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
return; // Not supported, skip test
|
||||
}
|
||||
|
||||
assertTrue("Expected to read positive value", input.read() >= 0);
|
||||
int first = input.read();
|
||||
assertTrue("Expected to read positive value", first >= 0);
|
||||
|
||||
int readlimit = 5;
|
||||
|
||||
@@ -273,19 +282,24 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
int read = input.read();
|
||||
assertTrue("Expected to read positive value", read >= 0);
|
||||
|
||||
input.reset();
|
||||
assertEquals("Expected value read differes from actual", read, input.read());
|
||||
assertTrue(input.read() >= 0);
|
||||
assertTrue(input.read() >= 0);
|
||||
|
||||
// Reset after read limit passed, may either throw exception, or reset to last mark
|
||||
input.reset();
|
||||
assertEquals("Expected value read differs from actual", read, input.read());
|
||||
|
||||
// Reset after read limit passed, may either throw exception, or reset to last good mark
|
||||
try {
|
||||
input.reset();
|
||||
assertEquals("Re-read of reset data should be same", read, input.read());
|
||||
int reRead = input.read();
|
||||
assertTrue("Re-read of reset data should be same as initially marked or first", reRead == read || reRead == first);
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertTrue("Wrong read-limit IOException message", e.getMessage().contains("mark"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkip() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(10));
|
||||
|
||||
@@ -302,6 +316,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
assertEquals("Unexpected value read after EOF", -1, input.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityOrdered() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = makeOrderedArray(25);
|
||||
@@ -314,6 +329,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityOrdered2() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = makeOrderedArray(25);
|
||||
@@ -332,6 +348,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityNegative() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = new byte[25];
|
||||
@@ -347,6 +364,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityNegative2() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = new byte[25];
|
||||
@@ -368,6 +386,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityRandom() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = makeRandomArray(25);
|
||||
@@ -380,6 +399,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanityRandom2() throws IOException {
|
||||
// This is to sanity check that the test itself is correct...
|
||||
byte[] bytes = makeRandomArray(25);
|
||||
+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 $
|
||||
*/
|
||||
public class MemoryCacheSeekableStreamTestCase extends SeekableInputStreamAbstractTestCase {
|
||||
public MemoryCacheSeekableStreamTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected SeekableInputStream makeInputStream(final InputStream pStream) {
|
||||
return new MemoryCacheSeekableStream(pStream);
|
||||
}
|
||||
+21
-3
@@ -1,10 +1,13 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* InputStreamAbstractTestCase
|
||||
* <p/>
|
||||
@@ -15,6 +18,7 @@ import java.io.IOException;
|
||||
public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCase {
|
||||
protected abstract OutputStream makeObject();
|
||||
|
||||
@Test
|
||||
public void testWrite() throws IOException {
|
||||
OutputStream os = makeObject();
|
||||
|
||||
@@ -23,12 +27,14 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArray() throws IOException {
|
||||
OutputStream os = makeObject();
|
||||
|
||||
os.write(new byte[256]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArrayNull() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
@@ -46,7 +52,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
public void testWriteByteArrayOffsetLenght() throws IOException {
|
||||
@Test
|
||||
public void testWriteByteArrayOffsetLength() throws IOException {
|
||||
byte[] input = new byte[256];
|
||||
|
||||
OutputStream os = makeObject();
|
||||
@@ -65,7 +72,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
public void testWriteByteArrayZeroLenght() {
|
||||
@Test
|
||||
public void testWriteByteArrayZeroLength() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
os.write(new byte[1], 0, 0);
|
||||
@@ -75,7 +83,8 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
public void testWriteByteArrayOffsetLenghtNull() {
|
||||
@Test
|
||||
public void testWriteByteArrayOffsetLengthNull() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
os.write(null, 5, 10);
|
||||
@@ -92,6 +101,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArrayNegativeOffset() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
@@ -109,6 +119,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArrayNegativeLength() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
@@ -126,6 +137,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArrayOffsetOutOfBounds() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
@@ -143,6 +155,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteByteArrayLengthOutOfBounds() {
|
||||
OutputStream os = makeObject();
|
||||
try {
|
||||
@@ -160,14 +173,17 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlush() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() {
|
||||
// TODO: Implement
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAfterClose() throws IOException {
|
||||
OutputStream os = makeObject();
|
||||
|
||||
@@ -200,6 +216,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushAfterClose() throws IOException {
|
||||
OutputStream os = makeObject();
|
||||
|
||||
@@ -221,6 +238,7 @@ public abstract class OutputStreamAbstractTestCase extends ObjectAbstractTestCas
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseAfterClose() throws IOException {
|
||||
OutputStream os = makeObject();
|
||||
|
||||
+10
-1
@@ -1,10 +1,13 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ReaderAbstractTestCase
|
||||
* <p/>
|
||||
@@ -36,6 +39,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
|
||||
protected abstract Reader makeReader(String pInput);
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -51,6 +55,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
assertEquals(mInput, buffer.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBuffer() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -70,6 +75,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
assertEquals(mInput, new String(chars));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipToEnd() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -83,6 +89,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
assertEquals(0, toSkip);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipToEndAndRead() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -95,6 +102,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
}
|
||||
|
||||
// TODO: It's possible to support reset and not mark (resets to beginning of stream, for example)
|
||||
@Test
|
||||
public void testResetMarkSupported() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -154,6 +162,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetMarkNotSupported() throws IOException {
|
||||
Reader reader = makeReader();
|
||||
|
||||
@@ -198,7 +207,7 @@ public abstract class ReaderAbstractTestCase extends ObjectAbstractTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadAfterClose() throws IOException {
|
||||
Reader reader = makeReader("foo bar");
|
||||
|
||||
+7
-3
@@ -1,6 +1,8 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* SeekableAbstractTestCase
|
||||
@@ -9,14 +11,16 @@ import junit.framework.TestCase;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableAbstractTestCase.java#1 $
|
||||
*/
|
||||
public abstract class SeekableAbstractTestCase extends TestCase implements SeekableInterfaceTest {
|
||||
public abstract class SeekableAbstractTestCase implements SeekableInterfaceTest {
|
||||
|
||||
protected abstract Seekable createSeekable();
|
||||
|
||||
@Test
|
||||
public void testFail() {
|
||||
fail();
|
||||
fail("Do not create stand-alone test classes based on this class. Instead, create an inner class and delegate to it.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekable() {
|
||||
assertTrue(createSeekable() instanceof Seekable);
|
||||
}
|
||||
+19
-11
@@ -1,10 +1,14 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* SeekableInputStreamAbstractTestCase
|
||||
* <p/>
|
||||
@@ -13,13 +17,8 @@ import java.io.InputStream;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/SeekableInputStreamAbstractTestCase.java#4 $
|
||||
*/
|
||||
public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbstractTestCase implements SeekableInterfaceTest {
|
||||
|
||||
public SeekableInputStreamAbstractTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
//// TODO: Figure out a better way of creating interface tests without duplicating code
|
||||
final SeekableAbstractTestCase mSeekableTestCase = new SeekableAbstractTestCase() {
|
||||
final SeekableAbstractTestCase seekableTestCase = new SeekableAbstractTestCase() {
|
||||
protected Seekable createSeekable() {
|
||||
return makeInputStream();
|
||||
}
|
||||
@@ -41,6 +40,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
|
||||
protected abstract SeekableInputStream makeInputStream(InputStream pStream);
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testResetAfterReset() throws Exception {
|
||||
InputStream input = makeInputStream(makeOrderedArray(25));
|
||||
@@ -59,9 +59,9 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
assertTrue("Expected to read positive value", read >= 0);
|
||||
|
||||
input.reset();
|
||||
assertEquals("Expected value read differes from actual", read, input.read());
|
||||
assertEquals("Expected value read differs from actual", read, input.read());
|
||||
|
||||
// Reset after read limit passed, may either throw exception, or reset to last mark
|
||||
// Reset after read limit passed, may either throw exception, or reset to last good mark
|
||||
try {
|
||||
input.reset();
|
||||
assertEquals("Re-read of reset data should be first", 0, input.read());
|
||||
@@ -71,10 +71,12 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekable() {
|
||||
mSeekableTestCase.testSeekable();
|
||||
seekableTestCase.testSeekable();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushBeyondCurrentPos() throws Exception {
|
||||
SeekableInputStream seekable = makeInputStream(20);
|
||||
|
||||
@@ -88,6 +90,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeek() throws Exception {
|
||||
SeekableInputStream seekable = makeInputStream(55);
|
||||
int pos = 37;
|
||||
@@ -97,6 +100,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
assertEquals("Stream positon should match seeked position", pos, streamPos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekFlush() throws Exception {
|
||||
SeekableInputStream seekable = makeInputStream(133);
|
||||
int pos = 45;
|
||||
@@ -114,6 +118,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkFlushReset() throws Exception {
|
||||
SeekableInputStream seekable = makeInputStream(77);
|
||||
|
||||
@@ -134,6 +139,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
assertEquals(position, seekable.getStreamPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekSkipRead() throws Exception {
|
||||
SeekableInputStream seekable = makeInputStream(133);
|
||||
int pos = 45;
|
||||
@@ -147,7 +153,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
}
|
||||
}
|
||||
|
||||
public void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException {
|
||||
protected void testSeekSkip(SeekableInputStream pSeekable, String pStr) throws IOException {
|
||||
System.out.println();
|
||||
pSeekable.seek(pStr.length());
|
||||
FileUtil.read(pSeekable);
|
||||
@@ -330,6 +336,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
}
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testReadResetReadDirectBufferBug() throws IOException {
|
||||
// Make sure we use the exact size of the buffer
|
||||
final int size = 1024;
|
||||
@@ -365,6 +372,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
assertTrue(rangeEquals(bytes, size, result, 0, size));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadAllByteValuesRegression() throws IOException {
|
||||
final int size = 128;
|
||||
|
||||
@@ -401,6 +409,7 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseUnderlyingStream() throws IOException {
|
||||
final boolean[] closed = new boolean[1];
|
||||
|
||||
@@ -476,5 +485,4 @@ public abstract class SeekableInputStreamAbstractTestCase extends InputStreamAbs
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user