mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-21 00:00:01 -04:00
Compare commits
569 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 666e0ef94a | |||
| b58c4ba5dd | |||
| 6328862e4f | |||
| a95235b422 | |||
| 9f4b09fc7d | |||
| 03455f0132 | |||
| ac5779d8d6 | |||
| fef8ff3aab | |||
| a9e4b2e262 | |||
| 7147e2dfb1 | |||
| 1680fadf83 | |||
| d1df8c13ed | |||
| 031937fe99 | |||
| 24c473ae45 | |||
| 57941cb638 | |||
| d325b6deec | |||
| 3f67430b2d | |||
| 314071dde8 | |||
| 83ad25c2dd | |||
| d7dae90e2e | |||
| ad437c2470 | |||
| dabc26bdb5 | |||
| b441298a9a | |||
| 345ca0ac13 | |||
| 9720a931c5 | |||
| 4d3f691e6a | |||
| 2e0e575183 | |||
| 2c7c47b158 | |||
| 57680f1bec | |||
| 8dc83c4e9e | |||
| 929bea6b6b | |||
| 699662d054 | |||
| 1bebee1851 | |||
| b0e8dd86bb | |||
| 5bdb0b5502 | |||
| 85d47528a3 | |||
| a7ebd66149 | |||
| f0a032a7b9 | |||
| 690cb064e7 | |||
| e65f471a8f | |||
| f2cc9faaf8 | |||
| 8bd3f4f34a | |||
| be348543d8 | |||
| 80229b8c3c | |||
| a5ee53569d | |||
| c22ada03cd | |||
| b61341ce3b | |||
| e73b0a7bcf | |||
| ea9d1c0dc5 | |||
| e0563ee7dd | |||
| 44bcd202e8 | |||
| 5b57b51ff1 | |||
| 0b5f8a071e | |||
| d3dc578936 | |||
| 2ae937b870 | |||
| 2a7763299a | |||
| a57a1d35f9 | |||
| e618805786 | |||
| e3cb923d37 | |||
| 4513b0c166 | |||
| b55d4e8c1b | |||
| 399c75f59e | |||
| a7f7d73e2d | |||
| 41a83191b7 | |||
| 49e805eeb7 | |||
| f426637fdd | |||
| 4081ff1545 | |||
| ae4fbcc726 | |||
| 66208bfa03 | |||
| e72255805e | |||
| 780ccd7916 | |||
| 957e917281 | |||
| 9daf8337ee | |||
| 89c104d59f | |||
| 8608ee76c5 | |||
| aab2d36e92 | |||
| 608b37232d | |||
| e3ebf8e0fa | |||
| 9af10625a2 | |||
| ccc4e7b411 | |||
| 439323c5e5 | |||
| 332e191d0d | |||
| 58d1a89028 | |||
| 72fe799cc8 | |||
| 18af213e27 | |||
| 9d267f352f | |||
| f8f6f9f1e4 | |||
| 2841d26785 | |||
| 98ae5967ca | |||
| da9d87a561 | |||
| d472191926 | |||
| a8472170c4 | |||
| b2f7cada21 | |||
| 2a4c152c3d | |||
| 660932b7e7 | |||
| d0a17ff3b3 | |||
| 4259903bdd | |||
| 3018d2a342 | |||
| 8f94318f28 | |||
| 60b7151eb6 | |||
| 8ad21307e8 | |||
| 8c854f1e20 | |||
| 1445ff2533 | |||
| e8f1e80d4e | |||
| 18e29e9ed5 | |||
| ec4003b1c1 | |||
| 164b8db988 | |||
| 21feace385 | |||
| ba1f754611 | |||
| 822b5da631 | |||
| c785f6932f | |||
| 43e2a27c7f | |||
| 9d50acd2fe | |||
| ba73a308d6 | |||
| 20cd259abd | |||
| 033a1423ff | |||
| 7c8c520006 | |||
| 9162458e36 | |||
| d38af3cb16 | |||
| c57e8f80fa | |||
| 4750359ada | |||
| cbe8038619 | |||
| 2d8125e69c | |||
| 57a664c093 | |||
| 7f82377fd7 | |||
| 72b9f19a51 | |||
| 0083b8e77e | |||
| 79982cd493 | |||
| f0db338f3b | |||
| 9db4e0b3ed | |||
| cd4cbdcb82 | |||
| 15dc4b3852 | |||
| a3534ecd59 | |||
| 7bb5fee23b | |||
| 6cb7424bd0 | |||
| 9aa04d311e | |||
| 967e71dc92 | |||
| 628523ddc8 | |||
| 783c28ae0e | |||
| d35d67651f | |||
| 816d48efef | |||
| 25ee21d090 | |||
| 536ea7ba88 | |||
| d2687383f5 | |||
| 3ce35e059c | |||
| 1d5359dd35 | |||
| 2699b75b79 | |||
| 4e614dfc7e | |||
| 3a2efd9491 | |||
| c5dc2e4e53 | |||
| 41460bd32a | |||
| 13b37b3839 | |||
| 6dd74070f4 | |||
| 81b358b377 | |||
| 9715f4e74c | |||
| 38256c8be0 | |||
| bf3c1fad17 | |||
| b8488ae39b | |||
| d3bea0ae38 | |||
| 9435410b1e | |||
| f74e8c8ba1 | |||
| 0ae2c2f01d | |||
| 77c81a06bc | |||
| 2bbcd88798 | |||
| 829fbe7547 | |||
| 078425eed9 | |||
| 8ddcbbd2b2 | |||
| 507cca5fd7 | |||
| a4caac0c82 | |||
| 54c07b849c | |||
| c531d4f5d3 | |||
| aa2e8e5d7e | |||
| 76a35331b0 | |||
| 6b3f1c6ee3 | |||
| c731e10e8f | |||
| 4a8c3530f7 | |||
| e8996daa12 | |||
| 9196e60c74 | |||
| eabb8fd02b | |||
| 1794e336de | |||
| ac7612b3df | |||
| 606fd53823 | |||
| 614a07e040 | |||
| 34e8d88007 | |||
| 9b727df901 | |||
| f1f98bb4a4 | |||
| b34b26e08c | |||
| 993e07ee34 | |||
| a377712bdb | |||
| e5dc6aa878 | |||
| 46b1c1cf96 | |||
| d9300b1c90 | |||
| 0a2efb9eac | |||
| 3eabc591d8 | |||
| 5cefce2dbf | |||
| 4c645c0220 | |||
| 703848ca45 | |||
| 0ebd18fcb6 | |||
| 29f7547a99 | |||
| 25cd351eee | |||
| 77c98c917e | |||
| 4bbe946f46 | |||
| 78832ed923 | |||
| 164cc11592 | |||
| 8f5c1b409f | |||
| 26981513d8 | |||
| a3a30d54d4 | |||
| 102e9cff51 | |||
| da800be8c8 | |||
| 70493bd323 | |||
| 304d050bc3 | |||
| 0443172666 | |||
| cee2663f06 | |||
| 8f44cfc43c | |||
| 8a240aac68 | |||
| 61424f33b6 | |||
| c7b9b1fadd | |||
| ab08ec1e0d | |||
| cbe78dc67f | |||
| c9e11f171f | |||
| bc2c0c2301 | |||
| 6581e2e2a1 | |||
| c2873b1f27 | |||
| 35f2f0be9f | |||
| b5856fd110 | |||
| 1919d77a45 | |||
| 37afe24aac | |||
| 081f2efea2 | |||
| 627bb1bf1f | |||
| a77b62b6ba | |||
| 2500d8cc15 | |||
| c01336fb8a | |||
| 6f9b9bee01 | |||
| b9b1a35408 | |||
| 7ed5663633 | |||
| 6458fcdcbd | |||
| 9375bfda9a | |||
| 0160fb70f8 | |||
| 29dca0f124 | |||
| c21f14efe1 | |||
| 81ba43e1e8 | |||
| a1fcfc3958 | |||
| c60116a611 | |||
| 15e6ddc1fd | |||
| 49f4e5401e | |||
| e333c7d1b2 | |||
| cda34b704b | |||
| 7c4487be04 | |||
| 5a4525aaa1 | |||
| b766420e3e | |||
| c858454c5a | |||
| 67b48ce1e3 | |||
| 6608f61353 | |||
| 326b98d5e5 | |||
| fafa58b718 | |||
| 0ed0246762 | |||
| b3004a1227 | |||
| 7ab627a754 | |||
| 008e57a7ce | |||
| 28270b4d5b | |||
| 7382151db8 | |||
| b856ce07af | |||
| 190fe87ee9 | |||
| d1872ce94f | |||
| a5c52a99b4 | |||
| 4170b393fa | |||
| 53f9ba91e0 | |||
| be2d7d5f10 | |||
| 00aec2c90e | |||
| 2b04f7205c | |||
| 7d401d0194 | |||
| 48691139a3 | |||
| bcb87c09d2 | |||
| 84a8ceeb93 | |||
| 0cb99feedf | |||
| 91493c5145 | |||
| 6ddb799a95 | |||
| 8a187f6657 | |||
| b7d865f2cf | |||
| d50fb1a51e | |||
| 8992406f50 | |||
| 44eebff62f | |||
| 8c85c4ca96 | |||
| fa5c77bff0 | |||
| d87b80deea | |||
| ae138c3b4e | |||
| ab13fdd09d | |||
| aab5b062bd | |||
| 00d6acd1bf | |||
| 0f8a7ea482 | |||
| 9fe87fe10d | |||
| a33dbaf897 | |||
| 9e2f369459 | |||
| d34b0b7fcf | |||
| 5effcb1344 | |||
| b67d687761 | |||
| d0881c8b5c | |||
| 976928f48c | |||
| e1c2f2ee73 | |||
| 92632fa2a3 | |||
| 5a563e315f | |||
| c06d47d123 | |||
| fea6beb364 | |||
| 4b951c06cc | |||
| a3e6e52c95 | |||
| 5347015cbd | |||
| 4d190892df | |||
| 60eab81709 | |||
| b400b6b157 | |||
| 499b3ef120 | |||
| 92bc9c73f6 | |||
| 2a77558cac | |||
| 816cad60a8 | |||
| 7167f81c69 | |||
| f5cfa0e619 | |||
| 73ad024833 | |||
| 379449b621 | |||
| e17faad6fb | |||
| 1271a3d55e | |||
| 1cd594d113 | |||
| b76f74e79a | |||
| 78817a489b | |||
| b8f2a80ca6 | |||
| ac8a36db1c | |||
| 7e0d8922da | |||
| 9a6b8c9bfe | |||
| eced5b8efd | |||
| 74611e4e52 | |||
| b8614eca4d | |||
| efd24456ac | |||
| 191b2371c8 | |||
| 33419ef291 | |||
| 123f0bb7fc | |||
| 99b5f28a49 | |||
| b30fb4f8c3 | |||
| dc0bdcbd5b | |||
| 0cf29c167d | |||
| 98e4b76206 | |||
| aa4b5db054 | |||
| 433311c10d | |||
| f50178bc78 | |||
| e016e970e5 | |||
| 4223d13898 | |||
| 444aeabf21 | |||
| 05507a59d6 | |||
| c4c89a0a25 | |||
| b0ad6b2a4b | |||
| 25c703f4b2 | |||
| 529c59f93f | |||
| 584b1d9b21 | |||
| 312ce364cc | |||
| 7de8231471 | |||
| 0de9f79029 | |||
| eeb56acdde | |||
| a6862cfec8 | |||
| f8284700b4 | |||
| 38caeb22e0 | |||
| b2c5915db8 | |||
| 3911191b04 | |||
| bc328419ac | |||
| da4efe98bf | |||
| 6653f4a85d | |||
| 511a29beb9 | |||
| 5617b4323c | |||
| 16d0af357d | |||
| 74927d5396 | |||
| 7e809dd834 | |||
| 7aa95a08bc | |||
| c28963ae49 | |||
| 0327f5fc1a | |||
| 1c59057c30 | |||
| 3e1f85c4dc | |||
| 11227a68a0 | |||
| 62ba73a30e | |||
| 1f33afb5a1 | |||
| 9d3f271867 | |||
| 812e12acb0 | |||
| 060b6cf852 | |||
| e68ce7ffd1 | |||
| 778cdef69c | |||
| d46a76fca8 | |||
| 105a1ee466 | |||
| aa030f526c | |||
| 976e5d6210 | |||
| 6daca00fcd | |||
| ef05872934 | |||
| 1ddab866fd | |||
| ce997a6951 | |||
| 23bf5cb7b2 | |||
| 564778f415 | |||
| e28bf8fb44 | |||
| cf8d630d01 | |||
| 0ff7224912 | |||
| 196081a317 | |||
| ff50180d86 | |||
| 8f2c482167 | |||
| eab24890ca | |||
| cd42d81817 | |||
| ba5c667b6c | |||
| 4e9fa9442c | |||
| 4d2326c18d | |||
| 94eac2d6e5 | |||
| f63a33d541 | |||
| 00f8d87f36 | |||
| 4c2ab6da7b | |||
| b5088312e2 | |||
| f04f968f12 | |||
| 8896092e31 | |||
| 2f9768a1d4 | |||
| 06bcf22242 | |||
| 20c7f8e60e | |||
| 15a9ad0a9b | |||
| 7ae2d636dc | |||
| 12e756b23c | |||
| 4e2bf131d2 | |||
| d0c4a07556 | |||
| 21059c8d5a | |||
| fa7b530809 | |||
| 790cf3b32e | |||
| b1baaad23b | |||
| 7fa704ace5 | |||
| 8d07f4fe90 | |||
| 32bba6857b | |||
| ab7b08dfa9 | |||
| e0d6fa0d84 | |||
| 51bdd370da | |||
| ee2be3f88f | |||
| c5511833cc | |||
| 6ac8a5d8b4 | |||
| 3f7cb24407 | |||
| 8bf9f7a8f0 | |||
| 03ab9558a0 | |||
| 715bde8358 | |||
| 0151efb5f6 | |||
| bd796429c5 | |||
| b4ef5823f3 | |||
| 9adf0f4da3 | |||
| 01a4e55185 | |||
| 2e2ab11091 | |||
| 419ffc9373 | |||
| b67975eef7 | |||
| b32a861b2d | |||
| 6930168c93 | |||
| fac9f1a927 | |||
| 913a03608c | |||
| 46ce99e10f | |||
| 3e4460ac41 | |||
| 5b7fc25520 | |||
| 42196e8513 | |||
| bc07524e7a | |||
| 0011b9a480 | |||
| 7b09ec8919 | |||
| 9c8977062d | |||
| b01b820ec8 | |||
| b61f2c179c | |||
| 967f8e6984 | |||
| bb650e5280 | |||
| 3b34d6e7ce | |||
| db782cfe9e | |||
| 96223f9f9f | |||
| da45c5783d | |||
| 5b6c819ac4 | |||
| 6d41f2db86 | |||
| ba0bb7b903 | |||
| d03dc28764 | |||
| 20a785ea5e | |||
| 0286fa4268 | |||
| 85fb9e6af3 | |||
| 97a8806bfb | |||
| 970f4f3a7e | |||
| 6d192968d1 | |||
| f5959af2e1 | |||
| ea74ac2714 | |||
| 80c595cea8 | |||
| fbc738f2d4 | |||
| 3e3acf3332 | |||
| 0a77520d67 | |||
| 72cd3aade3 | |||
| 88bd9cd2ba | |||
| 5ee8678a29 | |||
| fb1937ae63 | |||
| de02e3d7e0 | |||
| ebaa69713f | |||
| 8a1a90dafd | |||
| 6f6e65be12 | |||
| 253f04066b | |||
| 74902b3fb4 | |||
| af1a6492d4 | |||
| 0da007ec8c | |||
| 9053fb3816 | |||
| c1d4e474f0 | |||
| 6bac13eb84 | |||
| 0e48ddd306 | |||
| 8682decbbc | |||
| bb615b90bf | |||
| cb0c320b45 | |||
| 73044bea58 | |||
| 3bb312e9e1 | |||
| c7d2f422b8 | |||
| 4dedf76ebc | |||
| 2376d16ffd | |||
| 1fe0bdd41f | |||
| 1b4d25342f | |||
| bc391550fb | |||
| b563f573de | |||
| 25150b421c | |||
| 94031a2913 | |||
| 64fb421b38 | |||
| 78af95d747 | |||
| 1d4f681b8f | |||
| eda2cd76db | |||
| 4adc60a6c6 | |||
| 0d5577a9a4 | |||
| 918f92aba7 | |||
| 7a24d55be7 | |||
| a84cc1c060 | |||
| 31cb79d2b9 | |||
| d995e7baa0 | |||
| e7fe6d5c22 | |||
| 918b698e50 | |||
| 2427b2323f | |||
| 0a8222fea3 | |||
| 60a00b89ae | |||
| 4c88efa19d | |||
| 17d65a1f6f | |||
| fcd03eb903 | |||
| 4e69efce28 | |||
| 16caec4a22 | |||
| 08282ea09d | |||
| c04fed1aff | |||
| 97e788883a | |||
| a16fce0749 | |||
| 26e2fa0168 | |||
| 120deb3ad4 | |||
| 0a9e2df5de | |||
| 6ffcb88872 | |||
| 960e764c7b | |||
| d88f27b251 | |||
| e5b3e9755e | |||
| 6c34fb211f | |||
| 9fdbc3b1fc | |||
| 622c6f40d4 | |||
| 107da17ca9 | |||
| f9871b73a3 | |||
| 7605b646fe | |||
| 19c62ac7da | |||
| a5e4412d1a | |||
| 651246566a | |||
| fe8f854b17 | |||
| a4d20a4af4 | |||
| 0643d5910a | |||
| c78a456985 | |||
| 27017576d3 | |||
| f1810be10a | |||
| 021aba1a98 | |||
| a0b68adff3 | |||
| fa4586663c | |||
| 623d13a517 | |||
| a7ebc1b52f | |||
| f54f4370c0 | |||
| 5040e9fe8a | |||
| fc72cd34a1 | |||
| 6d71a3d306 | |||
| 86f8cf52a5 | |||
| bda6544a5f | |||
| 49c7cd1979 | |||
| 9dae58d5a6 | |||
| ed14b97199 | |||
| b94135a91c |
@@ -0,0 +1 @@
|
|||||||
|
github: haraldk
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: Reported bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Version information**
|
||||||
|
1. The version of the TwelveMonkeys ImageIO library in use.
|
||||||
|
For example: 4.0.0
|
||||||
|
|
||||||
|
2. The *exact* output of `java --version` (or `java -version` for older Java releases).
|
||||||
|
For example:
|
||||||
|
|
||||||
|
java version "1.8.0_271"
|
||||||
|
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
|
||||||
|
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
|
||||||
|
|
||||||
|
3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
1. Compile the below sample code
|
||||||
|
2. Download the sample image file
|
||||||
|
3. Run the code with the sample file
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Example code**
|
||||||
|
Preferably as a failing JUnit test, or a standalone program with a `main` method that showcases the problem.
|
||||||
|
|
||||||
|
Less is more. Don't add your entire project, only the code required to reproduce the problem. 😀
|
||||||
|
|
||||||
|
**Sample file(s)**
|
||||||
|
Attach any sample files needed to reproduce the problem. Use a ZIP-file if the format is not directly supported by GitHub.
|
||||||
|
|
||||||
|
**Stak trace**
|
||||||
|
Always include the stack trace you experience.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
Do not add screenshots of code or stack traces. 😀
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: New feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a use case or a problem you are working on? Please describe.**
|
||||||
|
A clear and concise description of what the problem or use case is. Understanding the rationale is key, to be able to implemeent the right solution.
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've already considered, and why they won't work.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: Trouble shooting and programming help
|
||||||
|
about: "General programming issues will reach a wider audience at StackOverflow. Tag
|
||||||
|
questions with javax-imageio and/or twelvemonkeys \U0001F600 "
|
||||||
|
title: ''
|
||||||
|
labels: Trouble-shooting
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
General programming issues and problems will reach a much wider audience at StackOverflow, we suggest you ask them there. This will offload our work with maintaining the library, and make sure you get better help sooner.
|
||||||
|
|
||||||
|
Tag the question with `javax-imageio` and/or `twelvemonkeys` and we'll find them there.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
**What is fixed** Add link to the issue this PR fixes.
|
||||||
|
|
||||||
|
Example: Fixes #42.
|
||||||
|
|
||||||
|
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
|
||||||
|
|
||||||
|
**What is changed** Briefly describe the changes proposed in this pull request:
|
||||||
|
|
||||||
|
* Fixed rare exception happening in `x >= 42` case
|
||||||
|
* Small optimization of `decompress()` method
|
||||||
|
* Corrected API doc for `compress()` method to reflect current implementation
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Maven/Java library updates
|
||||||
|
- package-ecosystem: "maven"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
# GitHub actions updates
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/.github/workflows"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
- '!dependabot/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [ 'master' ]
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
|
java: [ 8, 11, 17, 21 ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: ${{ matrix.java }}
|
||||||
|
java-package: jdk
|
||||||
|
cache: 'maven'
|
||||||
|
- name: Run Tests
|
||||||
|
run: mvn --batch-mode --no-transfer-progress test
|
||||||
|
- name: Publish Test Report
|
||||||
|
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||||
|
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||||
|
|
||||||
|
test_oracle:
|
||||||
|
name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
kcms: [ true, false ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- run: |
|
||||||
|
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
|
||||||
|
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||||
|
- uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with:
|
||||||
|
distribution: 'jdkfile'
|
||||||
|
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||||
|
java-version: '8'
|
||||||
|
cache: 'maven'
|
||||||
|
- name: Set MAVEN_OPTS
|
||||||
|
if: ${{ matrix.kcms }}
|
||||||
|
run: |
|
||||||
|
echo "MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider" >> $GITHUB_ENV
|
||||||
|
- name: Display Java version
|
||||||
|
run: java -version
|
||||||
|
- name: Run Tests
|
||||||
|
run: mvn --batch-mode --no-transfer-progress test
|
||||||
|
- name: Publish Test Report
|
||||||
|
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||||
|
check_name: Unit Test Results for Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Deploy
|
||||||
|
needs: [ test, test_oracle ]
|
||||||
|
if: github.ref == 'refs/heads/master' # only perform on latest master
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Set up Maven Central
|
||||||
|
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with: # running setup-java again overwrites the settings.xml
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '8'
|
||||||
|
java-package: jdk
|
||||||
|
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
|
server-username: MAVEN_CENTRAL_USERNAME # env variable for username in deploy (1)
|
||||||
|
server-password: MAVEN_CENTRAL_PASSWORD # env variable for token in deploy (2)
|
||||||
|
gpg-private-key: ${{ secrets.GPG_KEY }} # Value of the GPG private key to import
|
||||||
|
gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3)
|
||||||
|
- name: Get Project Version
|
||||||
|
run: |
|
||||||
|
echo "PROJECT_VERSION=$(mvn --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
||||||
|
- name: Publish to Maven Central
|
||||||
|
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
|
||||||
|
run: mvn --batch-mode --no-transfer-progress deploy -P release -DskipTests
|
||||||
|
env:
|
||||||
|
MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} # must be the same env variable name as (1)
|
||||||
|
MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} # must be the same env variable name as (2)
|
||||||
|
MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # must be the same env variable name as (3)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "master" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '26 13 * * 6'
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
# - https://gh.io/using-larger-runners
|
||||||
|
# Consider using larger runners for possible analysis time improvements.
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'java' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||||
|
# by a third-party and are governed by separate terms of service, privacy
|
||||||
|
# policy, and support documentation.
|
||||||
|
|
||||||
|
name: Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
# For Branch-Protection check. Only the default branch is supported. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||||
|
branch_protection_rule:
|
||||||
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
|
schedule:
|
||||||
|
- cron: '38 8 * * 2'
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
permissions: read-all # Declare default permissions as read only.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
security-events: write # to upload the results to code-scanning dashboard.
|
||||||
|
id-token: write # to publish results and get a badge
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "Run analysis"
|
||||||
|
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||||
|
# you want to enable the Branch-Protection check on the repository
|
||||||
|
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-fine-grained-pat-optional.
|
||||||
|
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||||
|
|
||||||
|
# Publish Results:
|
||||||
|
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||||
|
# - Allows the repository to include the Scorecard badge.
|
||||||
|
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||||
|
publish_results: true
|
||||||
|
|
||||||
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
|
# format to the repository Actions tab.
|
||||||
|
- name: "Upload artifact"
|
||||||
|
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
|
- name: "Upload to code-scanning"
|
||||||
|
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
||||||
@@ -15,3 +15,4 @@ private
|
|||||||
profiles.xml
|
profiles.xml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/.metadata/
|
||||||
|
|||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
dist: trusty
|
|
||||||
language: java
|
|
||||||
jdk:
|
|
||||||
- oraclejdk8
|
|
||||||
# Oracle JDK 7 no longer supported, we use env matrix to test various CMM providers
|
|
||||||
# - oraclejdk7
|
|
||||||
# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling
|
|
||||||
# - openjdk7
|
|
||||||
env:
|
|
||||||
- MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
|
|
||||||
- MAVEN_OPTS=""
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.m2
|
|
||||||
@@ -1,264 +1,84 @@
|
|||||||
## Latest
|
[](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
|
||||||
|
[](https://github.com/haraldk/TwelveMonkeys/actions/workflows/codeql.yml)
|
||||||
|
[](https://securityscorecards.dev/viewer/?uri=github.com/haraldk/TwelveMonkeys)
|
||||||
|
[](https://www.bestpractices.dev/projects/7900)
|
||||||
|
|
||||||
Master branch build status: [](https://travis-ci.org/haraldk/TwelveMonkeys)
|
[](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
|
||||||
|
[](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
|
||||||
|
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||||
|
[](https://paypal.me/haraldk76/100)
|
||||||
|
|
||||||
Latest release is TwelveMonkeys ImageIO [3.5](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio%20AND%20v:3.5) (Jan. 22nd, 2020).
|

|
||||||
[Release notes](https://github.com/haraldk/TwelveMonkeys/releases/latest).
|
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
|
TwelveMonkeys ImageIO provides extended image file format support for the Java platform, through plugins for the `javax.imageio.*` package.
|
||||||
|
|
||||||
These plugins extends the number of image file formats supported in Java, using the javax.imageio.* package.
|
The main goal of this project is to provide support for formats not covered by the JRE itself.
|
||||||
The main purpose of this project is to provide support for formats not covered by the JRE itself.
|
Support for these formats is important, to be able to read data found
|
||||||
|
|
||||||
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.
|
"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.
|
As 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
|
## File formats supported
|
||||||
|
|
||||||
Mainstream format support
|
| Plugin | Format | Description | R | W | Metadata | Notes |
|
||||||
|
| ------ | -------- |---------------------------------------------------------|:---:|:---:| -------- | ----- |
|
||||||
|
| Batik | **SVG** | Scalable Vector Graphics | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||||
|
| | WMF | MS Windows Metafile | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||||
|
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/bmp_metadata.html), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | CUR | MS Windows Cursor Format | ✔ | - | - |
|
||||||
|
| | ICO | MS Windows Icon Format | ✔ | ✔ | - |
|
||||||
|
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - |
|
||||||
|
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | JPEG Lossless | | ✔ | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | DCX | Multi-page PCX fax document | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PNTG | Apple MacPaint Picture Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PBM | NetPBM Portable Bit Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PGM | NetPBM Portable Grey Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PFM | Portable Float Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | (✔) | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only |
|
||||||
|
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| | BigTIFF | | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
| XWD | XWD | X11 Window Dump Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
|
||||||
#### BMP - MS Windows/IBM OS/2 Device Independent Bitmap
|
|
||||||
|
|
||||||
* Read support for all known versions of the DIB/BMP format
|
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
|
||||||
* Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE
|
and make sure you use an updated and secure version.*
|
||||||
* RGB, 16, 24 and 32 bit
|
|
||||||
* Embedded PNG and JPEG data
|
|
||||||
* Windows and OS/2 versions
|
|
||||||
* Native and standard metadata format
|
|
||||||
|
|
||||||
#### JPEG
|
|
||||||
|
|
||||||
* Read support for the following JPEG "flavors":
|
|
||||||
* All JFIF compliant JPEGs
|
|
||||||
* All Exif compliant JPEGs
|
|
||||||
* YCbCr JPEGs without JFIF segment (converted to RGB, using embedded ICC profile)
|
|
||||||
* CMYK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
|
|
||||||
* Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using embedded ICC profile)
|
|
||||||
* JPEGs containing ICC profiles with interpretation other than 'Perceptual' or class other than 'Display'
|
|
||||||
* JPEGs containing ICC profiles that are incompatible with stream data, corrupted ICC profiles or corrupted `ICC_PROFILE` segments
|
|
||||||
* JPEGs using non-standard color spaces, unsupported by Java 2D
|
|
||||||
* JPEGs with APP14/Adobe segments with length other than 14 bytes
|
|
||||||
* 8 bit JPEGs with 16 bit DQT segments
|
|
||||||
* 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)
|
|
||||||
* Non-conforming 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:
|
|
||||||
* CMYK JPEGs
|
|
||||||
* YCCK JPEGs in progress
|
|
||||||
|
|
||||||
#### 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).
|
|
||||||
|
|
||||||
Alternatively, if you have or know of a JPEG-2000 implementation in Java with a suitable license, get in touch. :-)
|
|
||||||
|
|
||||||
#### PNM - NetPBM Portable Any Map
|
|
||||||
|
|
||||||
* Read support for the following file types:
|
|
||||||
* PBM in 'P1' (ASCII) and 'P4' (binary) formats, 1 bit per pixel
|
|
||||||
* PGM in 'P2' (ASCII) and 'P5' (binary) formats, up to 16/32 bits per pixel
|
|
||||||
* PPM in 'P3' (ASCII) and 'P6' (binary) formats, up to 16/32 bits per pixel component
|
|
||||||
* PAM in 'P7' (binary) format up to 32 bits per pixel component
|
|
||||||
* Limited support for PFM in 'Pf' (gray) and 'PF' (RGB) formats, 32 bits floating point
|
|
||||||
* Write support for the following formats:
|
|
||||||
* PPM in 'P6' (binary) format
|
|
||||||
* PAM in 'P7' (binary) format
|
|
||||||
* Standard metadata support
|
|
||||||
|
|
||||||
#### PSD - Adobe Photoshop Document
|
|
||||||
|
|
||||||
* 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)
|
|
||||||
* Native and Standard metadata support
|
|
||||||
|
|
||||||
#### TIFF - Aldus/Adobe Tagged Image File Format
|
|
||||||
|
|
||||||
* Read support for the following "Baseline" TIFF file types:
|
|
||||||
* Class B (Bi-level), all relevant compression types, 1 bit per sample
|
|
||||||
* 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
|
|
||||||
* Class F (Facsimile), CCITT Modified Huffman RLE, T4 and T6 (type 2, 3 and 4) compressions.
|
|
||||||
* LZW Compression (type 5)
|
|
||||||
* "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined
|
|
||||||
* JPEG Compression (type 7)
|
|
||||||
* ZLib (aka Adobe-style Deflate) Compression (type 8)
|
|
||||||
* Deflate Compression (type 32946)
|
|
||||||
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression
|
|
||||||
* Alpha channel (ExtraSamples type 1/Associated Alpha and type 2/Unassociated Alpha)
|
|
||||||
* CMYK data (PhotometricInterpretation type 5/Separated)
|
|
||||||
* YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
|
|
||||||
* CIELab data in TIFF, ITU and ICC variants (PhotometricInterpretation type 9, 10 and 11)
|
|
||||||
* Planar data (PlanarConfiguration type 2/Planar)
|
|
||||||
* ICC profiles (ICCProfile)
|
|
||||||
* BitsPerSample values up to 16 for most PhotometricInterpretations
|
|
||||||
* Multiple images (pages) in one file
|
|
||||||
* Write support for most "Baseline" TIFF options
|
|
||||||
* Uncompressed, PackBits, ZLib and Deflate
|
|
||||||
* Additional support for CCITT T4 and and T6 compressions.
|
|
||||||
* Additional support for LZW and JPEG (type 7) compressions
|
|
||||||
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate
|
|
||||||
* Native and Standard metadata support
|
|
||||||
|
|
||||||
Legacy formats
|
|
||||||
|
|
||||||
#### HDR - Radiance High Dynamic Range RGBE Format
|
|
||||||
|
|
||||||
* Read support for the most common RGBE (.hdr) format
|
|
||||||
* Samples are converted to 32 bit floating point (`float`) and normalized using a global tone mapper by default.
|
|
||||||
* Support for custom global tone mappers
|
|
||||||
* Alternatively, use a "null-tone mapper", for unnormalized data (allows local tone mapping)
|
|
||||||
* Unconverted RGBE samples accessible using `readRaster`
|
|
||||||
* Standard metadata support
|
|
||||||
|
|
||||||
#### IFF - Commodore Amiga/Electronic Arts Interchange File Format
|
|
||||||
|
|
||||||
* Legacy format, allows reading popular image format from the Commodore Amiga computer.
|
|
||||||
* Read support for the following file types:
|
|
||||||
* ILBM Indexed color, 1-8 interleaved bit planes, including 6 bit EHB
|
|
||||||
* ILBM Gray, 8 bit interleaved bit planes
|
|
||||||
* 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)
|
|
||||||
|
|
||||||
#### PCX - ZSoft Paintbrush Format
|
|
||||||
|
|
||||||
* Read support for the following file types:
|
|
||||||
* Indexed color, 1, 2, 4 or 8 bits per pixel, bit planes or interleaved
|
|
||||||
* Grayscale, 8 bits per pixel
|
|
||||||
* Color (RGB), 8 bits per pixel component
|
|
||||||
* Read support for DCX (multi-page) fax format, containing any of the above types
|
|
||||||
* Support for the following compression types:
|
|
||||||
* Uncompressed (experimental)
|
|
||||||
* RLE compressed
|
|
||||||
* Standard metadata support
|
|
||||||
|
|
||||||
#### PICT - Apple Mac Paint Picture Format
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
||||||
#### SGI - Silicon Graphics Image Format
|
|
||||||
|
|
||||||
* Read support for the following file types:
|
|
||||||
* 1, 2, 3 or 4 channel image data
|
|
||||||
* 8 or 16 bits per pixel component
|
|
||||||
* Support for the following compression types:
|
|
||||||
* Uncompressed
|
|
||||||
* RLE compressed
|
|
||||||
* Standard metadata support
|
|
||||||
|
|
||||||
#### TGA - Truevision TGA Image Format
|
|
||||||
|
|
||||||
* Read support for the following file types:
|
|
||||||
* ColorMapped
|
|
||||||
* Monochrome
|
|
||||||
* TrueColor
|
|
||||||
* Support for the following compression types:
|
|
||||||
* Uncompressed
|
|
||||||
* RLE compressed
|
|
||||||
* Standard metadata support
|
|
||||||
* Write support
|
|
||||||
|
|
||||||
Icon/other formats
|
|
||||||
|
|
||||||
#### ICNS - Apple Icon Image
|
|
||||||
|
|
||||||
* Read support for the following icon types:
|
|
||||||
* All known "native" icon types
|
|
||||||
* Large PNG encoded icons
|
|
||||||
* Large JPEG 2000 encoded icons (requires JPEG 2000 ImageIO plugin or fallback to `sips` command line tool)
|
|
||||||
* Write support for PNG encoded icons
|
|
||||||
|
|
||||||
#### ICO & CUR - MS Windows Icon and Cursor Formats
|
|
||||||
|
|
||||||
* Read support for the following file types:
|
|
||||||
* ICO Indexed color, 1, 4 and 8 bit
|
|
||||||
* ICO RGB, 16, 24 and 32 bit
|
|
||||||
* CUR Indexed color, 1, 4 and 8 bit
|
|
||||||
* CUR RGB, 16, 24 and 32 bit
|
|
||||||
* Write support
|
|
||||||
* *3.1* Note: These formats are now part of the BMP plugin
|
|
||||||
|
|
||||||
#### Thumbs.db - MS Windows Thumbs DB
|
|
||||||
|
|
||||||
* Read support
|
|
||||||
|
|
||||||
Other formats, using 3rd party libraries
|
|
||||||
|
|
||||||
#### SVG - Scalable Vector Graphics
|
|
||||||
|
|
||||||
* Read-only support using Batik
|
|
||||||
|
|
||||||
#### WMF - MS Windows MetaFile
|
|
||||||
|
|
||||||
* Limited read-only support using Batik
|
|
||||||
|
|
||||||
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](http://xmlgraphics.apache.org/security.html), and make sure you use
|
|
||||||
either version 1.6.1, 1.7.1 or 1.8+.*
|
|
||||||
|
|
||||||
|
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
|
||||||
|
[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html).
|
||||||
|
For BMP, JPEG, and TIFF formats the TwelveMonkeys plugins provides extended format support and additional features.
|
||||||
|
|
||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
Most of the time, all you need to do is simply include the plugins in your project and write:
|
Most of the time, all you need to do is simply include the plugins in your project and write:
|
||||||
|
|
||||||
BufferedImage image = ImageIO.read(file);
|
```java
|
||||||
|
BufferedImage image = ImageIO.read(file);
|
||||||
|
```
|
||||||
|
|
||||||
This will load the first image of the file, entirely into memory.
|
This will load the first image of the file, entirely into memory.
|
||||||
|
|
||||||
The basic and simplest form of writing is:
|
The basic and simplest form of writing is:
|
||||||
|
|
||||||
if (!ImageIO.write(image, format, file)) {
|
```java
|
||||||
// Handle image not written case
|
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.
|
This will write the entire image into a single file, using the default settings for the given format.
|
||||||
|
|
||||||
@@ -269,50 +89,44 @@ The plugins are discovered automatically at run time. See the [FAQ](#faq) for mo
|
|||||||
If you need more control of read parameters and the reading process, the common idiom for reading is something like:
|
If you need more control of read parameters and the reading process, the common idiom for reading is something like:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Create input stream
|
// Create input stream (in try-with-resource block to avoid leaks)
|
||||||
ImageInputStream input = ImageIO.createImageInputStream(file);
|
try (ImageInputStream input = ImageIO.createImageInputStream(file)) {
|
||||||
|
// Get the reader
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||||
|
|
||||||
|
if (!readers.hasNext()) {
|
||||||
|
throw new IllegalArgumentException("No reader for: " + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageReader reader = readers.next();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the reader
|
reader.setInput(input);
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
|
||||||
|
|
||||||
if (!readers.hasNext()) {
|
// Optionally, listen for read warnings, progress, etc.
|
||||||
throw new IllegalArgumentException("No reader for: " + file);
|
reader.addIIOReadWarningListener(...);
|
||||||
}
|
reader.addIIOReadProgressListener(...);
|
||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
|
||||||
try {
|
// Optionally, control read settings like sub sampling, source region or destination etc.
|
||||||
reader.setInput(input);
|
param.setSourceSubsampling(...);
|
||||||
|
param.setSourceRegion(...);
|
||||||
|
param.setDestination(...);
|
||||||
|
// ...
|
||||||
|
|
||||||
// Optionally, listen for read warnings, progress, etc.
|
// Finally read the image, using settings from param
|
||||||
reader.addIIOReadWarningListener(...);
|
BufferedImage image = reader.read(0, param);
|
||||||
reader.addIIOReadProgressListener(...);
|
|
||||||
|
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
// Optionally, read thumbnails, meta data, etc...
|
||||||
|
int numThumbs = reader.getNumThumbnails(0);
|
||||||
// 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 {
|
finally {
|
||||||
// Close stream in finally block to avoid resource leaks
|
// Dispose reader in finally block to avoid memory leaks
|
||||||
input.close();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
|
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
|
||||||
@@ -324,86 +138,56 @@ It's also possible to read multiple images from the same file in a loop, using `
|
|||||||
If you need more control of write parameters and the writing process, the common idiom for writing is something like:
|
If you need more control of write parameters and the writing process, the common idiom for writing is something like:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
// Get the writer
|
// Get the writer
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
|
||||||
|
|
||||||
if (!writers.hasNext()) {
|
if (!writers.hasNext()) {
|
||||||
throw new IllegalArgumentException("No writer for: " + format);
|
throw new IllegalArgumentException("No writer for: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageWriter writer = writers.next();
|
ImageWriter writer = writers.next();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create output stream
|
// Create output stream (in try-with-resource block to avoid leaks)
|
||||||
ImageOutputStream output = ImageIO.createImageOutputStream(file);
|
try (ImageOutputStream output = ImageIO.createImageOutputStream(file)) {
|
||||||
|
writer.setOutput(output);
|
||||||
try {
|
|
||||||
writer.setOutput(output);
|
// Optionally, listen to progress, warnings, etc.
|
||||||
|
|
||||||
// Optionally, listen to progress, warnings, etc.
|
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||||
|
|
||||||
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, 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);
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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
|
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)
|
[Java Image I/O API Guide](https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
|
||||||
from Oracle.
|
from Oracle.
|
||||||
|
|
||||||
|
#### Adobe Clipping Path support
|
||||||
|
|
||||||
#### Deploying the plugins in a web app
|
```java
|
||||||
|
import com.twelvemonkeys.imageio.path.Paths;
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
In addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context).
|
try (ImageInputStream stream = ImageIO.createImageInputStream(new File("image_with_path.jpg")) {
|
||||||
If you restart your application, old classes will by default remain in memory forever (because the next time
|
BufferedImage image = Paths.readClipped(stream);
|
||||||
`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 or `NoClassDefFoundError`s
|
|
||||||
for uninitialized inner classes) may occur.
|
|
||||||
|
|
||||||
To work around both the discovery problem and the resource leak,
|
// Do something with the clipped image...
|
||||||
it is *strongly recommended* to use the `IIOProviderContextListener` that implements
|
}
|
||||||
dynamic loading and unloading of ImageIO plugins for web applications.
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<web-app ...>
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
<listener>
|
|
||||||
<display-name>ImageIO service provider loader/unloader</display-name>
|
|
||||||
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
|
|
||||||
</listener>
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
</web-app>
|
|
||||||
```
|
```
|
||||||
|
See [Adobe Clipping Path support on the Wiki](https://github.com/haraldk/TwelveMonkeys/wiki/Photoshop-Clipping-Path-support) for more details and example code.
|
||||||
|
|
||||||
Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly.
|
|
||||||
|
|
||||||
The context listener has no dependencies to the TwelveMonkeys ImageIO plugins, and may be used with JAI ImageIO
|
|
||||||
or other ImageIO plugins as well.
|
|
||||||
|
|
||||||
Another safe option, is to place the JAR files in the application server's shared or common lib folder.
|
|
||||||
|
|
||||||
#### Using the ResampleOp
|
#### Using the ResampleOp
|
||||||
|
|
||||||
@@ -411,15 +195,15 @@ The library comes with a resampling (image resizing) operation, that contains ma
|
|||||||
to provide excellent results at reasonable speed.
|
to provide excellent results at reasonable speed.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.twelvemonkeys.image.ResampleOp;
|
import com.twelvemonkeys.image.ResampleOp;
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
BufferedImage input = ...; // Image to resample
|
BufferedImage input = ...; // Image to resample
|
||||||
int width, height = ...; // new width/height
|
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
|
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);
|
BufferedImage output = resampler.filter(input, null);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using the DiffusionDither
|
#### Using the DiffusionDither
|
||||||
@@ -428,30 +212,30 @@ The library comes with a dithering operation, that can be used to convert `Buffe
|
|||||||
Floyd-Steinberg error-diffusion dither.
|
Floyd-Steinberg error-diffusion dither.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import com.twelvemonkeys.image.DiffusionDither;
|
import com.twelvemonkeys.image.DiffusionDither;
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
BufferedImage input = ...; // Image to dither
|
BufferedImage input = ...; // Image to dither
|
||||||
|
|
||||||
BufferedImageOp ditherer = new DiffusionDither();
|
BufferedImageOp ditherer = new DiffusionDither();
|
||||||
BufferedImage output = ditherer.filter(input, null);
|
BufferedImage output = ditherer.filter(input, null);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Download the project (using [Git](http://git-scm.com/downloads)):
|
Download the project (using [Git](https://git-scm.com/downloads)):
|
||||||
|
|
||||||
$ git clone git@github.com:haraldk/TwelveMonkeys.git
|
$ git clone git@github.com:haraldk/TwelveMonkeys.git
|
||||||
|
|
||||||
This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys`
|
This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys`
|
||||||
folder, and issue the command below to build.
|
folder, and issue the command below to build.
|
||||||
|
|
||||||
Build the project (using [Maven](http://maven.apache.org/download.cgi)):
|
Build the project (using [Maven](https://maven.apache.org/download.cgi)):
|
||||||
|
|
||||||
$ mvn package
|
$ mvn package
|
||||||
|
|
||||||
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
|
Currently, the recommended JDK for making a build is Oracle JDK 8.x.
|
||||||
|
|
||||||
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
|
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
|
||||||
|
|
||||||
@@ -473,10 +257,10 @@ The ImageIO registry and service lookup mechanism will make sure the plugins are
|
|||||||
To verify that the JPEG plugin is installed and used at run-time, you could use the following code:
|
To verify that the JPEG plugin is installed and used at run-time, you could use the following code:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
|
||||||
while (readers.hasNext()) {
|
while (readers.hasNext()) {
|
||||||
System.out.println("reader: " + readers.next());
|
System.out.println("reader: " + readers.next());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The first line should print:
|
The first line should print:
|
||||||
@@ -488,119 +272,162 @@ The first line should print:
|
|||||||
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
|
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
|
...
|
||||||
|
<dependencies>
|
||||||
...
|
...
|
||||||
<dependencies>
|
<dependency>
|
||||||
...
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<dependency>
|
<artifactId>imageio-jpeg</artifactId>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<version>3.9.4</version>
|
||||||
<artifactId>imageio-jpeg</artifactId>
|
</dependency>
|
||||||
<version>3.5</version>
|
<dependency>
|
||||||
</dependency>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<dependency>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<version>3.9.4</version>
|
||||||
<artifactId>imageio-tiff</artifactId>
|
</dependency>
|
||||||
<version>3.5</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Optional dependency. Needed only if you deploy `ImageIO` plugins as part of a web app.
|
Optional dependency. Needed only if you deploy ImageIO plugins as part of a web app.
|
||||||
Make sure you add the `IIOProviderContextListener` to your `web.xml`, see above.
|
Make sure you add the IIOProviderContextListener to your web.xml, see above.
|
||||||
-->
|
-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.servlet</groupId>
|
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||||
<artifactId>servlet</artifactId>
|
<artifactId>servlet</artifactId>
|
||||||
<version>3.5</version>
|
<version>3.9.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
|
||||||
|
<!--
|
||||||
|
Or Jakarta version, for Servlet API 5.0
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||||
|
<artifactId>servlet</artifactId>
|
||||||
|
<version>3.9.4</version>
|
||||||
|
<classifier>jakarta</classifier>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Manual dependency example
|
#### 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:
|
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.5.jar
|
twelvemonkeys-common-lang-3.9.4.jar
|
||||||
twelvemonkeys-common-io-3.5.jar
|
twelvemonkeys-common-io-3.9.4.jar
|
||||||
twelvemonkeys-common-image-3.5.jar
|
twelvemonkeys-common-image-3.9.4.jar
|
||||||
twelvemonkeys-imageio-core-3.5.jar
|
twelvemonkeys-imageio-core-3.9.4.jar
|
||||||
twelvemonkeys-imageio-metadata-3.5.jar
|
twelvemonkeys-imageio-metadata-3.9.4.jar
|
||||||
twelvemonkeys-imageio-jpeg-3.5.jar
|
twelvemonkeys-imageio-jpeg-3.9.4.jar
|
||||||
twelvemonkeys-imageio-tiff-3.5.jar
|
twelvemonkeys-imageio-tiff-3.9.4.jar
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
In 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 or `NoClassDefFoundError`s
|
||||||
|
for uninitialized inner classes) may occur.
|
||||||
|
|
||||||
|
To work around both the discovery problem and the resource leak,
|
||||||
|
it is *strongly recommended* to use the `IIOProviderContextListener` that implements
|
||||||
|
dynamic loading and unloading of ImageIO plugins for web applications.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<web-app ...>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
<listener>
|
||||||
|
<display-name>ImageIO service provider loader/unloader</display-name>
|
||||||
|
<listener-class>com.twelvemonkeys.servlet.image.IIOProviderContextListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</web-app>
|
||||||
|
```
|
||||||
|
|
||||||
|
Loading plugins from `WEB-INF/lib` without the context listener installed is unsupported and will not work correctly.
|
||||||
|
|
||||||
|
The context listener has no dependencies to the TwelveMonkeys ImageIO plugins, and may be used with JAI ImageIO
|
||||||
|
or other ImageIO plugins as well.
|
||||||
|
|
||||||
|
Another safe option, is to place the JAR files in the application server's shared or common lib folder.
|
||||||
|
|
||||||
|
#### Including the plugins in a "fat" JAR
|
||||||
|
|
||||||
|
The recommended way to use the plugins, is just to include the JARs as-is in your project, through a Maven dependency or similar.
|
||||||
|
Re-packaging is not necessary to use the library, and not recommended.
|
||||||
|
|
||||||
|
However, if you like to create a "fat"
|
||||||
|
JAR, or otherwise like to re-package the JARs for some reason, it's important to remember that automatic discovery of
|
||||||
|
the plugins by ImageIO depends on the
|
||||||
|
[Service Provider Interface (SPI)](https://docs.oracle.com/javase/tutorial/sound/SPI-intro.html) mechanism.
|
||||||
|
In short, each JAR contains a special folder, named `META-INF/services` containing one or more files,
|
||||||
|
typically `javax.imageio.spi.ImageReaderSpi` and `javax.imageio.spi.ImageWriterSpi`.
|
||||||
|
These files exist *with the same name in every JAR*,
|
||||||
|
so if you simply unpack everything to a single folder or create a JAR, files will be overwritten and behavior be
|
||||||
|
unspecified (most likely you will end up with a single plugin being installed).
|
||||||
|
|
||||||
|
The solution is to make sure all files with the same name, are merged to a single file,
|
||||||
|
containing all the SPI information of each type. If using the Maven Shade plugin, you should use the
|
||||||
|
[ServicesResourceTransformer](https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html#ServicesResourceTransformer)
|
||||||
|
to properly merge these files. You may also want to use the
|
||||||
|
[ManifestResourceTransforme](https://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html#ManifestResourceTransformer)
|
||||||
|
to get the correct vendor name, version info etc.
|
||||||
|
Other "fat" JAR bundlers will probably have similar mechanisms to merge entries with the same name.
|
||||||
|
|
||||||
### Links to prebuilt binaries
|
### Links to prebuilt binaries
|
||||||
|
|
||||||
##### Latest version (3.5)
|
##### Latest version (3.9.4)
|
||||||
|
|
||||||
Requires Java 7 or later.
|
The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later.
|
||||||
|
|
||||||
Common dependencies
|
Common dependencies
|
||||||
* [common-lang-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.5/common-lang-3.5.jar)
|
* [common-lang-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.9.4/common-lang-3.9.4.jar)
|
||||||
* [common-io-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.5/common-io-3.5.jar)
|
* [common-io-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.9.4/common-io-3.9.4.jar)
|
||||||
* [common-image-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.5/common-image-3.5.jar)
|
* [common-image-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.9.4/common-image-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO dependencies
|
ImageIO dependencies
|
||||||
* [imageio-core-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.5/imageio-core-3.5.jar)
|
* [imageio-core-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.9.4/imageio-core-3.9.4.jar)
|
||||||
* [imageio-metadata-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.5/imageio-metadata-3.5.jar)
|
* [imageio-metadata-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.9.4/imageio-metadata-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO plugins
|
ImageIO plugins
|
||||||
* [imageio-bmp-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.5/imageio-bmp-3.5.jar)
|
* [imageio-bmp-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.9.4/imageio-bmp-3.9.4.jar)
|
||||||
* [imageio-jpeg-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.5/imageio-jpeg-3.5.jar)
|
* [imageio-hdr-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.9.4/imageio-hdr-3.9.4.jar)
|
||||||
* [imageio-tiff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.5/imageio-tiff-3.5.jar)
|
* [imageio-icns-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.9.4/imageio-icns-3.9.4.jar)
|
||||||
* [imageio-pnm-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.5/imageio-pnm-3.5.jar)
|
* [imageio-iff-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.9.4/imageio-iff-3.9.4.jar)
|
||||||
* [imageio-psd-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.5/imageio-psd-3.5.jar)
|
* [imageio-jpeg-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.9.4/imageio-jpeg-3.9.4.jar)
|
||||||
* [imageio-hdr-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.5/imageio-hdr-3.5.jar)
|
* [imageio-pcx-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.9.4/imageio-pcx-3.9.4.jar)
|
||||||
* [imageio-iff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.5/imageio-iff-3.5.jar)
|
* [imageio-pict-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.9.4/imageio-pict-3.9.4.jar)
|
||||||
* [imageio-pcx-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.5/imageio-pcx-3.5.jar)
|
* [imageio-pnm-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.9.4/imageio-pnm-3.9.4.jar)
|
||||||
* [imageio-pict-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.5/imageio-pict-3.5.jar)
|
* [imageio-psd-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.9.4/imageio-psd-3.9.4.jar)
|
||||||
* [imageio-sgi-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.5/imageio-sgi-3.5.jar)
|
* [imageio-sgi-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.9.4/imageio-sgi-3.9.4.jar)
|
||||||
* [imageio-tga-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.5/imageio-tga-3.5.jar)
|
* [imageio-tga-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.9.4/imageio-tga-3.9.4.jar)
|
||||||
* [imageio-icns-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.5/imageio-icns-3.5.jar)
|
* [imageio-thumbsdb-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.9.4/imageio-thumbsdb-3.9.4.jar)
|
||||||
* [imageio-thumbsdb-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.5/imageio-thumbsdb-3.5.jar)
|
* [imageio-tiff-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.9.4/imageio-tiff-3.9.4.jar)
|
||||||
|
* [imageio-webp-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.9.4/imageio-webp-3.9.4.jar)
|
||||||
|
* [imageio-xwd-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.9.4/imageio-xwd-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO plugins requiring 3rd party libs
|
ImageIO plugins requiring 3rd party libs
|
||||||
* [imageio-batik-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.5/imageio-batik-3.5.jar)
|
* [imageio-batik-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.9.4/imageio-batik-3.9.4.jar)
|
||||||
|
|
||||||
Photoshop Path support for ImageIO
|
Photoshop Path support for ImageIO
|
||||||
* [imageio-clippath-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.5/imageio-clippath-3.5.jar)
|
* [imageio-clippath-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.9.4/imageio-clippath-3.9.4.jar)
|
||||||
|
|
||||||
Servlet support
|
Servlet support
|
||||||
* [servlet-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.5/servlet-3.5.jar)
|
* [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar)
|
||||||
|
|
||||||
##### Old version (3.0.x)
|
|
||||||
|
|
||||||
Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8 or later*.
|
|
||||||
|
|
||||||
Common dependencies
|
|
||||||
* [common-lang-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar)
|
|
||||||
* [common-io-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar)
|
|
||||||
* [common-image-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO dependencies
|
|
||||||
* [imageio-core-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar)
|
|
||||||
* [imageio-metadata-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO plugins
|
|
||||||
* [imageio-jpeg-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar)
|
|
||||||
* [imageio-tiff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar)
|
|
||||||
* [imageio-psd-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0.2/imageio-psd-3.0.2.jar)
|
|
||||||
* [imageio-pict-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0.2/imageio-pict-3.0.2.jar)
|
|
||||||
* [imageio-iff-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0.2/imageio-iff-3.0.2.jar)
|
|
||||||
* [imageio-icns-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar)
|
|
||||||
* [imageio-ico-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar)
|
|
||||||
* [imageio-thumbsdb-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO plugins requiring 3rd party libs
|
|
||||||
* [imageio-batik-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar)
|
|
||||||
* [imageio-jmagick-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar)
|
|
||||||
|
|
||||||
Servlet support
|
|
||||||
* [servlet-3.0.2.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
|
This project is provided under the OSI approved [BSD license](https://opensource.org/licenses/BSD-3-Clause):
|
||||||
|
|
||||||
Copyright (c) 2008-2018, Harald Kuhr
|
Copyright (c) 2008-2022, Harald Kuhr
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -632,8 +459,9 @@ The project is distributed under the OSI approved [BSD license](http://opensourc
|
|||||||
|
|
||||||
q: How do I use it?
|
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.
|
a: The easiest way is to build your own project using Maven, Gradle or other build tool with dependency management,
|
||||||
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
|
and just add dependencies to the specific plug-ins you need.
|
||||||
|
If you don't use such a build tool, 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?
|
q: What changes do I have to make to my code in order to use the plug-ins?
|
||||||
@@ -651,22 +479,41 @@ 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.
|
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
|
||||||
|
|
||||||
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath.
|
All you have to do, is to make sure you have the TwelveMonkeys ImageIO JARs in your classpath.
|
||||||
|
|
||||||
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](http://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
|
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
|
||||||
|
|
||||||
The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and
|
The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and
|
||||||
utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before
|
utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before
|
||||||
the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
the Sun/Oracle provided `JPEGImageReader`, `BMPImageReader` `TIFFImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
||||||
respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most
|
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.
|
cases you'll end up using the TwelveMonkeys plug-ins instead.
|
||||||
|
|
||||||
|
|
||||||
|
q: Why is there no support for common formats like GIF or PNG?
|
||||||
|
|
||||||
|
a: The short answer is simply that the built-in support in ImageIO for these formats are considered good enough as-is.
|
||||||
|
If you are looking for better PNG write performance on Java 7 and 8, see [JDK9 PNG Writer Backport](https://github.com/gredler/jdk9-png-writer-backport).
|
||||||
|
|
||||||
|
|
||||||
|
q: When is the next release? What is the current release schedule?
|
||||||
|
|
||||||
|
a: The goal is to make monthly releases, containing bug fixes and minor new features.
|
||||||
|
And quarterly releases with more "major" features.
|
||||||
|
|
||||||
|
|
||||||
|
q: I love this project! How can I help?
|
||||||
|
|
||||||
|
a: Have a look at the open issues, and see if there are any issues you can help fix, or provide sample file or create test cases for.
|
||||||
|
It is also possible for you or your organization to become a sponsor, through GitHub Sponsors.
|
||||||
|
Providing funding will allow us to spend more time on fixing bugs and implementing new features.
|
||||||
|
|
||||||
|
|
||||||
q: What about JAI? Several of the formats are already supported by JAI.
|
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.
|
a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues.
|
||||||
The most obvious being:
|
The most obvious being:
|
||||||
- It's not actively developed. No issues has been fixed for years.
|
- It's not actively developed. No issue has been fixed for years.
|
||||||
- To get full format support, you need native libs.
|
- 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.
|
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.
|
Some environments may also prevent deployment of native libs, which brings us back to square one.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
To report a security issue, please disclose it at [security advisory](https://github.com/haraldk/TwelveMonkeys/security/advisories/new).
|
||||||
|
|
||||||
|
Vulnerabilities will be disclosed in a best effort base.
|
||||||
+11
-1
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.twelvemonkeys.bom</groupId>
|
<groupId>com.twelvemonkeys.bom</groupId>
|
||||||
@@ -123,6 +123,16 @@
|
|||||||
<artifactId>imageio-tiff</artifactId>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-webp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-xwd</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- ImageIO 3rd party dependent plugins -->
|
<!-- ImageIO 3rd party dependent plugins -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -4,15 +4,19 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-image</artifactId>
|
<artifactId>common-image</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: Image</name>
|
<name>TwelveMonkeys :: Common :: Image</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common Image support
|
TwelveMonkeys Common image support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.common.image</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
@@ -27,9 +31,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jmagick</groupId>
|
<groupId>jmagick</groupId>
|
||||||
<artifactId>jmagick</artifactId>
|
<artifactId>jmagick</artifactId>
|
||||||
<version>6.2.4</version>
|
<version>6.6.9</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ import java.awt.*;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.BufferedImageOp;
|
||||||
|
import java.awt.image.ColorModel;
|
||||||
|
import java.awt.image.ImagingOpException;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.RasterOp;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
||||||
@@ -70,6 +76,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
||||||
try {
|
try {
|
||||||
@@ -80,10 +87,9 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics2D g2d = null;
|
Graphics2D g2d = dst.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g2d = dst.createGraphics();
|
|
||||||
int interpolationType = delegate.getInterpolationType();
|
int interpolationType = delegate.getInterpolationType();
|
||||||
|
|
||||||
if (interpolationType > 0) {
|
if (interpolationType > 0) {
|
||||||
@@ -109,9 +115,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
|||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (g2d != null) {
|
g2d.dispose();
|
||||||
g2d.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+96
-86
@@ -79,7 +79,7 @@ public final class BufferedImageFactory {
|
|||||||
private int scanSize;
|
private int scanSize;
|
||||||
|
|
||||||
private ColorModel sourceColorModel;
|
private ColorModel sourceColorModel;
|
||||||
private Hashtable sourceProperties; // ImageConsumer API dictates Hashtable
|
private Hashtable<?, ?> sourceProperties; // ImageConsumer API dictates Hashtable
|
||||||
|
|
||||||
private Object sourcePixels;
|
private Object sourcePixels;
|
||||||
|
|
||||||
@@ -91,21 +91,21 @@ public final class BufferedImageFactory {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code BufferedImageFactory}.
|
* Creates a {@code BufferedImageFactory}.
|
||||||
* @param pSource the source image
|
* @param source the source image
|
||||||
* @throws IllegalArgumentException if {@code pSource == null}
|
* @throws IllegalArgumentException if {@code source == null}
|
||||||
*/
|
*/
|
||||||
public BufferedImageFactory(final Image pSource) {
|
public BufferedImageFactory(final Image source) {
|
||||||
this(pSource != null ? pSource.getSource() : null);
|
this(source != null ? source.getSource() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code BufferedImageFactory}.
|
* Creates a {@code BufferedImageFactory}.
|
||||||
* @param pSource the source image producer
|
* @param source the source image producer
|
||||||
* @throws IllegalArgumentException if {@code pSource == null}
|
* @throws IllegalArgumentException if {@code source == null}
|
||||||
*/
|
*/
|
||||||
public BufferedImageFactory(final ImageProducer pSource) {
|
public BufferedImageFactory(final ImageProducer source) {
|
||||||
Validate.notNull(pSource, "source");
|
Validate.notNull(source, "source");
|
||||||
producer = pSource;
|
producer = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,44 +155,44 @@ public final class BufferedImageFactory {
|
|||||||
/**
|
/**
|
||||||
* Sets the source region (AOI) for the new image.
|
* Sets the source region (AOI) for the new image.
|
||||||
*
|
*
|
||||||
* @param pRegion the source region
|
* @param region the source region
|
||||||
*/
|
*/
|
||||||
public void setSourceRegion(final Rectangle pRegion) {
|
public void setSourceRegion(final Rectangle region) {
|
||||||
// Re-fetch everything, if region changed
|
// Re-fetch everything, if region changed
|
||||||
if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) {
|
if (x != region.x || y != region.y || width != region.width || height != region.height) {
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
x = pRegion.x;
|
x = region.x;
|
||||||
y = pRegion.y;
|
y = region.y;
|
||||||
width = pRegion.width;
|
width = region.width;
|
||||||
height = pRegion.height;
|
height = region.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the source subsampling for the new image.
|
* Sets the source subsampling for the new image.
|
||||||
*
|
*
|
||||||
* @param pXSub horizontal subsampling factor
|
* @param xSubsampling horizontal subsampling factor
|
||||||
* @param pYSub vertical subsampling factor
|
* @param ySubsampling vertical subsampling factor
|
||||||
*/
|
*/
|
||||||
public void setSourceSubsampling(int pXSub, int pYSub) {
|
public void setSourceSubsampling(int xSubsampling, int ySubsampling) {
|
||||||
// Re-fetch everything, if subsampling changed
|
// Re-fetch everything, if subsampling changed
|
||||||
if (xSub != pXSub || ySub != pYSub) {
|
if (xSub != xSubsampling || ySub != ySubsampling) {
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pXSub > 1) {
|
if (xSubsampling > 1) {
|
||||||
xSub = pXSub;
|
xSub = xSubsampling;
|
||||||
}
|
}
|
||||||
if (pYSub > 1) {
|
if (ySubsampling > 1) {
|
||||||
ySub = pYSub;
|
ySub = ySubsampling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
|
private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException {
|
||||||
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
||||||
// NOTE: Subsampling is only applied if extracting full image
|
// NOTE: Subsampling is only applied if extracting full image
|
||||||
if (!pColorModelOnly && (xSub > 1 || ySub > 1)) {
|
if (!colorModelOnly && (xSub > 1 || ySub > 1)) {
|
||||||
// If only sampling a region, the region must be scaled too
|
// If only sampling a region, the region must be scaled too
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
width = (width + xSub - 1) / xSub;
|
width = (width + xSub - 1) / xSub;
|
||||||
@@ -207,38 +207,41 @@ public final class BufferedImageFactory {
|
|||||||
|
|
||||||
// Start fetching
|
// Start fetching
|
||||||
fetching = true;
|
fetching = true;
|
||||||
readColorModelOnly = pColorModelOnly;
|
readColorModelOnly = colorModelOnly;
|
||||||
|
|
||||||
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
|
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
|
||||||
|
|
||||||
// Wait until the producer wakes us up, by calling imageComplete
|
// Wait until the producer wakes us up, by calling imageComplete
|
||||||
while (fetching) {
|
while (fetching) {
|
||||||
try {
|
try {
|
||||||
wait(200l);
|
wait(200L);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
|
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumerException != null) {
|
try {
|
||||||
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
if (consumerException != null) {
|
||||||
}
|
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
||||||
|
}
|
||||||
|
|
||||||
if (pColorModelOnly) {
|
if (colorModelOnly) {
|
||||||
createColorModel();
|
createColorModel();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createBuffered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
finally {
|
||||||
createBuffered();
|
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||||
|
freeResources();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createColorModel() {
|
private void createColorModel() {
|
||||||
colorModel = sourceColorModel;
|
colorModel = sourceColorModel;
|
||||||
|
|
||||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
|
||||||
freeResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createBuffered() {
|
private void createBuffered() {
|
||||||
@@ -253,8 +256,9 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
if (buffered == null) {
|
||||||
freeResources();
|
throw new ImageConversionException("Could not create BufferedImage");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void freeResources() {
|
private void freeResources() {
|
||||||
@@ -280,27 +284,27 @@ public final class BufferedImageFactory {
|
|||||||
/**
|
/**
|
||||||
* Adds a progress listener to this factory.
|
* Adds a progress listener to this factory.
|
||||||
*
|
*
|
||||||
* @param pListener the progress listener
|
* @param listener the progress listener
|
||||||
*/
|
*/
|
||||||
public void addProgressListener(ProgressListener pListener) {
|
public void addProgressListener(ProgressListener listener) {
|
||||||
if (pListener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listeners == null) {
|
if (listeners == null) {
|
||||||
listeners = new CopyOnWriteArrayList<ProgressListener>();
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners.add(pListener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a progress listener from this factory.
|
* Removes a progress listener from this factory.
|
||||||
*
|
*
|
||||||
* @param pListener the progress listener
|
* @param listener the progress listener
|
||||||
*/
|
*/
|
||||||
public void removeProgressListener(ProgressListener pListener) {
|
public void removeProgressListener(ProgressListener listener) {
|
||||||
if (pListener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +312,7 @@ public final class BufferedImageFactory {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners.remove(pListener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,21 +328,22 @@ public final class BufferedImageFactory {
|
|||||||
* Converts an array of {@code int} pixels to an array of {@code short}
|
* Converts an array of {@code int} pixels to an array of {@code short}
|
||||||
* pixels. The conversion is done, by masking out the
|
* pixels. The conversion is done, by masking out the
|
||||||
* <em>higher 16 bits</em> of the {@code int}.
|
* <em>higher 16 bits</em> of the {@code int}.
|
||||||
*
|
* <p>
|
||||||
* For any given {@code int}, the {@code short} value is computed as
|
* For any given {@code int}, the {@code short} value is computed as
|
||||||
* follows:
|
* follows:
|
||||||
* <blockquote>{@code
|
* <blockquote>{@code
|
||||||
* short value = (short) (intValue & 0x0000ffff);
|
* short value = (short) (intValue & 0x0000ffff);
|
||||||
* }</blockquote>
|
* }</blockquote>
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param pPixels the pixel data to convert
|
* @param inputPixels the pixel data to convert
|
||||||
* @return an array of {@code short}s, same lenght as {@code pPixels}
|
* @return an array of {@code short}s, same length as {@code inputPixels}
|
||||||
*/
|
*/
|
||||||
private static short[] toShortPixels(int[] pPixels) {
|
private static short[] toShortPixels(int[] inputPixels) {
|
||||||
short[] pixels = new short[pPixels.length];
|
short[] pixels = new short[inputPixels.length];
|
||||||
|
|
||||||
for (int i = 0; i < pixels.length; i++) {
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
pixels[i] = (short) (pPixels[i] & 0xffff);
|
pixels[i] = (short) (inputPixels[i] & 0xffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pixels;
|
return pixels;
|
||||||
@@ -351,17 +356,17 @@ public final class BufferedImageFactory {
|
|||||||
* @see BufferedImageFactory#addProgressListener
|
* @see BufferedImageFactory#addProgressListener
|
||||||
* @see BufferedImageFactory#removeProgressListener
|
* @see BufferedImageFactory#removeProgressListener
|
||||||
*/
|
*/
|
||||||
public static interface ProgressListener extends EventListener {
|
public interface ProgressListener extends EventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports progress to this listener.
|
* Reports progress to this listener.
|
||||||
* Invoked by the {@code BufferedImageFactory} to report progress in
|
* Invoked by the {@code BufferedImageFactory} to report progress in
|
||||||
* the image decoding.
|
* the image decoding.
|
||||||
*
|
*
|
||||||
* @param pFactory the factory reporting the progress
|
* @param factory the factory reporting the progress
|
||||||
* @param pPercentage the percentage of progress
|
* @param percentage the percentage of progress
|
||||||
*/
|
*/
|
||||||
void progress(BufferedImageFactory pFactory, float pPercentage);
|
void progress(BufferedImageFactory factory, float percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Consumer implements ImageConsumer {
|
private class Consumer implements ImageConsumer {
|
||||||
@@ -446,18 +451,18 @@ public final class BufferedImageFactory {
|
|||||||
processProgress(pY + pHeight);
|
processProgress(pY + pHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, short[] pixels, int offset, int scanSize) {
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setColorModelOnce(final ColorModel pModel) {
|
private void setColorModelOnce(final ColorModel colorModel) {
|
||||||
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
|
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
|
||||||
// first passes the original color model through in setColorModel, then
|
// first passes the original color model through in setColorModel, then
|
||||||
// later replaces it with the default RGB in the first setPixels call
|
// later replaces it with the default RGB in the first setPixels call
|
||||||
// (this is probably allowed according to the spec, but it's a waste of time and space).
|
// (this is probably allowed according to the spec, but it's a waste of time and space).
|
||||||
if (sourceColorModel != pModel) {
|
if (sourceColorModel != colorModel) {
|
||||||
if (/*sourceColorModel == null ||*/ sourcePixels == null) {
|
if (sourcePixels == null) {
|
||||||
sourceColorModel = pModel;
|
sourceColorModel = colorModel;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
||||||
@@ -470,17 +475,16 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void imageComplete(int pStatus) {
|
@Override
|
||||||
|
public void imageComplete(int status) {
|
||||||
fetching = false;
|
fetching = false;
|
||||||
|
|
||||||
if (producer != null) {
|
if (producer != null) {
|
||||||
producer.removeConsumer(this);
|
producer.removeConsumer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (pStatus) {
|
if (status == ImageConsumer.IMAGEERROR) {
|
||||||
case ImageConsumer.IMAGEERROR:
|
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
||||||
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (BufferedImageFactory.this) {
|
synchronized (BufferedImageFactory.this) {
|
||||||
@@ -488,16 +492,18 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColorModel(ColorModel pModel) {
|
@Override
|
||||||
setColorModelOnce(pModel);
|
public void setColorModel(ColorModel colorModel) {
|
||||||
|
setColorModelOnce(colorModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDimensions(int pWidth, int pHeight) {
|
@Override
|
||||||
|
public void setDimensions(int w, int h) {
|
||||||
if (width < 0) {
|
if (width < 0) {
|
||||||
width = pWidth - x;
|
width = w - x;
|
||||||
}
|
}
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = pHeight - y;
|
height = h - y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hmm.. Special case, but is it a good idea?
|
// Hmm.. Special case, but is it a good idea?
|
||||||
@@ -506,27 +512,31 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHints(int pHintflags) {
|
@Override
|
||||||
|
public void setHints(int hintFlags) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
|
@Override
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, byte[] pixels, int offset, int scanSize) {
|
||||||
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
|
@Override
|
||||||
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, int[] pixels, int offset, int scanSize) {
|
||||||
|
if (colorModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||||
// NOTE: Workaround for limitation in ImageConsumer API
|
// NOTE: Workaround for limitation in ImageConsumer API
|
||||||
// Convert int[] to short[], to be compatible with the ColorModel
|
// Convert int[] to short[], to be compatible with the ColorModel
|
||||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, toShortPixels(pixels), offset, scanSize);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProperties(Hashtable pProperties) {
|
@Override
|
||||||
sourceProperties = pProperties;
|
public void setProperties(Hashtable properties) {
|
||||||
|
sourceProperties = properties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ import java.awt.image.BufferedImage;
|
|||||||
*/
|
*/
|
||||||
public class BufferedImageIcon implements Icon {
|
public class BufferedImageIcon implements Icon {
|
||||||
private final BufferedImage image;
|
private final BufferedImage image;
|
||||||
private int width;
|
private final int width;
|
||||||
private int height;
|
private final int height;
|
||||||
private final boolean fast;
|
private final boolean fast;
|
||||||
|
|
||||||
public BufferedImageIcon(BufferedImage pImage) {
|
public BufferedImageIcon(BufferedImage pImage) {
|
||||||
@@ -81,11 +81,10 @@ public class BufferedImageIcon implements Icon {
|
|||||||
else {
|
else {
|
||||||
//System.out.println("Scaling using interpolation");
|
//System.out.println("Scaling using interpolation");
|
||||||
Graphics2D g2 = (Graphics2D) g;
|
Graphics2D g2 = (Graphics2D) g;
|
||||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
|
||||||
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
transform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
g2.drawImage(image, transform, null);
|
||||||
g2.drawImage(image, xform, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -844,7 +844,7 @@ public final class ImageUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i > mapSize1; i++) {
|
for (int i = 0; i < mapSize1; i++) {
|
||||||
if (icm1.getRGB(i) != icm2.getRGB(i)) {
|
if (icm1.getRGB(i) != icm2.getRGB(i)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,6 +587,7 @@ class IndexImage {
|
|||||||
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
||||||
* This version will be removed in a later version of the API.
|
* This version will be removed in a later version of the API.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
||||||
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-16
@@ -30,17 +30,26 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import org.junit.Test;
|
import static java.lang.Math.min;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.image.*;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.BufferedImageOp;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.ImagingOpException;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.RasterOp;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AffineTransformOpTest.
|
* AffineTransformOpTest.
|
||||||
@@ -101,6 +110,7 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
private final int width = 30;
|
private final int width = 30;
|
||||||
private final int height = 20;
|
private final int height = 20;
|
||||||
|
private final double anchor = min(width, height) / 2.0;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetPoint2D() {
|
public void testGetPoint2D() {
|
||||||
@@ -128,8 +138,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateBIStandard() {
|
public void testFilterRotateBIStandard() {
|
||||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (Integer type : TYPES) {
|
for (Integer type : TYPES) {
|
||||||
BufferedImage image = new BufferedImage(width, height, type);
|
BufferedImage image = new BufferedImage(width, height, type);
|
||||||
@@ -147,8 +157,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateBICustom() {
|
public void testFilterRotateBICustom() {
|
||||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (ImageTypeSpecifier spec : SPECS) {
|
for (ImageTypeSpecifier spec : SPECS) {
|
||||||
BufferedImage image = spec.createBufferedImage(width, height);
|
BufferedImage image = spec.createBufferedImage(width, height);
|
||||||
@@ -197,8 +207,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateRasterStandard() {
|
public void testFilterRotateRasterStandard() {
|
||||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (Integer type : TYPES) {
|
for (Integer type : TYPES) {
|
||||||
Raster raster = new BufferedImage(width, height, type).getRaster();
|
Raster raster = new BufferedImage(width, height, type).getRaster();
|
||||||
@@ -221,8 +231,6 @@ public class AffineTransformOpTest {
|
|||||||
fail("No result!");
|
fail("No result!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
|
|
||||||
System.err.println("type: " + type);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,8 +248,8 @@ public class AffineTransformOpTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFilterRotateRasterCustom() {
|
public void testFilterRotateRasterCustom() {
|
||||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||||
|
|
||||||
for (ImageTypeSpecifier spec : SPECS) {
|
for (ImageTypeSpecifier spec : SPECS) {
|
||||||
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
||||||
@@ -264,8 +272,6 @@ public class AffineTransformOpTest {
|
|||||||
fail("No result!");
|
fail("No result!");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
|
|
||||||
System.err.println("spec: " + spec);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-14
@@ -34,14 +34,13 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.ImageProducer;
|
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BufferedImageFactoryTestCase
|
* BufferedImageFactoryTestCase
|
||||||
@@ -260,9 +259,9 @@ public class BufferedImageFactoryTest {
|
|||||||
|
|
||||||
// Listener should abort ASAP
|
// Listener should abort ASAP
|
||||||
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
if (pPercentage > 5) {
|
if (percentage > 5) {
|
||||||
pFactory.abort();
|
factory.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -343,7 +342,7 @@ public class BufferedImageFactoryTest {
|
|||||||
VerifyingListener listener = new VerifyingListener(factory);
|
VerifyingListener listener = new VerifyingListener(factory);
|
||||||
factory.addProgressListener(listener);
|
factory.addProgressListener(listener);
|
||||||
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
factory.getBufferedImage();
|
factory.getBufferedImage();
|
||||||
@@ -380,11 +379,11 @@ public class BufferedImageFactoryTest {
|
|||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
assertEquals(factory, pFactory);
|
assertEquals(this.factory, factory);
|
||||||
assertTrue(pPercentage >= progress && pPercentage <= 100f);
|
assertTrue(percentage >= progress && percentage <= 100f);
|
||||||
|
|
||||||
progress = pPercentage;
|
progress = percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,19 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: IO</name>
|
<name>TwelveMonkeys :: Common :: IO</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common IO support
|
TwelveMonkeys Common I/O support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.common.io</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
@@ -27,4 +31,12 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
private int currentReader;
|
private int currentReader;
|
||||||
private int markedReader;
|
private int markedReader;
|
||||||
private int mark;
|
private long mark;
|
||||||
private int mNext;
|
private long next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new compound reader.
|
* Create a new compound reader.
|
||||||
@@ -76,7 +76,7 @@ public class CompoundReader extends Reader {
|
|||||||
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
||||||
// reference can't change, only it's elements
|
// reference can't change, only it's elements
|
||||||
|
|
||||||
readers = new ArrayList<Reader>();
|
readers = new ArrayList<>();
|
||||||
|
|
||||||
boolean markSupported = true;
|
boolean markSupported = true;
|
||||||
while (pReaders.hasNext()) {
|
while (pReaders.hasNext()) {
|
||||||
@@ -101,7 +101,7 @@ public class CompoundReader extends Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||||
mNext = 0;
|
next = 0;
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
mark = mNext;
|
mark = next;
|
||||||
markedReader = currentReader;
|
markedReader = currentReader;
|
||||||
|
|
||||||
current.mark(pReadLimit);
|
current.mark(pReadLimit);
|
||||||
@@ -158,7 +158,7 @@ public class CompoundReader extends Reader {
|
|||||||
}
|
}
|
||||||
current.reset();
|
current.reset();
|
||||||
|
|
||||||
mNext = mark;
|
next = mark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +177,13 @@ public class CompoundReader extends Reader {
|
|||||||
return read(); // In case of 0-length readers
|
return read(); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext++;
|
next++;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
int read = current.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ public class CompoundReader extends Reader {
|
|||||||
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += read;
|
next += read;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ public class CompoundReader extends Reader {
|
|||||||
return skip(pChars); // In case of 0-length readers
|
return skip(pChars); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += skipped;
|
next += skipped;
|
||||||
|
|
||||||
return skipped;
|
return skipped;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
||||||
@@ -44,9 +45,6 @@ import java.io.OutputStream;
|
|||||||
*/
|
*/
|
||||||
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
||||||
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||||
/** Max grow size (unless if writing more than this amount of bytes) */
|
|
||||||
protected int maxGrowSize = 1024 * 1024; // 1 MB
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||||
* size.
|
* size.
|
||||||
@@ -72,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(byte pBytes[], int pOffset, int pLength) {
|
public void write(byte[] pBytes, int pOffset, int pLength) {
|
||||||
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||||
throw new IndexOutOfBoundsException();
|
throw new IndexOutOfBoundsException();
|
||||||
@@ -97,10 +95,8 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
|
|
||||||
private void growIfNeeded(int pNewCount) {
|
private void growIfNeeded(int pNewCount) {
|
||||||
if (pNewCount > buf.length) {
|
if (pNewCount > buf.length) {
|
||||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
|
int newSize = Math.max(buf.length << 1, pNewCount);
|
||||||
byte newBuf[] = new byte[newSize];
|
buf = Arrays.copyOf(buf, newSize);
|
||||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
|
||||||
buf = newBuf;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,10 +109,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
// Non-synchronized version of toByteArray
|
// Non-synchronized version of toByteArray
|
||||||
@Override
|
@Override
|
||||||
public byte[] toByteArray() {
|
public byte[] toByteArray() {
|
||||||
byte newBuf[] = new byte[count];
|
return Arrays.copyOf(buf, count);
|
||||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
|
||||||
|
|
||||||
return newBuf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ public final class FileUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the file (type) extension of the given file.
|
* Gets the file (type) extension of the given file.
|
||||||
* A file extension is the part of the filename, after the last occurence
|
* A file extension is the part of the filename, after the last occurrence
|
||||||
* of a period {@code '.'}.
|
* of a period {@code '.'}.
|
||||||
* If the filename contains no period, {@code null} is returned.
|
* If the filename contains no period, {@code null} is returned.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import java.io.FilenameFilter;
|
|||||||
* @see WildcardStringParser
|
* @see WildcardStringParser
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class FilenameMaskFilter implements FilenameFilter {
|
public class FilenameMaskFilter implements FilenameFilter {
|
||||||
|
|
||||||
// TODO: Rewrite to use regexp, or create new class
|
// TODO: Rewrite to use regexp, or create new class
|
||||||
|
|||||||
@@ -442,6 +442,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
* @see java.io.BufferedReader#readLine()
|
* @see java.io.BufferedReader#readLine()
|
||||||
* @see java.io.DataInputStream#readLine()
|
* @see java.io.DataInputStream#readLine()
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String readLine() throws IOException {
|
public String readLine() throws IOException {
|
||||||
DataInputStream ds = new DataInputStream(in);
|
DataInputStream ds = new DataInputStream(in);
|
||||||
return ds.readLine();
|
return ds.readLine();
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class StringArrayReader extends StringReader {
|
|||||||
protected final Object finalLock;
|
protected final Object finalLock;
|
||||||
private int currentSting;
|
private int currentSting;
|
||||||
private int markedString;
|
private int markedString;
|
||||||
private int mark;
|
private long mark;
|
||||||
private int next;
|
private long next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new string array reader.
|
* Create a new string array reader.
|
||||||
@@ -151,7 +151,7 @@ public class StringArrayReader extends StringReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
int read = current.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
|
|||||||
@@ -41,21 +41,20 @@ import java.io.InputStream;
|
|||||||
* underlying stream.
|
* underlying stream.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
|
|
||||||
*/
|
*/
|
||||||
public final class SubStream extends FilterInputStream {
|
public final class SubStream extends FilterInputStream {
|
||||||
private long bytesLeft;
|
private long bytesLeft;
|
||||||
private int markLimit;
|
private int markLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
* Creates a {@code SubStream} of the given {@code stream}.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream
|
* @param stream the underlying input stream
|
||||||
* @param pLength maximum number of bytes to read drom this stream
|
* @param length maximum number of bytes to read from this stream
|
||||||
*/
|
*/
|
||||||
public SubStream(final InputStream pStream, final long pLength) {
|
public SubStream(final InputStream stream, final long length) {
|
||||||
super(Validate.notNull(pStream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
bytesLeft = pLength;
|
bytesLeft = Validate.isTrue(length >= 0, length, "length < 0: %s");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,22 +63,23 @@ public final class SubStream extends FilterInputStream {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// NOTE: Do not close the underlying stream
|
// NOTE: Do not close the underlying stream, but consume it
|
||||||
while (bytesLeft > 0) {
|
while (bytesLeft > 0) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
if (skip(bytesLeft) <= 0 && read() < 0) {
|
||||||
skip(bytesLeft);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
public int available() throws IOException {
|
||||||
return (int) Math.min(super.available(), bytesLeft);
|
return (int) findMaxLen(super.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int pReadLimit) {
|
public void mark(int readLimit) {
|
||||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
super.mark(readLimit);// This either succeeds or does nothing...
|
||||||
markLimit = pReadLimit;
|
markLimit = readLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,44 +93,42 @@ public final class SubStream extends FilterInputStream {
|
|||||||
if (bytesLeft-- <= 0) {
|
if (bytesLeft-- <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.read();
|
return super.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int read(byte[] pBytes) throws IOException {
|
public int read(byte[] bytes) throws IOException {
|
||||||
return read(pBytes, 0, pBytes.length);
|
return read(bytes, 0, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] bytes, final int off, final int len) throws IOException {
|
||||||
if (bytesLeft <= 0) {
|
if (bytesLeft <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
int read = super.read(bytes, off, (int) findMaxLen(len));
|
||||||
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long length) throws IOException {
|
||||||
|
long skipped = super.skip(findMaxLen(length)); // Skips 0 or more, never -1
|
||||||
|
bytesLeft -= skipped;
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the maximum number of bytes we can read or skip, from this stream.
|
* Finds the maximum number of bytes we can read or skip, from this stream.
|
||||||
*
|
*
|
||||||
* @param pLength the requested length
|
* @param length the requested length
|
||||||
* @return the maximum number of bytes to read
|
* @return the maximum number of bytes to read
|
||||||
*/
|
*/
|
||||||
private long findMaxLen(long pLength) {
|
private long findMaxLen(long length) {
|
||||||
if (bytesLeft < pLength) {
|
return bytesLeft < length ? Math.max(bytesLeft, 0) : length;
|
||||||
return (int) Math.max(bytesLeft, 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return pLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long skip(long pLength) throws IOException {
|
|
||||||
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
|
||||||
bytesLeft -= skipped;
|
|
||||||
return skipped;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,39 +45,39 @@ import java.nio.ByteBuffer;
|
|||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
||||||
*/
|
*/
|
||||||
public final class DecoderStream extends FilterInputStream {
|
public final class DecoderStream extends FilterInputStream {
|
||||||
protected final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
protected final Decoder decoder;
|
private final Decoder decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder stream and chains it to the
|
* Creates a new decoder stream and chains it to the
|
||||||
* input stream specified by the {@code pStream} argument.
|
* input stream specified by the {@code stream} argument.
|
||||||
* The stream will use a default decode buffer size.
|
* The stream will use a default decode buffer size.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream.
|
* @param stream the underlying input stream.
|
||||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
* @param decoder the decoder that will be used to decode the underlying stream
|
||||||
*
|
*
|
||||||
* @see java.io.FilterInputStream#in
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
public DecoderStream(final InputStream stream, final Decoder decoder) {
|
||||||
// TODO: Let the decoder decide preferred buffer size
|
// TODO: Let the decoder decide preferred buffer size
|
||||||
this(pStream, pDecoder, 1024);
|
this(stream, decoder, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder stream and chains it to the
|
* Creates a new decoder stream and chains it to the
|
||||||
* input stream specified by the {@code pStream} argument.
|
* input stream specified by the {@code stream} argument.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream.
|
* @param stream the underlying input stream.
|
||||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
* @param decoder the decoder that will be used to decode the underlying stream
|
||||||
* @param pBufferSize the size of the decode buffer
|
* @param bufferSize the size of the decode buffer
|
||||||
*
|
*
|
||||||
* @see java.io.FilterInputStream#in
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
|
||||||
super(pStream);
|
super(stream);
|
||||||
|
|
||||||
decoder = pDecoder;
|
this.decoder = decoder;
|
||||||
buffer = ByteBuffer.allocate(pBufferSize);
|
buffer = ByteBuffer.allocate(bufferSize);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
return buffer.get() & 0xff;
|
return buffer.get() & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
if (pBytes == null) {
|
if (bytes == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
else if ((offset < 0) || (offset > bytes.length) || (length < 0) ||
|
||||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
((offset + length) > bytes.length) || ((offset + length) < 0)) {
|
||||||
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
|
throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length);
|
||||||
}
|
}
|
||||||
else if (pLength == 0) {
|
else if (length == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read until we have read pLength bytes, or have reached EOF
|
// Read until we have read length bytes, or have reached EOF
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int off = pOffset;
|
int off = offset;
|
||||||
|
|
||||||
while (pLength > count) {
|
while (length > count) {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
break;
|
break;
|
||||||
@@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy as many bytes as possible
|
// Copy as many bytes as possible
|
||||||
int dstLen = Math.min(pLength - count, buffer.remaining());
|
int dstLen = Math.min(length - count, buffer.remaining());
|
||||||
buffer.get(pBytes, off, dstLen);
|
buffer.get(bytes, off, dstLen);
|
||||||
|
|
||||||
// Update offset (rest)
|
// Update offset (rest)
|
||||||
off += dstLen;
|
off += dstLen;
|
||||||
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long skip(final long pLength) throws IOException {
|
public long skip(final long length) throws IOException {
|
||||||
// End of file?
|
// End of file?
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
@@ -147,10 +147,10 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
// Skip until we have skipped length bytes, or have reached EOF
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
|
||||||
while (total < pLength) {
|
while (total < length) {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
break;
|
break;
|
||||||
@@ -158,7 +158,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
// 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());
|
int skipped = (int) Math.min(length - total, buffer.remaining());
|
||||||
buffer.position(buffer.position() + skipped);
|
buffer.position(buffer.position() + skipped);
|
||||||
total += skipped;
|
total += skipped;
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
*
|
*
|
||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
protected int fill() throws IOException {
|
private int fill() throws IOException {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
int read = decoder.decode(in, buffer);
|
int read = decoder.decode(in, buffer);
|
||||||
|
|
||||||
|
|||||||
@@ -45,41 +45,39 @@ import java.nio.ByteBuffer;
|
|||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
||||||
*/
|
*/
|
||||||
public final class EncoderStream extends FilterOutputStream {
|
public final class EncoderStream extends FilterOutputStream {
|
||||||
// TODO: This class need a test case ASAP!!!
|
|
||||||
|
|
||||||
protected final Encoder encoder;
|
private final Encoder encoder;
|
||||||
private final boolean flushOnWrite;
|
private final boolean flushOnWrite;
|
||||||
|
|
||||||
protected final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an output stream filter built on top of the specified
|
* Creates an output stream filter built on top of the specified
|
||||||
* underlying output stream.
|
* underlying output stream.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying output stream
|
* @param stream the underlying output stream
|
||||||
* @param pEncoder the encoder to use
|
* @param encoder the encoder to use
|
||||||
*/
|
*/
|
||||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
|
public EncoderStream(final OutputStream stream, final Encoder encoder) {
|
||||||
this(pStream, pEncoder, false);
|
this(stream, encoder, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an output stream filter built on top of the specified
|
* Creates an output stream filter built on top of the specified
|
||||||
* underlying output stream.
|
* underlying output stream.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying output stream
|
* @param stream the underlying output stream
|
||||||
* @param pEncoder the encoder to use
|
* @param encoder the encoder to use
|
||||||
* @param pFlushOnWrite if {@code true}, calls to the byte-array
|
* @param flushOnWrite if {@code true}, calls to the byte-array
|
||||||
* {@code write} methods will automatically flush the buffer.
|
* {@code write} methods will automatically flush the buffer.
|
||||||
*/
|
*/
|
||||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
|
||||||
super(pStream);
|
super(stream);
|
||||||
|
|
||||||
encoder = pEncoder;
|
this.encoder = encoder;
|
||||||
flushOnWrite = pFlushOnWrite;
|
this.flushOnWrite = flushOnWrite;
|
||||||
|
|
||||||
buffer = ByteBuffer.allocate(1024);
|
buffer = ByteBuffer.allocate(1024);
|
||||||
buffer.flip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
@@ -104,33 +102,33 @@ public final class EncoderStream extends FilterOutputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void write(final byte[] pBytes) throws IOException {
|
public void write(final byte[] bytes) throws IOException {
|
||||||
write(pBytes, 0, pBytes.length);
|
write(bytes, 0, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify that this works for the general case (it probably won't)...
|
// TODO: Verify that this works for the general case (it probably won't)...
|
||||||
// TODO: We might need a way to explicitly flush the encoder, or specify
|
// TODO: We might need a way to explicitly flush the encoder, or specify
|
||||||
// that the encoder can't buffer. In that case, the encoder should probably
|
// that the encoder can't buffer. In that case, the encoder should probably
|
||||||
// tell the EncoderStream how large buffer it prefers...
|
// tell the EncoderStream how large buffer it prefers...
|
||||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public void write(final byte[] values, final int offset, final int length) throws IOException {
|
||||||
if (!flushOnWrite && pLength < buffer.remaining()) {
|
if (!flushOnWrite && length < buffer.remaining()) {
|
||||||
// Buffer data
|
// Buffer data
|
||||||
buffer.put(pBytes, pOffset, pLength);
|
buffer.put(values, offset, length);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Encode data already in the buffer
|
// Encode data already in the buffer
|
||||||
encodeBuffer();
|
encodeBuffer();
|
||||||
|
|
||||||
// Encode rest without buffering
|
// Encode rest without buffering
|
||||||
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
|
encoder.encode(out, ByteBuffer.wrap(values, offset, length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(final int pByte) throws IOException {
|
public void write(final int value) throws IOException {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
encodeBuffer(); // Resets bufferPos to 0
|
encodeBuffer(); // Resets bufferPos to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.put((byte) pByte);
|
buffer.put((byte) value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.xml;
|
package com.twelvemonkeys.xml;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.Writer;
|
||||||
|
|
||||||
import org.w3c.dom.DOMConfiguration;
|
import org.w3c.dom.DOMConfiguration;
|
||||||
import org.w3c.dom.DOMImplementationList;
|
import org.w3c.dom.DOMImplementationList;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
@@ -38,9 +41,6 @@ import org.w3c.dom.ls.DOMImplementationLS;
|
|||||||
import org.w3c.dom.ls.LSOutput;
|
import org.w3c.dom.ls.LSOutput;
|
||||||
import org.w3c.dom.ls.LSSerializer;
|
import org.w3c.dom.ls.LSSerializer;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Writer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code DOMImplementationLS} backed implementation.
|
* {@code DOMImplementationLS} backed implementation.
|
||||||
*
|
*
|
||||||
@@ -88,17 +88,6 @@ public final class DOMSerializer {
|
|||||||
output.setCharacterStream(pStream);
|
output.setCharacterStream(pStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: Is it useful?
|
|
||||||
public void setNewLine(final String pNewLine) {
|
|
||||||
serializer.setNewLine(pNewLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNewLine() {
|
|
||||||
return serializer.getNewLine();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies wether the serializer should use indentation and optimize for
|
* Specifies wether the serializer should use indentation and optimize for
|
||||||
* readability.
|
* readability.
|
||||||
@@ -169,13 +158,7 @@ public final class DOMSerializer {
|
|||||||
try {
|
try {
|
||||||
return DOMImplementationRegistry.newInstance();
|
return DOMImplementationRegistry.newInstance();
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException e) {
|
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
catch (InstantiationException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
catch (IllegalAccessException e) {
|
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,16 +30,23 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.xml;
|
package com.twelvemonkeys.xml;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import java.io.ByteArrayInputStream;
|
||||||
import org.w3c.dom.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
import org.xml.sax.SAXException;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import java.io.*;
|
|
||||||
import java.nio.charset.Charset;
|
import org.w3c.dom.*;
|
||||||
import java.util.Date;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XMLSerializer
|
* XMLSerializer
|
||||||
@@ -290,7 +297,7 @@ public class XMLSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
||||||
pBuilder.append(pString.substring(pStart, pEnd));
|
pBuilder.append(pString, pStart, pEnd);
|
||||||
pBuilder.append(pEntity);
|
pBuilder.append(pEntity);
|
||||||
return pEnd + 1;
|
return pEnd + 1;
|
||||||
}
|
}
|
||||||
@@ -527,8 +534,7 @@ public class XMLSerializer {
|
|||||||
builder = factory.newDocumentBuilder();
|
builder = factory.newDocumentBuilder();
|
||||||
}
|
}
|
||||||
catch (ParserConfigurationException e) {
|
catch (ParserConfigurationException e) {
|
||||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
throw new IOException(e);
|
||||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DOMImplementation dom = builder.getDOMImplementation();
|
DOMImplementation dom = builder.getDOMImplementation();
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubStreamTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SubStreamTest.java,v 1.0 07/11/2023 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class SubStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(2918475687L);
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateNullStream() {
|
||||||
|
new SubStream(null, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateNegativeLength() {
|
||||||
|
new SubStream(new ByteArrayInputStream(new byte[1]), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAll() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
rng.nextBytes(buf);
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
for (byte b : buf) {
|
||||||
|
assertEquals(b, (byte) stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAllArray() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
rng.nextBytes(buf);
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
byte[] temp = new byte[buf.length / 4];
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
assertEquals(temp.length, stream.read(temp)); // Depends on ByteArrayInputStream specifics...
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(buf, i * temp.length, (i + 1) * temp.length), temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSkipAll() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
assertEquals(128, stream.skip(buf.length)); // Depends on ByteArrayInputStream specifics...
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test
|
||||||
|
public void testCloseConsumesAll() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[128]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 128)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, stream.available());
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test
|
||||||
|
public void testCloseConsumesAllLongStream() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[256]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 128)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(128, stream.available());
|
||||||
|
assertEquals(0, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test(timeout = 500L)
|
||||||
|
public void testCloseConsumesAllShortStream() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[13]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 42)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, stream.available());
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.enc;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class DecoderStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(5467809876546L);
|
||||||
|
|
||||||
|
private byte[] createData(final int length) {
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
rng.nextBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeSingleBytes() throws IOException {
|
||||||
|
byte[] data = createData(1327);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
for (byte datum : data) {
|
||||||
|
int read = stream.read();
|
||||||
|
assertNotEquals(-1, read);
|
||||||
|
assertEquals(datum, (byte) read);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeArray() throws IOException {
|
||||||
|
int length = 793;
|
||||||
|
byte[] data = createData(length * 10);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
byte[] result = new byte[477];
|
||||||
|
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
int dataOffset = 0;
|
||||||
|
while (dataOffset < data.length) {
|
||||||
|
int count = stream.read(result);
|
||||||
|
|
||||||
|
assertFalse(count <= 0);
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(data, dataOffset, dataOffset + count), Arrays.copyOfRange(result, 0, count));
|
||||||
|
|
||||||
|
dataOffset += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeArrayOffset() throws IOException {
|
||||||
|
int length = 793;
|
||||||
|
byte[] data = createData(length * 10);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
byte[] result = new byte[477];
|
||||||
|
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
int dataOffset = 0;
|
||||||
|
while (dataOffset < data.length) {
|
||||||
|
int resultOffset = dataOffset % result.length;
|
||||||
|
int count = stream.read(result, resultOffset, result.length - resultOffset);
|
||||||
|
|
||||||
|
assertFalse(count <= 0);
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(data, dataOffset + resultOffset, dataOffset + count), Arrays.copyOfRange(result, resultOffset, count));
|
||||||
|
|
||||||
|
dataOffset += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class NullDecoder implements Decoder {
|
||||||
|
@Override
|
||||||
|
public int decode(InputStream stream, ByteBuffer buffer) throws IOException {
|
||||||
|
int read = stream.read(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||||
|
|
||||||
|
if (read > 0) {
|
||||||
|
// Set position, should be equivalent to using buffer.put(stream.read()) until EOF or buffer full
|
||||||
|
buffer.position(read);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,13 +32,13 @@ package com.twelvemonkeys.io.enc;
|
|||||||
|
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
import com.twelvemonkeys.lang.ObjectAbstractTest;
|
import com.twelvemonkeys.lang.ObjectAbstractTest;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +73,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createData(final int pLength) throws Exception {
|
private byte[] createData(final int pLength) {
|
||||||
byte[] bytes = new byte[pLength];
|
byte[] bytes = new byte[pLength];
|
||||||
RANDOM.nextBytes(bytes);
|
RANDOM.nextBytes(bytes);
|
||||||
return bytes;
|
return bytes;
|
||||||
@@ -82,9 +82,8 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
private void runStreamTest(final int pLength) throws Exception {
|
private void runStreamTest(final int pLength) throws Exception {
|
||||||
byte[] data = createData(pLength);
|
byte[] data = createData(pLength);
|
||||||
ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
|
ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
|
||||||
OutputStream out = new EncoderStream(outBytes, createEncoder(), true);
|
|
||||||
|
|
||||||
try {
|
try (OutputStream out = new EncoderStream(outBytes, createEncoder(), true)) {
|
||||||
// Provoke failure for encoders that doesn't take array offset properly into account
|
// Provoke failure for encoders that doesn't take array offset properly into account
|
||||||
int off = (data.length + 1) / 2;
|
int off = (data.length + 1) / 2;
|
||||||
out.write(data, 0, off);
|
out.write(data, 0, off);
|
||||||
@@ -92,9 +91,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
out.write(data, off, data.length - off);
|
out.write(data, off, data.length - off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] encoded = outBytes.toByteArray();
|
byte[] encoded = outBytes.toByteArray();
|
||||||
|
|
||||||
@@ -102,7 +98,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
// System.err.println("encoded: " + Arrays.toString(encoded));
|
// System.err.println("encoded: " + Arrays.toString(encoded));
|
||||||
|
|
||||||
byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder()));
|
byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder()));
|
||||||
assertTrue(Arrays.equals(data, decoded));
|
assertArrayEquals(data, decoded);
|
||||||
|
|
||||||
InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder());
|
InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder());
|
||||||
outBytes = new ByteArrayOutputStream();
|
outBytes = new ByteArrayOutputStream();
|
||||||
@@ -116,7 +112,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
decoded = outBytes.toByteArray();
|
decoded = outBytes.toByteArray();
|
||||||
assertTrue(Arrays.equals(data, decoded));
|
assertArrayEquals(data, decoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -129,10 +125,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
fail(e.getMessage() + ": " + i);
|
fail(e.getMessage() + ": " + i);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage() + ": " + i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 100; i < 2000; i += 250) {
|
for (int i = 100; i < 2000; i += 250) {
|
||||||
@@ -143,10 +135,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
fail(e.getMessage() + ": " + i);
|
fail(e.getMessage() + ": " + i);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage() + ": " + i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 2000; i < 80000; i += 1000) {
|
for (int i = 2000; i < 80000; i += 1000) {
|
||||||
@@ -157,14 +145,8 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
fail(e.getMessage() + ": " + i);
|
fail(e.getMessage() + ": " + i);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
fail(e.getMessage() + ": " + i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset.
|
// TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset.
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.enc;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
|
public class EncoderStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(5467809876546L);
|
||||||
|
|
||||||
|
private byte[] createData(final int length) {
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
rng.nextBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeSingleBytes() throws IOException {
|
||||||
|
byte[] data = createData(1327);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (byte datum : data) {
|
||||||
|
stream.write(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals(data, result.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeArray() throws IOException {
|
||||||
|
byte[] data = createData(1793);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
stream.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encoded = result.toByteArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeArrayOffset() throws IOException {
|
||||||
|
byte[] data = createData(87);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
stream.write(data, 13, 59);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] original = Arrays.copyOfRange(data, 13, 13 + 59);
|
||||||
|
byte[] encoded = result.toByteArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class NullEncoder implements Encoder {
|
||||||
|
@Override
|
||||||
|
public void encode(OutputStream stream, ByteBuffer buffer) throws IOException {
|
||||||
|
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,25 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: Language support</name>
|
<name>TwelveMonkeys :: Common :: Language support</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common Language support
|
TwelveMonkeys Common language support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.common.lang</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -770,6 +770,7 @@ public final class StringUtil {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*public*/
|
/*public*/
|
||||||
|
@Deprecated
|
||||||
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
|
|
||||||
@@ -903,7 +904,7 @@ public final class StringUtil {
|
|||||||
}
|
}
|
||||||
catch (ParseException pe) {
|
catch (ParseException pe) {
|
||||||
// Wrap in RuntimeException
|
// Wrap in RuntimeException
|
||||||
throw new IllegalArgumentException(pe.getMessage());
|
throw new IllegalArgumentException(pe.getMessage() + " at pos " + pe.getErrorOffset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1464,6 +1465,7 @@ public final class StringUtil {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*public*/
|
/*public*/
|
||||||
|
@Deprecated
|
||||||
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
||||||
StringBuilder filteredString = new StringBuilder();
|
StringBuilder filteredString = new StringBuilder();
|
||||||
boolean insideDemarcatedArea = false;
|
boolean insideDemarcatedArea = false;
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple Map.Entry implementaton.
|
* A simple Map.Entry implementation.
|
||||||
*/
|
*/
|
||||||
static class BasicEntry<K, V> implements Entry<K, V>, Serializable {
|
static class BasicEntry<K, V> implements Entry<K, V>, Serializable {
|
||||||
K mKey;
|
K mKey;
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ public class Time {
|
|||||||
* @see #parseTime(String)
|
* @see #parseTime(String)
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public String toString(String pFormatStr) {
|
public String toString(String pFormatStr) {
|
||||||
TimeFormat tf = new TimeFormat(pFormatStr);
|
TimeFormat tf = new TimeFormat(pFormatStr);
|
||||||
|
|
||||||
@@ -174,6 +175,7 @@ public class Time {
|
|||||||
* @see #toString(String)
|
* @see #toString(String)
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static Time parseTime(String pStr) {
|
public static Time parseTime(String pStr) {
|
||||||
TimeFormat tf = TimeFormat.getInstance();
|
TimeFormat tf = TimeFormat.getInstance();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import java.io.Serializable;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code Map} implementation that removes (exipres) its elements after
|
* A {@code Map} implementation that removes (expires) its elements after
|
||||||
* a given period. The map is by default backed by a {@link java.util.HashMap},
|
* a given period. The map is by default backed by a {@link java.util.HashMap},
|
||||||
* or can be instantiated with any given {@code Map} as backing.
|
* or can be instantiated with any given {@code Map} as backing.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -67,7 +67,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
protected long expiryTime = 60000L; // 1 minute
|
protected long expiryTime = 60000L; // 1 minute
|
||||||
|
|
||||||
//////////////////////
|
//////////////////////
|
||||||
private volatile long nextExpiryTime;
|
private volatile long nextExpiryTime = Long.MAX_VALUE;
|
||||||
//////////////////////
|
//////////////////////
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,7 +178,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
* @return {@code true} if this map contains no key-value mappings.
|
* @return {@code true} if this map contains no key-value mappings.
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return (size() <= 0);
|
return size() <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,7 +208,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
* @see #containsKey(java.lang.Object)
|
* @see #containsKey(java.lang.Object)
|
||||||
*/
|
*/
|
||||||
public V get(Object pKey) {
|
public V get(Object pKey) {
|
||||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey);
|
TimedEntry entry = (TimedEntry) entries.get(pKey);
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -236,7 +236,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
* {@code null} values.
|
* {@code null} values.
|
||||||
*/
|
*/
|
||||||
public V put(K pKey, V pValue) {
|
public V put(K pKey, V pValue) {
|
||||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey);
|
TimedEntry entry = (TimedEntry) entries.get(pKey);
|
||||||
V oldValue;
|
V oldValue;
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
@@ -272,7 +272,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
* {@code null} values.
|
* {@code null} values.
|
||||||
*/
|
*/
|
||||||
public V remove(Object pKey) {
|
public V remove(Object pKey) {
|
||||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.remove(pKey);
|
TimedEntry entry = (TimedEntry) entries.remove(pKey);
|
||||||
return (entry != null) ? entry.getValue() : null;
|
return (entry != null) ? entry.getValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,13 +284,12 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*protected*/ TimedEntry<K, V> createEntry(K pKey, V pValue) {
|
/*protected*/ TimedEntry createEntry(K pKey, V pValue) {
|
||||||
return new TimedEntry<K, V>(pKey, pValue);
|
return new TimedEntry(pKey, pValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any expired mappings.
|
* Removes any expired mappings.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
protected void removeExpiredEntries() {
|
protected void removeExpiredEntries() {
|
||||||
// Remove any expired elements
|
// Remove any expired elements
|
||||||
@@ -312,7 +311,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
long next = Long.MAX_VALUE;
|
long next = Long.MAX_VALUE;
|
||||||
nextExpiryTime = next; // Avoid multiple runs...
|
nextExpiryTime = next; // Avoid multiple runs...
|
||||||
for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) {
|
for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) {
|
||||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) iterator.next();
|
TimedEntry entry = (TimedEntry) iterator.next();
|
||||||
////
|
////
|
||||||
long expires = entry.expires();
|
long expires = entry.expires();
|
||||||
if (expires < next) {
|
if (expires < next) {
|
||||||
@@ -376,7 +375,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
|
|
||||||
while (mNext == null && mIterator.hasNext()) {
|
while (mNext == null && mIterator.hasNext()) {
|
||||||
Entry<K, Entry<K, V>> entry = mIterator.next();
|
Entry<K, Entry<K, V>> entry = mIterator.next();
|
||||||
TimedEntry<K, V> timed = (TimedEntry<K, V>) entry.getValue();
|
TimedEntry timed = (TimedEntry) entry.getValue();
|
||||||
|
|
||||||
if (timed.isExpiredBy(mNow)) {
|
if (timed.isExpiredBy(mNow)) {
|
||||||
// Remove from map, and continue
|
// Remove from map, and continue
|
||||||
@@ -425,19 +424,28 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
|||||||
/**
|
/**
|
||||||
* Keeps track of timed objects
|
* Keeps track of timed objects
|
||||||
*/
|
*/
|
||||||
private class TimedEntry<K, V> extends BasicEntry<K, V> {
|
private class TimedEntry extends BasicEntry<K, V> {
|
||||||
private long mTimestamp;
|
private long mTimestamp;
|
||||||
|
|
||||||
TimedEntry(K pKey, V pValue) {
|
TimedEntry(K pKey, V pValue) {
|
||||||
super(pKey, pValue);
|
super(pKey, pValue);
|
||||||
mTimestamp = System.currentTimeMillis();
|
updateTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
public V setValue(V pValue) {
|
public V setValue(V pValue) {
|
||||||
mTimestamp = System.currentTimeMillis();
|
updateTimestamp();
|
||||||
return super.setValue(pValue);
|
return super.setValue(pValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTimestamp() {
|
||||||
|
mTimestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
long expires = expires();
|
||||||
|
if (expires < nextExpiryTime) {
|
||||||
|
nextExpiryTime = expires;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final boolean isExpired() {
|
final boolean isExpired() {
|
||||||
return isExpiredBy(System.currentTimeMillis());
|
return isExpiredBy(System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -111,6 +111,7 @@ import java.io.PrintStream;
|
|||||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||||
* @deprecated Will probably be removed in the near future
|
* @deprecated Will probably be removed in the near future
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class WildcardStringParser {
|
public class WildcardStringParser {
|
||||||
// TODO: Get rid of this class
|
// TODO: Get rid of this class
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.lang;
|
package com.twelvemonkeys.lang;
|
||||||
|
|
||||||
import org.junit.Test;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
@@ -41,7 +41,7 @@ import java.util.Date;
|
|||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* StringUtilTestCase
|
* StringUtilTestCase
|
||||||
@@ -76,24 +76,24 @@ public class StringUtilTest {
|
|||||||
assertNull(StringUtil.valueOf(null));
|
assertNull(StringUtil.valueOf(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testToUpperCase() {
|
public void testToUpperCase() {
|
||||||
String str = StringUtil.toUpperCase(TEST_STRING);
|
String str = StringUtil.toUpperCase(TEST_STRING);
|
||||||
assertNotNull(str);
|
assertNotNull(str);
|
||||||
assertEquals(TEST_STRING.toUpperCase(), str);
|
assertEquals(TEST_STRING.toUpperCase(), str);
|
||||||
|
|
||||||
str = StringUtil.toUpperCase(null);
|
assertNull(StringUtil.toUpperCase(null));
|
||||||
assertNull(str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testToLowerCase() {
|
public void testToLowerCase() {
|
||||||
String str = StringUtil.toLowerCase(TEST_STRING);
|
String str = StringUtil.toLowerCase(TEST_STRING);
|
||||||
assertNotNull(str);
|
assertNotNull(str);
|
||||||
assertEquals(TEST_STRING.toLowerCase(), str);
|
assertEquals(TEST_STRING.toLowerCase(), str);
|
||||||
|
|
||||||
str = StringUtil.toLowerCase(null);
|
assertNull(StringUtil.toLowerCase(null));
|
||||||
assertNull(str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -113,6 +113,7 @@ public class StringUtilTest {
|
|||||||
assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING}));
|
assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testContains() {
|
public void testContains() {
|
||||||
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING));
|
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING));
|
||||||
@@ -145,6 +146,7 @@ public class StringUtilTest {
|
|||||||
assertFalse(StringUtil.containsIgnoreCase(null, null));
|
assertFalse(StringUtil.containsIgnoreCase(null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void testContainsChar() {
|
public void testContainsChar() {
|
||||||
for (int i = 0; i < TEST_STRING.length(); i++) {
|
for (int i = 0; i < TEST_STRING.length(); i++) {
|
||||||
@@ -466,7 +468,7 @@ public class StringUtilTest {
|
|||||||
assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING));
|
assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING));
|
||||||
assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING));
|
assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING));
|
||||||
assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING));
|
assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING));
|
||||||
assertFalse(TEST_STRING.equals(StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING)));
|
assertNotEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING));
|
||||||
// TODO: Test is not complete
|
// TODO: Test is not complete
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +477,7 @@ public class StringUtilTest {
|
|||||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING));
|
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING));
|
||||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " "));
|
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " "));
|
||||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING));
|
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING));
|
||||||
assertFalse(TEST_STRING.equals(StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING)));
|
assertNotEquals(TEST_STRING, StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING));
|
||||||
// TODO: Test is not complete
|
// TODO: Test is not complete
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,7 +518,7 @@ public class StringUtilTest {
|
|||||||
public void testCaptialize() {
|
public void testCaptialize() {
|
||||||
assertNull(StringUtil.capitalize(null));
|
assertNull(StringUtil.capitalize(null));
|
||||||
assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase()));
|
assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase()));
|
||||||
assertTrue(StringUtil.capitalize("abc").charAt(0) == 'A');
|
assertEquals('A', StringUtil.capitalize("abc").charAt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -552,13 +554,13 @@ public class StringUtilTest {
|
|||||||
public void testToDateWithFormatString() {
|
public void testToDateWithFormatString() {
|
||||||
Calendar cal = new GregorianCalendar();
|
Calendar cal = new GregorianCalendar();
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(1976, 2, 16); // Month is 0-based
|
cal.set(1976, Calendar.MARCH, 16); // Month is 0-based
|
||||||
Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy");
|
Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy");
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
|
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(2004, 4, 13, 23, 51, 3);
|
cal.set(2004, Calendar.MAY, 13, 23, 51, 3);
|
||||||
date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)");
|
date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)");
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
@@ -576,23 +578,23 @@ public class StringUtilTest {
|
|||||||
public void testToDateWithFormat() {
|
public void testToDateWithFormat() {
|
||||||
Calendar cal = new GregorianCalendar();
|
Calendar cal = new GregorianCalendar();
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(1976, 2, 16); // Month is 0-based
|
cal.set(1976, Calendar.MARCH, 16); // Month is 0-based
|
||||||
Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy"));
|
Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy"));
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
|
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(2004, 4, 13, 23, 51);
|
cal.set(2004, Calendar.MAY, 13, 23, 51);
|
||||||
date = StringUtil.toDate("13.5.04 23:51",
|
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO"));
|
||||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO")));
|
date = StringUtil.toDate(format.format(cal.getTime()), format);
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
|
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(Calendar.HOUR, 1);
|
cal.set(Calendar.HOUR, 1);
|
||||||
cal.set(Calendar.MINUTE, 2);
|
cal.set(Calendar.MINUTE, 2);
|
||||||
date = StringUtil.toDate("1:02 am",
|
format = new SimpleDateFormat("HH:mm");
|
||||||
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
|
date = StringUtil.toDate("1:02", format);
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
}
|
}
|
||||||
@@ -601,10 +603,9 @@ public class StringUtilTest {
|
|||||||
public void testToTimestamp() {
|
public void testToTimestamp() {
|
||||||
Calendar cal = new GregorianCalendar();
|
Calendar cal = new GregorianCalendar();
|
||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(1976, 2, 16, 21, 28, 4); // Month is 0-based
|
cal.set(1976, Calendar.MARCH, 16, 21, 28, 4); // Month is 0-based
|
||||||
Date date = StringUtil.toTimestamp("1976-03-16 21:28:04");
|
Timestamp date = StringUtil.toTimestamp("1976-03-16 21:28:04");
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertTrue(date instanceof Timestamp);
|
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,7 +822,7 @@ public class StringUtilTest {
|
|||||||
assertTrue(StringUtil.isNumber("12345"));
|
assertTrue(StringUtil.isNumber("12345"));
|
||||||
assertTrue(StringUtil.isNumber(TEST_INTEGER.toString()));
|
assertTrue(StringUtil.isNumber(TEST_INTEGER.toString()));
|
||||||
assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890"));
|
assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890"));
|
||||||
assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
|
assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE));
|
||||||
assertFalse(StringUtil.isNumber("abc"));
|
assertFalse(StringUtil.isNumber("abc"));
|
||||||
assertFalse(StringUtil.isNumber(TEST_STRING));
|
assertFalse(StringUtil.isNumber(TEST_STRING));
|
||||||
}
|
}
|
||||||
@@ -831,7 +832,7 @@ public class StringUtilTest {
|
|||||||
assertTrue(StringUtil.isNumber("-12345"));
|
assertTrue(StringUtil.isNumber("-12345"));
|
||||||
assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString()));
|
assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString()));
|
||||||
assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890"));
|
assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890"));
|
||||||
assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
|
assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE));
|
||||||
assertFalse(StringUtil.isNumber("-abc"));
|
assertFalse(StringUtil.isNumber("-abc"));
|
||||||
assertFalse(StringUtil.isNumber('-' + TEST_STRING));
|
assertFalse(StringUtil.isNumber('-' + TEST_STRING));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ public class TimeoutMapTest extends MapAbstractTest {
|
|||||||
// NOTE: Only wait fist time, to avoid slooow tests
|
// NOTE: Only wait fist time, to avoid slooow tests
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
try {
|
try {
|
||||||
wait(60l);
|
wait(60L);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
}
|
}
|
||||||
@@ -591,7 +591,7 @@ public class TimeoutMapTest extends MapAbstractTest {
|
|||||||
try {
|
try {
|
||||||
wait(60l);
|
wait(60l);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException ignore) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -651,5 +651,24 @@ public class TimeoutMapTest extends MapAbstractTest {
|
|||||||
assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey));
|
assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey));
|
||||||
assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey));
|
assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testContainsKeyOnEmptyMap() {
|
||||||
|
// See #600
|
||||||
|
Map<String, String> timeoutMap = new TimeoutMap<>(30);
|
||||||
|
assertFalse(timeoutMap.containsKey("xyz"));
|
||||||
|
timeoutMap.put("xyz", "xyz");
|
||||||
|
assertTrue(timeoutMap.containsKey("xyz"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(50); // Let the item expire
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(timeoutMap.containsKey("xyz"));
|
||||||
|
assertNull(timeoutMap.get("xyz"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.7</version>
|
<version>4.13.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
+12
-3
@@ -4,13 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||||
<artifactId>contrib</artifactId>
|
<artifactId>contrib</artifactId>
|
||||||
<name>TwelveMonkeys :: Contrib</name>
|
<name>TwelveMonkeys :: Contrib</name>
|
||||||
<description>
|
<description>
|
||||||
Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
|
Contributions to TwelveMonkeys and code that doesn't fit anywhere else.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -65,8 +65,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.7</version>
|
<version>4.13.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.twelvemonkeys.contrib.exif;
|
|||||||
|
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.imageio.IIOImage;
|
import javax.imageio.IIOImage;
|
||||||
@@ -29,48 +30,68 @@ import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
|
|||||||
public class EXIFUtilities {
|
public class EXIFUtilities {
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input a {@code URL}
|
* @param input a {@code URL}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
||||||
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
|
||||||
return readWithOrientation(stream);
|
return readWithOrientation(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input an {@code InputStream}
|
* @param input an {@code InputStream}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
||||||
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
|
||||||
return readWithOrientation(stream);
|
return readWithOrientation(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input a {@code File}
|
* @param input a {@code File}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final File input) throws IOException {
|
public static IIOImage readWithOrientation(final File input) throws IOException {
|
||||||
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
|
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
|
||||||
return readWithOrientation(stream);
|
return readWithOrientation(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input an {@code ImageInputStream}
|
* @param input an {@code ImageInputStream}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
||||||
@@ -82,12 +103,11 @@ public class EXIFUtilities {
|
|||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
try {
|
try {
|
||||||
reader.setInput(input, true, false);
|
reader.setInput(input, true, false);
|
||||||
IIOImage image = reader.readAll(0, reader.getDefaultReadParam());
|
|
||||||
|
|
||||||
BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage());
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
image.setRenderedImage(applyOrientation(bufferedImage, findImageOrientation(image.getMetadata()).value()));
|
BufferedImage bufferedImage = applyOrientation(reader.read(0), findImageOrientation(metadata).value());
|
||||||
|
|
||||||
return image;
|
return new IIOImage(bufferedImage, null, metadata);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
@@ -123,9 +143,15 @@ public class EXIFUtilities {
|
|||||||
for (String arg : args) {
|
for (String arg : args) {
|
||||||
File input = new File(arg);
|
File input = new File(arg);
|
||||||
|
|
||||||
// Read everything (similar to ImageReader.readAll(0, null)), but applies the correct image orientation
|
// Read everything but thumbnails (similar to ImageReader.readAll(0, null)),
|
||||||
|
// and applies the correct image orientation
|
||||||
IIOImage image = readWithOrientation(input);
|
IIOImage image = readWithOrientation(input);
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
System.err.printf("No reader for %s%n", input);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Finds the orientation as defined by the javax_imageio_1.0 format
|
// Finds the orientation as defined by the javax_imageio_1.0 format
|
||||||
Orientation orientation = findImageOrientation(image.getMetadata());
|
Orientation orientation = findImageOrientation(image.getMetadata());
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,9 @@
|
|||||||
package com.twelvemonkeys.contrib.tiff;
|
package com.twelvemonkeys.contrib.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
||||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
|
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat;
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@@ -154,7 +155,7 @@ public class TIFFUtilitiesTest {
|
|||||||
reader.setInput(checkTest1);
|
reader.setInput(checkTest1);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Node metaData = reader.getImageMetadata(i)
|
Node metaData = reader.getImageMetadata(i)
|
||||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||||
}
|
}
|
||||||
@@ -171,7 +172,7 @@ public class TIFFUtilitiesTest {
|
|||||||
reader.setInput(checkTest2);
|
reader.setInput(checkTest2);
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
Node metaData = reader.getImageMetadata(i)
|
Node metaData = reader.getImageMetadata(i)
|
||||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||||
Assert.assertEquals(orientation, i == 1
|
Assert.assertEquals(orientation, i == 1
|
||||||
? TIFFExtension.ORIENTATION_BOTRIGHT
|
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-batik</artifactId>
|
<artifactId>imageio-batik</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||||
@@ -14,6 +14,12 @@
|
|||||||
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
|
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
|
||||||
for more information.]]>
|
for more information.]]>
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
|
||||||
|
<batik.version>1.17</batik.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
@@ -21,12 +27,24 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||||
true
|
true
|
||||||
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -39,6 +57,14 @@
|
|||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.15.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -61,13 +87,6 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.xmlgraphics</groupId>
|
|
||||||
<artifactId>xmlgraphics-commons</artifactId>
|
|
||||||
<version>2.2</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.xmlgraphics</groupId>
|
<groupId>org.apache.xmlgraphics</groupId>
|
||||||
<artifactId>batik-anim</artifactId>
|
<artifactId>batik-anim</artifactId>
|
||||||
@@ -91,7 +110,7 @@
|
|||||||
<!--
|
<!--
|
||||||
There seems to be some weirdness in the
|
There seems to be some weirdness in the
|
||||||
Batik/FOP poms (Batik depends on FOP 0.20-5) that screws things up,
|
Batik/FOP poms (Batik depends on FOP 0.20-5) that screws things up,
|
||||||
making everything end up depending on Batik 1.5, not 1.6
|
making everything end up depending on Batik 1.5, not the specified version
|
||||||
-->
|
-->
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
@@ -101,8 +120,4 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
|
||||||
<batik.version>1.12</batik.version>
|
|
||||||
</properties>
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+135
-125
@@ -33,11 +33,12 @@ package com.twelvemonkeys.imageio.plugins.svg;
|
|||||||
import com.twelvemonkeys.image.ImageUtil;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||||
import org.apache.batik.bridge.*;
|
import org.apache.batik.bridge.*;
|
||||||
import org.apache.batik.css.parser.CSSLexicalUnit;
|
|
||||||
import org.apache.batik.dom.util.DOMUtilities;
|
import org.apache.batik.dom.util.DOMUtilities;
|
||||||
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
||||||
import org.apache.batik.gvt.CanvasGraphicsNode;
|
import org.apache.batik.gvt.CanvasGraphicsNode;
|
||||||
@@ -45,11 +46,14 @@ import org.apache.batik.gvt.GraphicsNode;
|
|||||||
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
|
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
|
||||||
import org.apache.batik.gvt.renderer.ImageRenderer;
|
import org.apache.batik.gvt.renderer.ImageRenderer;
|
||||||
import org.apache.batik.gvt.renderer.ImageRendererFactory;
|
import org.apache.batik.gvt.renderer.ImageRendererFactory;
|
||||||
import org.apache.batik.transcoder.*;
|
import org.apache.batik.transcoder.SVGAbstractTranscoder;
|
||||||
|
import org.apache.batik.transcoder.TranscoderException;
|
||||||
|
import org.apache.batik.transcoder.TranscoderInput;
|
||||||
|
import org.apache.batik.transcoder.TranscoderOutput;
|
||||||
|
import org.apache.batik.transcoder.TranscodingHints;
|
||||||
import org.apache.batik.transcoder.image.ImageTranscoder;
|
import org.apache.batik.transcoder.image.ImageTranscoder;
|
||||||
import org.apache.batik.util.ParsedURL;
|
import org.apache.batik.util.ParsedURL;
|
||||||
import org.apache.batik.util.SVGConstants;
|
import org.apache.batik.util.SVGConstants;
|
||||||
import org.apache.batik.xml.LexicalUnits;
|
|
||||||
import org.w3c.dom.DOMImplementation;
|
import org.w3c.dom.DOMImplementation;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.svg.SVGSVGElement;
|
import org.w3c.dom.svg.SVGSVGElement;
|
||||||
@@ -59,10 +63,8 @@ import javax.imageio.ImageReadParam;
|
|||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.*;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.image.*;
|
||||||
import java.awt.geom.Rectangle2D;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -76,12 +78,13 @@ import java.util.Map;
|
|||||||
* @author Harald Kuhr
|
* @author Harald Kuhr
|
||||||
* @author Inpspired by code from the Batik Team
|
* @author Inpspired by code from the Batik Team
|
||||||
* @version $Id: $
|
* @version $Id: $
|
||||||
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
|
* @see <a href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</a>
|
||||||
*/
|
*/
|
||||||
public class SVGImageReader extends ImageReaderBase {
|
public class SVGImageReader extends ImageReaderBase {
|
||||||
|
|
||||||
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
|
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
|
||||||
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources"));
|
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowExternalResources",
|
||||||
|
System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")));
|
||||||
|
|
||||||
private Rasterizer rasterizer;
|
private Rasterizer rasterizer;
|
||||||
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
||||||
@@ -89,10 +92,10 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
/**
|
/**
|
||||||
* Creates an {@code SVGImageReader}.
|
* Creates an {@code SVGImageReader}.
|
||||||
*
|
*
|
||||||
* @param pProvider the provider
|
* @param provider the provider
|
||||||
*/
|
*/
|
||||||
public SVGImageReader(final ImageReaderSpi pProvider) {
|
public SVGImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
@@ -106,20 +109,20 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) {
|
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||||
super.setInput(pInput, seekForwardOnly, ignoreMetadata);
|
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||||
|
|
||||||
if (imageInput != null) {
|
if (imageInput != null) {
|
||||||
TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
TranscoderInput transcoderInput = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||||
rasterizer.setInput(input);
|
rasterizer.setInput(transcoderInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
if (pParam instanceof SVGReadParam) {
|
if (param instanceof SVGReadParam) {
|
||||||
SVGReadParam svgParam = (SVGReadParam) pParam;
|
SVGReadParam svgParam = (SVGReadParam) param;
|
||||||
|
|
||||||
// set the external-resource-resolution preference
|
// set the external-resource-resolution preference
|
||||||
allowExternalResources = svgParam.isAllowExternalResources();
|
allowExternalResources = svgParam.isAllowExternalResources();
|
||||||
@@ -132,43 +135,38 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
// Set ImageReadParams as hints
|
// Set ImageReadParams as hints
|
||||||
// Note: The cast to Map invokes a different method that preserves
|
// Note: The cast to Map invokes a different method that preserves
|
||||||
// unset defaults, DO NOT REMOVE!
|
// unset defaults, DO NOT REMOVE!
|
||||||
|
//noinspection rawtypes
|
||||||
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
||||||
}
|
}
|
||||||
|
|
||||||
Dimension size = null;
|
Dimension size = null;
|
||||||
if (pParam != null) {
|
if (param != null) {
|
||||||
size = pParam.getSourceRenderSize();
|
size = param.getSourceRenderSize();
|
||||||
}
|
}
|
||||||
if (size == null) {
|
if (size == null) {
|
||||||
size = new Dimension(getWidth(pIndex), getHeight(pIndex));
|
size = new Dimension(getWidth(imageIndex), getHeight(imageIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), size.width, size.height);
|
||||||
|
|
||||||
// Read in the image, using the Batik Transcoder
|
// Read in the image, using the Batik Transcoder
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
BufferedImage image = rasterizer.getImage();
|
||||||
|
|
||||||
|
Graphics2D g = destination.createGraphics();
|
||||||
try {
|
try {
|
||||||
processImageStarted(pIndex);
|
g.setComposite(AlphaComposite.Src);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||||
BufferedImage image = rasterizer.getImage();
|
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
||||||
|
|
||||||
Graphics2D g = destination.createGraphics();
|
|
||||||
try {
|
|
||||||
g.setComposite(AlphaComposite.Src);
|
|
||||||
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
|
||||||
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
g.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
processImageComplete();
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
}
|
||||||
catch (TranscoderException e) {
|
finally {
|
||||||
Throwable cause = unwrapException(e);
|
g.dispose();
|
||||||
throw new IIOException(cause.getMessage(), cause);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Throwable unwrapException(TranscoderException ex) {
|
private static Throwable unwrapException(TranscoderException ex) {
|
||||||
@@ -176,18 +174,18 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return ex.getException() != null ? ex.getException() : ex;
|
return ex.getException() != null ? ex.getException() : ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException {
|
private TranscodingHints paramsToHints(SVGReadParam param) throws IOException {
|
||||||
TranscodingHints hints = new TranscodingHints();
|
TranscodingHints hints = new TranscodingHints();
|
||||||
// Note: We must allow generic ImageReadParams, so converting to
|
// Note: We must allow generic ImageReadParams, so converting to
|
||||||
// TanscodingHints should be done outside the SVGReadParam class.
|
// TanscodingHints should be done outside the SVGReadParam class.
|
||||||
|
|
||||||
// Set dimensions
|
// Set dimensions
|
||||||
Dimension size = pParam.getSourceRenderSize();
|
Dimension size = param.getSourceRenderSize();
|
||||||
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
|
Rectangle viewBox = rasterizer.getViewBox();
|
||||||
if (size == null) {
|
if (size == null) {
|
||||||
// SVG is not a pixel based format, but we'll scale it, according to
|
// SVG is not a pixel based format, but we'll scale it, according to
|
||||||
// the subsampling for compatibility
|
// the subsampling for compatibility
|
||||||
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
|
size = getSourceRenderSizeFromSubsamping(param, viewBox.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size != null) {
|
if (size != null) {
|
||||||
@@ -196,7 +194,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set area of interest
|
// Set area of interest
|
||||||
Rectangle region = pParam.getSourceRegion();
|
Rectangle region = param.getSourceRegion();
|
||||||
if (region != null) {
|
if (region != null) {
|
||||||
hints.put(ImageTranscoder.KEY_AOI, region);
|
hints.put(ImageTranscoder.KEY_AOI, region);
|
||||||
|
|
||||||
@@ -207,8 +205,8 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Need to resize here...
|
// Need to resize here...
|
||||||
double xScale = size.getWidth() / origSize.getWidth();
|
double xScale = size.getWidth() / viewBox.getWidth();
|
||||||
double yScale = size.getHeight() / origSize.getHeight();
|
double yScale = size.getHeight() / viewBox.getHeight();
|
||||||
|
|
||||||
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
||||||
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
||||||
@@ -216,11 +214,11 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else if (size != null) {
|
else if (size != null) {
|
||||||
// Allow non-uniform scaling
|
// Allow non-uniform scaling
|
||||||
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
|
hints.put(ImageTranscoder.KEY_AOI, viewBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background color
|
// Background color
|
||||||
Paint bg = pParam.getBackgroundColor();
|
Paint bg = param.getBackgroundColor();
|
||||||
if (bg != null) {
|
if (bg != null) {
|
||||||
hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg);
|
hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg);
|
||||||
}
|
}
|
||||||
@@ -228,10 +226,10 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return hints;
|
return hints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
|
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam param, Dimension origSize) {
|
||||||
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
|
if (param.getSourceXSubsampling() > 1 || param.getSourceYSubsampling() > 1) {
|
||||||
return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
|
return new Dimension((int) (origSize.width / (float) param.getSourceXSubsampling()),
|
||||||
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
|
(int) (origSize.height / (float) param.getSourceYSubsampling()));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -240,28 +238,19 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return new SVGReadParam();
|
return new SVGReadParam();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(int pIndex) throws IOException {
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
try {
|
|
||||||
return rasterizer.getDefaultWidth();
|
return rasterizer.getDefaultWidth();
|
||||||
}
|
|
||||||
catch (TranscoderException e) {
|
|
||||||
throw new IIOException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(int pIndex) throws IOException {
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
try {
|
return rasterizer.getDefaultHeight();
|
||||||
return rasterizer.getDefaultHeight();
|
|
||||||
}
|
|
||||||
catch (TranscoderException e) {
|
|
||||||
throw new IIOException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
||||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
return Collections.singleton(ImageTypeSpecifiers.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,12 +260,11 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
* and needs major refactoring!
|
* and needs major refactoring!
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
|
private class Rasterizer extends SVGAbstractTranscoder {
|
||||||
|
|
||||||
private BufferedImage image;
|
private BufferedImage image;
|
||||||
private TranscoderInput transcoderInput;
|
private TranscoderInput transcoderInput;
|
||||||
private float defaultWidth;
|
private final Rectangle2D viewBox = new Rectangle2D.Float();
|
||||||
private float defaultHeight;
|
private final Dimension defaultSize = new Dimension();
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
private SVGOMDocument document;
|
private SVGOMDocument document;
|
||||||
private String uri;
|
private String uri;
|
||||||
@@ -289,7 +277,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is cheating... We don't fully transcode after all
|
// This is cheating... We don't fully transcode after all
|
||||||
protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException {
|
protected void transcode(Document document, final String uri, final TranscoderOutput output) {
|
||||||
// Sets up root, curTxf & curAoi
|
// Sets up root, curTxf & curAoi
|
||||||
// ----
|
// ----
|
||||||
if (document != null) {
|
if (document != null) {
|
||||||
@@ -337,54 +325,66 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
// ----
|
// ----
|
||||||
SVGSVGElement rootElement = svgDoc.getRootElement();
|
SVGSVGElement rootElement = svgDoc.getRootElement();
|
||||||
|
|
||||||
// get the 'width' and 'height' attributes of the SVG document
|
// Get the viewBox
|
||||||
UnitProcessor.Context uctx
|
String viewBoxStr = rootElement.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
||||||
= UnitProcessor.createContext(ctx, rootElement);
|
if (viewBoxStr.length() != 0) {
|
||||||
|
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
||||||
|
viewBox.setFrame(rect[0], rect[1], rect[2], rect[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the 'width' and 'height' attributes of the SVG document
|
||||||
|
double width = 0;
|
||||||
|
double height = 0;
|
||||||
|
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, rootElement);
|
||||||
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
||||||
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
||||||
if (!StringUtil.isEmpty(widthStr)) {
|
if (!StringUtil.isEmpty(widthStr)) {
|
||||||
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
width = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||||
}
|
}
|
||||||
if(!StringUtil.isEmpty(heightStr)){
|
if (!StringUtil.isEmpty(heightStr)) {
|
||||||
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasWidth = defaultWidth > 0.0;
|
boolean hasWidth = width > 0.0;
|
||||||
boolean hasHeight = defaultHeight > 0.0;
|
boolean hasHeight = height > 0.0;
|
||||||
|
|
||||||
if (!hasWidth || !hasHeight) {
|
if (!hasWidth || !hasHeight) {
|
||||||
String viewBoxStr = rootElement.getAttributeNS
|
if (!viewBox.isEmpty()) {
|
||||||
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
// If one dimension is given, calculate other by aspect ratio in viewBox
|
||||||
if (viewBoxStr.length() != 0) {
|
|
||||||
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
|
||||||
// if one dimension is given, calculate other by aspect ratio in viewBox
|
|
||||||
// or use viewBox if no dimension is given
|
|
||||||
if (hasWidth) {
|
if (hasWidth) {
|
||||||
defaultHeight = defaultWidth * rect[3] / rect[2];
|
height = width * viewBox.getHeight() / viewBox.getWidth();
|
||||||
}
|
}
|
||||||
else if (hasHeight) {
|
else if (hasHeight) {
|
||||||
defaultWidth = defaultHeight * rect[2] / rect[3];
|
width = height * viewBox.getWidth() / viewBox.getHeight();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
defaultWidth = rect[2];
|
// ...or use viewBox if no dimension is given
|
||||||
defaultHeight = rect[3];
|
width = viewBox.getWidth();
|
||||||
|
height = viewBox.getHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// No viewBox, just assume square size
|
||||||
if (hasHeight) {
|
if (hasHeight) {
|
||||||
defaultWidth = defaultHeight;
|
width = height;
|
||||||
}
|
}
|
||||||
else if (hasWidth) {
|
else if (hasWidth) {
|
||||||
defaultHeight = defaultWidth;
|
height = width;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// fallback to batik default sizes
|
// ...or finally fall back to Batik default sizes
|
||||||
defaultWidth = 400;
|
width = 400;
|
||||||
defaultHeight = 400;
|
height = 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We now have a size, in the rare case we don't have a viewBox; set it to this size
|
||||||
|
defaultSize.setSize(width, height);
|
||||||
|
if (viewBox.isEmpty()) {
|
||||||
|
viewBox.setRect(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
// Hack to work around exception above
|
// Hack to work around exception above
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
gvtRoot = root;
|
gvtRoot = root;
|
||||||
@@ -397,7 +397,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
ctx = null;
|
ctx = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readImage() throws TranscoderException {
|
private BufferedImage readImage() throws IOException {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
@@ -422,7 +422,8 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gvtRoot == null) {
|
if (gvtRoot == null) {
|
||||||
throw exception;
|
Throwable cause = unwrapException(exception);
|
||||||
|
throw new IIOException(cause.getMessage(), cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx = context;
|
ctx = context;
|
||||||
@@ -440,7 +441,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
setImageSize(defaultWidth, defaultHeight);
|
setImageSize(defaultSize.width, defaultSize.height);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -454,18 +455,17 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (BridgeException ex) {
|
catch (BridgeException ex) {
|
||||||
throw new TranscoderException(ex);
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
|
if (Px.isIdentity() && (width != defaultSize.width || height != defaultSize.height)) {
|
||||||
// The document has no viewBox, we need to resize it by hand.
|
// The document has no viewBox, we need to resize it by hand.
|
||||||
// we want to keep the document size ratio
|
// we want to keep the document size ratio
|
||||||
float xscale, yscale;
|
float xscale, yscale;
|
||||||
xscale = width / defaultWidth;
|
xscale = width / defaultSize.width;
|
||||||
yscale = height / defaultHeight;
|
yscale = height / defaultSize.height;
|
||||||
float scale = Math.min(xscale, yscale);
|
float scale = Math.min(xscale, yscale);
|
||||||
Px = AffineTransform.getScaleInstance(scale, scale);
|
Px = AffineTransform.getScaleInstance(scale, scale);
|
||||||
}
|
}
|
||||||
@@ -515,7 +515,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (BridgeException ex) {
|
catch (BridgeException ex) {
|
||||||
throw new TranscoderException(ex);
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.root = gvtRoot;
|
this.root = gvtRoot;
|
||||||
@@ -584,9 +584,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
TranscoderException exception = new TranscoderException(ex.getMessage());
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
exception.initCause(ex);
|
|
||||||
throw exception;
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
@@ -595,7 +593,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void init() throws TranscoderException {
|
private synchronized void init() throws IIOException {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
if (transcoderInput == null) {
|
if (transcoderInput == null) {
|
||||||
throw new IllegalStateException("input == null");
|
throw new IllegalStateException("input == null");
|
||||||
@@ -603,11 +601,18 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
super.transcode(transcoderInput, null);
|
try {
|
||||||
|
super.addTranscodingHint(SVGAbstractTranscoder.KEY_ALLOW_EXTERNAL_RESOURCES, allowExternalResources);
|
||||||
|
super.transcode(transcoderInput, null);
|
||||||
|
}
|
||||||
|
catch (TranscoderException e) {
|
||||||
|
Throwable cause = unwrapException(e);
|
||||||
|
throw new IIOException(cause.getMessage(), cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage getImage() throws TranscoderException {
|
private BufferedImage getImage() throws IOException {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
image = readImage();
|
image = readImage();
|
||||||
}
|
}
|
||||||
@@ -615,18 +620,23 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDefaultWidth() throws TranscoderException {
|
int getDefaultWidth() throws IOException {
|
||||||
init();
|
init();
|
||||||
return (int) Math.ceil(defaultWidth);
|
return defaultSize.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDefaultHeight() throws TranscoderException {
|
int getDefaultHeight() throws IOException {
|
||||||
init();
|
init();
|
||||||
return (int) Math.ceil(defaultHeight);
|
return defaultSize.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInput(final TranscoderInput pInput) {
|
Rectangle getViewBox() throws IOException {
|
||||||
transcoderInput = pInput;
|
init();
|
||||||
|
return viewBox.getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInput(final TranscoderInput input) {
|
||||||
|
transcoderInput = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -655,7 +665,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
if (allowExternalResources) {
|
if (allowExternalResources) {
|
||||||
return super.getExternalResourceSecurity(resourceURL, docURL);
|
return super.getExternalResourceSecurity(resourceURL, docURL);
|
||||||
}
|
}
|
||||||
return new NoLoadExternalResourceSecurity();
|
return new EmbededExternalResourceSecurity(resourceURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-14
@@ -60,22 +60,22 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new SVGProviderInfo());
|
super(new SVGProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("StatementWithEmptyBody")
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
private static boolean canDecode(final ImageInputStream input) throws IOException {
|
||||||
// NOTE: This test is quite quick as it does not involve any parsing,
|
// NOTE: This test is quite quick as it does not involve any parsing,
|
||||||
// however it may not recognize all kinds of SVG documents.
|
// however it may not recognize all kinds of SVG documents.
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
|
|
||||||
// TODO: This is not ok for UTF-16 and other wide encodings
|
// TODO: This is not ok for UTF-16 and other wide encodings
|
||||||
// TODO: Use an XML (encoding) aware Reader instance instead
|
// TODO: Use an XML (encoding) aware Reader instance instead
|
||||||
// Need to figure out pretty fast if this is XML or not
|
// Need to figure out pretty fast if this is XML or not
|
||||||
int b;
|
int b;
|
||||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
while (Character.isWhitespace((char) (b = input.read()))) {
|
||||||
// Skip over leading WS
|
// Skip over leading WS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,30 +95,30 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
byte[] buffer = new byte[4];
|
byte[] buffer = new byte[4];
|
||||||
while (true) {
|
while (true) {
|
||||||
pInput.readFully(buffer);
|
input.readFully(buffer);
|
||||||
|
|
||||||
if (buffer[0] == '?') {
|
if (buffer[0] == '?') {
|
||||||
// This is the XML declaration or a processing instruction
|
// This is the XML declaration or a processing instruction
|
||||||
while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) {
|
while (!((input.readByte() & 0xFF) == '?' && input.read() == '>')) {
|
||||||
// Skip until end of XML declaration or processing instruction or EOF
|
// Skip until end of XML declaration or processing instruction or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (buffer[0] == '!') {
|
else if (buffer[0] == '!') {
|
||||||
if (buffer[1] == '-' && buffer[2] == '-') {
|
if (buffer[1] == '-' && buffer[2] == '-') {
|
||||||
// This is a comment
|
// This is a comment
|
||||||
while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
while (!((input.readByte() & 0xFF) == '-' && input.read() == '-' && input.read() == '>')) {
|
||||||
// Skip until end of comment or EOF
|
// Skip until end of comment or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
||||||
&& pInput.read() == 'T' && pInput.read() == 'Y'
|
&& input.read() == 'T' && input.read() == 'Y'
|
||||||
&& pInput.read() == 'P' && pInput.read() == 'E') {
|
&& input.read() == 'P' && input.read() == 'E') {
|
||||||
// This is the DOCTYPE declaration
|
// This is the DOCTYPE declaration
|
||||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
while (Character.isWhitespace((char) (b = input.read()))) {
|
||||||
// Skip over WS
|
// Skip over WS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
|
if (b == 's' && input.read() == 'v' && input.read() == 'g') {
|
||||||
// It's SVG, identified by DOCTYPE
|
// It's SVG, identified by DOCTYPE
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((pInput.readByte() & 0xFF) != '<') {
|
while ((input.readByte() & 0xFF) != '<') {
|
||||||
// Skip over, until next begin tag or EOF
|
// Skip over, until next begin tag or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
//noinspection ThrowFromFinallyBlock
|
//noinspection ThrowFromFinallyBlock
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -51,16 +51,16 @@ public class SVGReadParam extends ImageReadParam {
|
|||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBackgroundColor(Paint pColor) {
|
public void setBackgroundColor(Paint color) {
|
||||||
background = pColor;
|
background = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseURI() {
|
public String getBaseURI() {
|
||||||
return baseURI;
|
return baseURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBaseURI(String pBaseURI) {
|
public void setBaseURI(String baseURI) {
|
||||||
baseURI = pBaseURI;
|
this.baseURI = baseURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllowExternalResources(boolean allow) {
|
public void setAllowExternalResources(boolean allow) {
|
||||||
|
|||||||
+12
-10
@@ -33,19 +33,24 @@ package com.twelvemonkeys.imageio.plugins.wmf;
|
|||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
||||||
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
||||||
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
|
|
||||||
import org.apache.batik.transcoder.TranscoderException;
|
import org.apache.batik.transcoder.TranscoderException;
|
||||||
import org.apache.batik.transcoder.TranscoderInput;
|
import org.apache.batik.transcoder.TranscoderInput;
|
||||||
import org.apache.batik.transcoder.TranscoderOutput;
|
import org.apache.batik.transcoder.TranscoderOutput;
|
||||||
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageReadParam;
|
import javax.imageio.ImageReadParam;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.io.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +61,6 @@ import java.util.Iterator;
|
|||||||
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
||||||
*/
|
*/
|
||||||
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
||||||
// TODO: Consider using temp file instead of in-memory stream
|
|
||||||
public final class WMFImageReader extends ImageReaderBase {
|
public final class WMFImageReader extends ImageReaderBase {
|
||||||
|
|
||||||
private SVGImageReader reader = null;
|
private SVGImageReader reader = null;
|
||||||
@@ -90,7 +94,7 @@ public final class WMFImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void init() throws IOException {
|
private void init() throws IOException {
|
||||||
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
||||||
if (imageInput == null) {
|
if (imageInput == null) {
|
||||||
throw new IllegalStateException("input == null");
|
throw new IllegalStateException("input == null");
|
||||||
@@ -98,10 +102,9 @@ public final class WMFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
WMFTranscoder transcoder = new WMFTranscoder();
|
WMFTranscoder transcoder = new WMFTranscoder();
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
|
||||||
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
|
||||||
Writer writer = new OutputStreamWriter(output, "UTF8");
|
|
||||||
try {
|
|
||||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||||
TranscoderOutput out = new TranscoderOutput(writer);
|
TranscoderOutput out = new TranscoderOutput(writer);
|
||||||
|
|
||||||
@@ -114,7 +117,7 @@ public final class WMFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader = new SVGImageReader(getOriginatingProvider());
|
reader = new SVGImageReader(getOriginatingProvider());
|
||||||
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
|
reader.setInput(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,5 +140,4 @@ public final class WMFImageReader extends ImageReaderBase {
|
|||||||
init();
|
init();
|
||||||
return reader.getImageTypes(pImageIndex);
|
return reader.getImageTypes(pImageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-29
@@ -31,6 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.svg;
|
package com.twelvemonkeys.imageio.plugins.svg;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -42,9 +43,7 @@ import javax.imageio.event.IIOReadWarningListener;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ImagingOpException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@@ -53,9 +52,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,7 +67,10 @@ import static org.mockito.Mockito.*;
|
|||||||
*/
|
*/
|
||||||
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
|
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
|
||||||
|
|
||||||
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new SVGImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
protected List<TestData> getTestData() {
|
protected List<TestData> getTestData() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
@@ -82,19 +85,6 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImageReaderSpi createProvider() {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected SVGImageReader createReader() {
|
|
||||||
return new SVGImageReader(createProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<SVGImageReader> getReaderClass() {
|
|
||||||
return SVGImageReader.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("svg");
|
return Collections.singletonList("svg");
|
||||||
}
|
}
|
||||||
@@ -111,8 +101,6 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
public void testScaleViewBox() throws IOException {
|
public void testScaleViewBox() throws IOException {
|
||||||
URL svgUrl = getClassLoaderResource("/svg/quadrants.svg");
|
URL svgUrl = getClassLoaderResource("/svg/quadrants.svg");
|
||||||
|
|
||||||
File tmpDir = new File(System.getProperty("java.io.tmpdir"));
|
|
||||||
|
|
||||||
SVGImageReader reader = createReader();
|
SVGImageReader reader = createReader();
|
||||||
SVGReadParam param = new SVGReadParam();
|
SVGReadParam param = new SVGReadParam();
|
||||||
|
|
||||||
@@ -160,11 +148,11 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void testReadWithSizeParam() {
|
public void testReadWithSizeParam() throws IOException {
|
||||||
try {
|
try {
|
||||||
super.testReadWithSizeParam();
|
super.testReadWithSizeParam();
|
||||||
}
|
}
|
||||||
catch (AssertionError failure) {
|
catch (AssertionError | IOException failure) {
|
||||||
Throwable cause = failure;
|
Throwable cause = failure;
|
||||||
|
|
||||||
while (cause.getCause() != null) {
|
while (cause.getCause() != null) {
|
||||||
@@ -206,12 +194,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
||||||
reader.setInput(redSquare.getInputStream());
|
reader.setInput(redSquare.getInputStream());
|
||||||
BufferedImage imageRed = reader.read(0, param);
|
BufferedImage imageRed = reader.read(0, param);
|
||||||
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
|
assertRGBEquals("Expected all red", 0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||||
|
|
||||||
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
||||||
reader.setInput(blueSquare.getInputStream());
|
reader.setInput(blueSquare.getInputStream());
|
||||||
BufferedImage imageBlue = reader.read(0, param);
|
BufferedImage imageBlue = reader.read(0, param);
|
||||||
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
|
assertRGBEquals("Expected all blue", 0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -237,7 +225,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
assertEquals(500, image.getHeight());
|
assertEquals(500, image.getHeight());
|
||||||
|
|
||||||
// CSS and embedded resources all go!
|
// CSS and embedded resources all go!
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
@@ -278,7 +266,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
assertEquals(500, image.getHeight());
|
assertEquals(500, image.getHeight());
|
||||||
|
|
||||||
// No more warnings now that the base URI is set
|
// No more warnings now that the base URI is set
|
||||||
verifyZeroInteractions(listener);
|
verifyNoInteractions(listener);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
reader.dispose();
|
reader.dispose();
|
||||||
@@ -299,7 +287,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
params.setAllowExternalResources(true);
|
params.setAllowExternalResources(true);
|
||||||
reader.read(0, params);
|
reader.read(0, params);
|
||||||
|
|
||||||
assertTrue("reader.read should've thrown an exception, but didn't", false);
|
fail("reader.read should've thrown an exception, but didn't");
|
||||||
}
|
}
|
||||||
catch (IIOException allowed) {
|
catch (IIOException allowed) {
|
||||||
assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find
|
assertTrue(allowed.getMessage().contains("batikLogo.svg")); // The embedded resource we don't find
|
||||||
@@ -309,6 +297,25 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadEmbeddedWithDisallowExternalResources() throws IOException{
|
||||||
|
// File using "data:" URLs for embedded resources
|
||||||
|
URL resource = getClassLoaderResource("/svg/embedded-data-resource.svg");
|
||||||
|
SVGImageReader reader = createReader();
|
||||||
|
|
||||||
|
TestData data = new TestData(resource, (Dimension) null);
|
||||||
|
try (ImageInputStream stream = data.getInputStream()) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
SVGReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setAllowExternalResources(false);
|
||||||
|
reader.read(0, param);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = SecurityException.class)
|
@Test(expected = SecurityException.class)
|
||||||
public void testDisallowedExternalResources() throws URISyntaxException, IOException {
|
public void testDisallowedExternalResources() throws URISyntaxException, IOException {
|
||||||
// system-property set to true in surefire-plugin-settings in the pom
|
// system-property set to true in surefire-plugin-settings in the pom
|
||||||
@@ -332,4 +339,69 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWitSourceRenderSize() throws IOException {
|
||||||
|
URL resource = getClassLoaderResource("/svg/circle.svg");
|
||||||
|
|
||||||
|
SVGImageReader reader = createReader();
|
||||||
|
|
||||||
|
TestData data = new TestData(resource, (Dimension) null);
|
||||||
|
try (ImageInputStream stream = data.getInputStream()) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
SVGReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRenderSize(new Dimension(100, 100));
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(100, image.getWidth());
|
||||||
|
assertEquals(100, image.getHeight());
|
||||||
|
|
||||||
|
// Some quick samples
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 99), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 99), 0);
|
||||||
|
assertRGBEquals("Expected red center", 0xffff0000, image.getRGB(50, 50), 0);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWitSourceRenderSizeViewBoxNegativeXY() throws IOException {
|
||||||
|
URL resource = getClassLoaderResource("/svg/Android_robot.svg");
|
||||||
|
|
||||||
|
SVGImageReader reader = createReader();
|
||||||
|
|
||||||
|
TestData data = new TestData(resource, (Dimension) null);
|
||||||
|
try (ImageInputStream stream = data.getInputStream()) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
SVGReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRenderSize(new Dimension(219, 256)); // Aspect scaled to 256 boxed
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(219, image.getWidth());
|
||||||
|
assertEquals(256, image.getHeight());
|
||||||
|
|
||||||
|
// Some quick samples
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 255), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 255), 0);
|
||||||
|
assertRGBEquals("Expected green head", 0xffa4c639, image.getRGB(109, 20), 25);
|
||||||
|
assertRGBEquals("Expected green center", 0xffa4c639, image.getRGB(109, 128), 25);
|
||||||
|
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(80, 246), 25);
|
||||||
|
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(130, 246), 25);
|
||||||
|
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(0, 128), 0);
|
||||||
|
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(218, 128), 0);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-13
@@ -31,6 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -49,7 +50,10 @@ import java.util.List;
|
|||||||
* @version $Id: WMFImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
* @version $Id: WMFImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader> {
|
public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader> {
|
||||||
private WMFImageReaderSpi provider = new WMFImageReaderSpi();
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new WMFImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
protected List<TestData> getTestData() {
|
protected List<TestData> getTestData() {
|
||||||
return Collections.singletonList(
|
return Collections.singletonList(
|
||||||
@@ -57,27 +61,17 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImageReaderSpi createProvider() {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected WMFImageReader createReader() {
|
|
||||||
return new WMFImageReader(createProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<WMFImageReader> getReaderClass() {
|
|
||||||
return WMFImageReader.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("wmf");
|
return Collections.singletonList("wmf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getSuffixes() {
|
protected List<String> getSuffixes() {
|
||||||
return Arrays.asList("wmf", "emf");
|
return Arrays.asList("wmf", "emf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Arrays.asList("image/x-wmf", "application/x-msmetafile");
|
return Arrays.asList("image/x-wmf", "application/x-msmetafile");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
||||||
|
<circle cx="25" cy="25" r="25" fill="red"/></svg>
|
||||||
|
After Width: | Height: | Size: 436 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 92 KiB |
@@ -4,12 +4,16 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-bmp</artifactId>
|
<artifactId>imageio-bmp</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||||
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
|
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.imageio.bmp</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
@@ -19,6 +23,26 @@
|
|||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi,
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+104
-91
@@ -39,7 +39,11 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
|||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.event.IIOReadUpdateListener;
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
@@ -47,13 +51,13 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,8 +81,8 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
super(new BMPImageReaderSpi());
|
super(new BMPImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BMPImageReader(final ImageReaderSpi pProvider) {
|
BMPImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,6 +129,11 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// Read DIB header
|
// Read DIB header
|
||||||
header = DIBHeader.read(imageInput);
|
header = DIBHeader.read(imageInput);
|
||||||
|
// System.out.println("header = " + header);
|
||||||
|
|
||||||
|
if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) {
|
||||||
|
throw new IIOException("Invalid pixel offset: " + pixelOffset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,79 +187,84 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth(int pImageIndex) throws IOException {
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
return header.getWidth();
|
return header.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight(int pImageIndex) throws IOException {
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
return header.getHeight();
|
return header.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int pImageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
|
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
|
||||||
return Arrays.asList(getRawImageType(pImageIndex)).iterator();
|
return Collections.singletonList(getRawImageType(imageIndex)).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
if (header.getPlanes() != 1) {
|
if (header.getPlanes() != 1) {
|
||||||
throw new IIOException("Multiple planes not supported");
|
throw new IIOException("Multiple planes not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (header.getBitCount()) {
|
try {
|
||||||
case 1:
|
switch (header.getBitCount()) {
|
||||||
case 2:
|
case 1:
|
||||||
case 4:
|
case 2:
|
||||||
case 8:
|
case 4:
|
||||||
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap());
|
case 8:
|
||||||
|
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap());
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
if (header.hasMasks()) {
|
if (header.hasMasks()) {
|
||||||
return ImageTypeSpecifiers.createPacked(
|
return ImageTypeSpecifiers.createPacked(
|
||||||
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||||
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
|
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
|
||||||
DataBuffer.TYPE_USHORT, false
|
DataBuffer.TYPE_USHORT, false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default if no mask is 555
|
// Default if no mask is 555
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||||
|
|
||||||
case 24:
|
case 24:
|
||||||
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
||||||
throw new IIOException("Unsupported compression for RGB: " + header.getCompression());
|
throw new IIOException("Unsupported compression for RGB: " + header.getCompression());
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
|
|
||||||
case 32:
|
case 32:
|
||||||
if (header.hasMasks()) {
|
if (header.hasMasks()) {
|
||||||
return ImageTypeSpecifiers.createPacked(
|
return ImageTypeSpecifiers.createPacked(
|
||||||
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||||
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
|
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
|
||||||
DataBuffer.TYPE_INT, false
|
DataBuffer.TYPE_INT, false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default if no mask
|
// Default if no mask
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
|
||||||
|
|
||||||
case 0:
|
case 0:
|
||||||
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
|
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
|
||||||
return initReaderDelegate(header.getCompression()).getRawImageType(0);
|
return initReaderDelegate(header.getCompression()).getRawImageType(0);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unsupported bit count: " + header.getBitCount());
|
throw new IIOException("Unsupported bit count: " + header.getBitCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw new IIOException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,14 +363,18 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageStarted(imageIndex);
|
processImageStarted(imageIndex);
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getBitCount()) {
|
int bitCount = header.getBitCount();
|
||||||
|
switch (bitCount) {
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 4:
|
case 4:
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
int bitsPerSample = bitCount == 24 ? 8 : bitCount;
|
||||||
|
int samplesPerPixel = bitCount == 24 ? 3 : 1;
|
||||||
|
|
||||||
|
readRowByte(input, height, srcRegion, xSub, ySub, bitsPerSample, samplesPerPixel, rowDataByte, destRaster, clippedRow, y);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
@@ -370,7 +388,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
|
throw new AssertionError("Unsupported pixel depth: " + bitCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100f * y / height);
|
processImageProgress(100f * y / height);
|
||||||
@@ -396,6 +414,13 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private ImageReader initReaderDelegate(int compression) throws IOException {
|
private ImageReader initReaderDelegate(int compression) throws IOException {
|
||||||
ImageReader reader = getImageReaderDelegate(compression);
|
ImageReader reader = getImageReaderDelegate(compression);
|
||||||
|
reader.reset();
|
||||||
|
|
||||||
|
// Install listener
|
||||||
|
ListenerDelegator listenerDelegator = new ListenerDelegator();
|
||||||
|
reader.addIIOReadWarningListener(listenerDelegator);
|
||||||
|
reader.addIIOReadProgressListener(listenerDelegator);
|
||||||
|
reader.addIIOReadUpdateListener(listenerDelegator);
|
||||||
|
|
||||||
imageInput.seek(pixelOffset);
|
imageInput.seek(pixelOffset);
|
||||||
reader.setInput(new SubImageInputStream(imageInput, header.getImageSize()));
|
reader.setInput(new SubImageInputStream(imageInput, header.getImageSize()));
|
||||||
@@ -436,12 +461,6 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
|
|
||||||
// Install listener
|
|
||||||
ListenerDelegator listenerDelegator = new ListenerDelegator();
|
|
||||||
reader.addIIOReadWarningListener(listenerDelegator);
|
|
||||||
reader.addIIOReadProgressListener(listenerDelegator);
|
|
||||||
reader.addIIOReadUpdateListener(listenerDelegator);
|
|
||||||
|
|
||||||
// Cache for later use
|
// Cache for later use
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case DIB.COMPRESSION_JPEG:
|
case DIB.COMPRESSION_JPEG:
|
||||||
@@ -466,9 +485,14 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
|
int bitsPerSample, int samplesPerPixel,
|
||||||
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataByte.length);
|
input.skipBytes(rowDataByte.length);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -478,24 +502,20 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// Subsample horizontal
|
// Subsample horizontal
|
||||||
if (xSub != 1) {
|
if (xSub != 1) {
|
||||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
|
||||||
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
|
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -515,19 +535,17 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
|
// Flip into position?
|
||||||
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
|
int dstY = (srcY - srcRegion.y) / ySub;
|
||||||
|
|
||||||
// If subsampled or outside source region, skip entire row
|
// If subsampled or outside source region, skip entire row
|
||||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||||
input.skipBytes(rowDataInt.length * 4);
|
input.skipBytes(rowDataInt.length * 4);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -542,13 +560,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.topDown) {
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
destChannel.setDataElements(0, y, srcChannel);
|
|
||||||
} else {
|
|
||||||
// Flip into position
|
|
||||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Candidate util method
|
// TODO: Candidate util method
|
||||||
@@ -619,7 +631,8 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
return new BMPMetadata(header, colors);
|
return new BMPMetadata(header, colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public static void main(String[] args) {
|
||||||
BMPImageReaderSpi provider = new BMPImageReaderSpi();
|
BMPImageReaderSpi provider = new BMPImageReaderSpi();
|
||||||
BMPImageReader reader = new BMPImageReader(provider);
|
BMPImageReader reader = new BMPImageReader(provider);
|
||||||
|
|
||||||
@@ -672,9 +685,9 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
|
@SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" })
|
||||||
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
|
static <T extends Throwable> void throwAs(final Class<T> type, final Throwable throwable) throws T {
|
||||||
throw (T) pThrowable;
|
throw (T) throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
||||||
|
|||||||
+8
-8
@@ -65,16 +65,16 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
private static boolean canDecode(final ImageInputStream input) throws IOException {
|
||||||
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
|
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
pInput.readFully(fileHeader);
|
input.readFully(fileHeader);
|
||||||
|
|
||||||
// Magic: BM
|
// Magic: BM
|
||||||
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
|
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
|
||||||
@@ -112,15 +112,15 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public ImageReader createReaderInstance(final Object extension) {
|
||||||
return new BMPImageReader(this);
|
return new BMPImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Device Independent Bitmap Format (BMP) Reader";
|
return "Windows Device Independent Bitmap Format (BMP) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ import java.nio.ByteOrder;
|
|||||||
* BMPImageWriter
|
* BMPImageWriter
|
||||||
*/
|
*/
|
||||||
public final class BMPImageWriter extends DIBImageWriter {
|
public final class BMPImageWriter extends DIBImageWriter {
|
||||||
protected BMPImageWriter(ImageWriterSpi provider) {
|
BMPImageWriter(ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
@@ -141,7 +142,7 @@ final class BMPMetadata extends AbstractMetadata {
|
|||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardChromaNode() {
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
// NOTE: BMP files may contain a color map, even if true color...
|
// NOTE: BMP files may contain a color map, even if true color...
|
||||||
// Not sure if this is a good idea to expose to the meta data,
|
// Not sure if this is a good idea to expose to the metadata,
|
||||||
// as it might be unexpected... Then again...
|
// as it might be unexpected... Then again...
|
||||||
if (colorMap != null) {
|
if (colorMap != null) {
|
||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|||||||
+8
-10
@@ -29,9 +29,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a bitmap structure.
|
* Describes a bitmap structure.
|
||||||
@@ -46,15 +47,12 @@ abstract class BitmapDescriptor {
|
|||||||
protected BufferedImage image;
|
protected BufferedImage image;
|
||||||
protected BitmapMask mask;
|
protected BitmapMask mask;
|
||||||
|
|
||||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapDescriptor(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
Validate.notNull(pEntry, "entry");
|
this.entry = notNull(entry, "entry");
|
||||||
Validate.notNull(pHeader, "header");
|
this.header = notNull(header, "header");
|
||||||
|
|
||||||
entry = pEntry;
|
|
||||||
header = pHeader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public BufferedImage getImage();
|
abstract public BufferedImage getImage() throws IOException;
|
||||||
|
|
||||||
public final int getWidth() {
|
public final int getWidth() {
|
||||||
return entry.getWidth();
|
return entry.getWidth();
|
||||||
@@ -77,7 +75,7 @@ abstract class BitmapDescriptor {
|
|||||||
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setMask(final BitmapMask mask) {
|
final void setMask(final BitmapMask mask) {
|
||||||
this.mask = mask;
|
this.mask = mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-22
@@ -29,10 +29,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.DataBuffer;
|
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,12 +38,13 @@ import java.util.Hashtable;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapIndexed extends BitmapDescriptor {
|
final class BitmapIndexed extends BitmapDescriptor {
|
||||||
protected final int[] bits;
|
final int[] bits;
|
||||||
protected final int[] colors;
|
final int[] colors;
|
||||||
|
|
||||||
|
public BitmapIndexed(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
|
super(entry, header);
|
||||||
|
|
||||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
|
||||||
super(pEntry, pHeader);
|
|
||||||
bits = new int[getWidth() * getHeight()];
|
bits = new int[getWidth() * getHeight()];
|
||||||
|
|
||||||
// NOTE: We're adding space for one extra color, for transparency
|
// NOTE: We're adding space for one extra color, for transparency
|
||||||
@@ -59,20 +57,16 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
|
|
||||||
IndexColorModel icm = createColorModel();
|
IndexColorModel icm = createColorModel();
|
||||||
|
|
||||||
// This is slightly obscure, and should probably be moved..
|
// We add cursor hotspot as a property to images created from CUR format.
|
||||||
|
// This is slightly obscure, and should probably be moved...
|
||||||
Hashtable<String, Object> properties = null;
|
Hashtable<String, Object> properties = null;
|
||||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||||
properties = new Hashtable<>(1);
|
properties = new Hashtable<>(1);
|
||||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage image = new BufferedImage(
|
WritableRaster raster = icm.createCompatibleWritableRaster(getWidth(), getHeight());
|
||||||
icm,
|
BufferedImage image = new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), properties);
|
||||||
icm.createCompatibleWritableRaster(getWidth(), getHeight()),
|
|
||||||
icm.isAlphaPremultiplied(), properties
|
|
||||||
);
|
|
||||||
|
|
||||||
WritableRaster raster = image.getRaster();
|
|
||||||
|
|
||||||
// Make pixels transparent according to mask
|
// Make pixels transparent according to mask
|
||||||
final int trans = icm.getTransparentPixel();
|
final int trans = icm.getTransparentPixel();
|
||||||
@@ -105,7 +99,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
// No duplicate found, increase bitcount
|
// No duplicate found, increase bit count
|
||||||
bits++;
|
bits++;
|
||||||
transparent = this.colors.length - 1;
|
transparent = this.colors.length - 1;
|
||||||
}
|
}
|
||||||
@@ -117,10 +111,8 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||||
return new IndexColorModel(
|
return new IndexColorModel(bits, colors, this.colors, 0, true, transparent,
|
||||||
bits, colors, this.colors, 0, true, transparent,
|
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT);
|
||||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||||
@@ -163,6 +155,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
return transparent;
|
return transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
image = createImageIndexed();
|
image = createImageIndexed();
|
||||||
|
|||||||
+8
-8
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,17 +39,17 @@ import java.awt.image.BufferedImage;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapMask extends BitmapDescriptor {
|
final class BitmapMask extends BitmapDescriptor {
|
||||||
protected final BitmapIndexed bitMask;
|
final BitmapIndexed bitMask;
|
||||||
|
|
||||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
public BitmapMask(final DirectoryEntry parent, final DIBHeader header) {
|
||||||
super(pParent, pHeader);
|
super(parent, header);
|
||||||
bitMask = new BitmapIndexed(pParent, pHeader);
|
bitMask = new BitmapIndexed(parent, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTransparent(final int pX, final int pY) {
|
boolean isTransparent(final int x, final int y) {
|
||||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||||
return bitMask.bits[pX + pY * getWidth()] != 0;
|
return bitMask.bits[x + y * getWidth()] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
|
|||||||
+6
-6
@@ -31,8 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||||
@@ -40,12 +39,13 @@ import java.awt.image.WritableRaster;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapRGB extends BitmapDescriptor {
|
final class BitmapRGB extends BitmapDescriptor {
|
||||||
|
|
||||||
public BitmapRGB(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapRGB(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
super(pEntry, pHeader);
|
super(entry, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
||||||
// might still have bitmask, but we don't read or use it.
|
// might still have bitmask, but we don't read or use it.
|
||||||
@@ -70,7 +70,7 @@ class BitmapRGB extends BitmapDescriptor {
|
|||||||
|
|
||||||
WritableRaster alphaRaster = masked.getAlphaRaster();
|
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||||
|
|
||||||
byte[] trans = {0x0};
|
byte[] trans = {0x00};
|
||||||
for (int y = 0; y < getHeight(); y++) {
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
for (int x = 0; x < getWidth(); x++) {
|
for (int x = 0; x < getWidth(); x++) {
|
||||||
if (mask.isTransparent(x, y)) {
|
if (mask.isTransparent(x, y)) {
|
||||||
|
|||||||
+11
-8
@@ -30,7 +30,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import javax.imageio.IIOException;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents bitmap structures we can't read.
|
* Represents bitmap structures we can't read.
|
||||||
@@ -39,16 +41,17 @@ import java.awt.image.BufferedImage;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapUnsupported extends BitmapDescriptor {
|
final class BitmapUnsupported extends BitmapDescriptor {
|
||||||
private String message;
|
private final String message;
|
||||||
|
|
||||||
public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) {
|
public BitmapUnsupported(final DirectoryEntry entry, DIBHeader header, final String message) {
|
||||||
super(pEntry, null);
|
super(entry, header);
|
||||||
|
|
||||||
message = pMessage;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
@Override
|
||||||
throw new IllegalStateException(message);
|
public BufferedImage getImage() throws IOException {
|
||||||
|
throw new IIOException(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -48,22 +48,22 @@ public final class CURImageReader extends DIBImageReader {
|
|||||||
super(new CURImageReaderSpi());
|
super(new CURImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CURImageReader(final ImageReaderSpi pProvider) {
|
CURImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hot spot location for the cursor.
|
* Returns the hot spot location for the cursor.
|
||||||
*
|
*
|
||||||
* @param pImageIndex the index of the cursor in the current input.
|
* @param imageIndex the index of the cursor in the current input.
|
||||||
* @return the hot spot location for the cursor
|
* @return the hot spot location for the cursor
|
||||||
*
|
*
|
||||||
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data
|
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data
|
||||||
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
|
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
|
||||||
* the number of cursors in the file
|
* the number of cursors in the file
|
||||||
*/
|
*/
|
||||||
public final Point getHotSpot(final int pImageIndex) throws IOException {
|
public Point getHotSpot(final int imageIndex) throws IOException {
|
||||||
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(pImageIndex);
|
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(imageIndex);
|
||||||
return entry.getHotspot();
|
return entry.getHotspot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -49,15 +48,15 @@ public final class CURImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new CURProviderInfo());
|
super(new CURProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) pSource, DIB.TYPE_CUR);
|
return source instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) source, DIB.TYPE_CUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public CURImageReader createReaderInstance(final Object extension) {
|
||||||
return new CURImageReader(this);
|
return new CURImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Cursor Format (CUR) Reader";
|
return "Windows Cursor Format (CUR) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+176
-102
@@ -90,17 +90,17 @@ abstract class DIBHeader {
|
|||||||
protected DIBHeader() {
|
protected DIBHeader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DIBHeader read(final DataInput pStream) throws IOException {
|
public static DIBHeader read(final DataInput stream) throws IOException {
|
||||||
int size = pStream.readInt();
|
int size = stream.readInt();
|
||||||
|
|
||||||
DIBHeader header = createHeader(size);
|
DIBHeader header = createHeader(size);
|
||||||
header.read(size, pStream);
|
header.read(size, stream);
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DIBHeader createHeader(final int pSize) throws IOException {
|
private static DIBHeader createHeader(final int size) throws IOException {
|
||||||
switch (pSize) {
|
switch (size) {
|
||||||
case DIB.BITMAP_CORE_HEADER_SIZE:
|
case DIB.BITMAP_CORE_HEADER_SIZE:
|
||||||
return new BitmapCoreHeader();
|
return new BitmapCoreHeader();
|
||||||
case DIB.OS2_V2_HEADER_16_SIZE:
|
case DIB.OS2_V2_HEADER_16_SIZE:
|
||||||
@@ -117,11 +117,12 @@ abstract class DIBHeader {
|
|||||||
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
|
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
|
||||||
return new BitmapV5InfoHeader();
|
return new BitmapV5InfoHeader();
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", pSize));
|
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void read(int pSize, DataInput pStream) throws IOException;
|
protected abstract void read(int size, DataInput stream) throws IOException;
|
||||||
|
protected abstract void write(final DataOutput stream) throws IOException;
|
||||||
|
|
||||||
public final int getSize() {
|
public final int getSize() {
|
||||||
return size;
|
return size;
|
||||||
@@ -188,12 +189,12 @@ abstract class DIBHeader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] readMasks(final DataInput pStream, final boolean hasAlphaMask) throws IOException {
|
private static int[] readMasks(final DataInput stream, final boolean hasAlphaMask) throws IOException {
|
||||||
int maskCount = hasAlphaMask ? 4 : 3;
|
int maskCount = hasAlphaMask ? 4 : 3;
|
||||||
int[] masks = new int[4];
|
int[] masks = new int[4];
|
||||||
|
|
||||||
for (int i = 0; i < maskCount; i++) {
|
for (int i = 0; i < maskCount; i++) {
|
||||||
masks[i] = pStream.readInt();
|
masks[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return masks;
|
return masks;
|
||||||
@@ -204,24 +205,30 @@ abstract class DIBHeader {
|
|||||||
// TODO: Get rid of code duplication below...
|
// TODO: Get rid of code duplication below...
|
||||||
|
|
||||||
static final class BitmapCoreHeader extends DIBHeader {
|
static final class BitmapCoreHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (pSize != DIB.BITMAP_CORE_HEADER_SIZE) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_CORE_HEADER_SIZE));
|
if (size != DIB.BITMAP_CORE_HEADER_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_CORE_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
|
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
|
||||||
width = pStream.readUnsignedShort();
|
width = stream.readUnsignedShort();
|
||||||
height = pStream.readUnsignedShort();
|
height = stream.readShort();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
@@ -240,45 +247,52 @@ abstract class DIBHeader {
|
|||||||
* @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a>
|
* @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a>
|
||||||
*/
|
*/
|
||||||
static final class BitmapCoreHeaderV2 extends DIBHeader {
|
static final class BitmapCoreHeaderV2 extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@SuppressWarnings("unused")
|
||||||
if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
|
@Override
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
|
if (size != DIB.OS2_V2_HEADER_SIZE && size != DIB.OS2_V2_HEADER_16_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.OS2_V2_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
|
|
||||||
if (pSize != DIB.OS2_V2_HEADER_16_SIZE) {
|
if (size != DIB.OS2_V2_HEADER_16_SIZE) {
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use? These fields are not reflected in metadata as per now...
|
// TODO: Use? These fields are not reflected in metadata as per now...
|
||||||
int units = pStream.readShort();
|
int units = stream.readShort();
|
||||||
int reserved = pStream.readShort();
|
int reserved = stream.readShort();
|
||||||
int recording = pStream.readShort(); // Recording algorithm
|
int recording = stream.readShort(); // Recording algorithm
|
||||||
int rendering = pStream.readShort(); // Halftoning algorithm
|
int rendering = stream.readShort(); // Halftoning algorithm
|
||||||
int size1 = pStream.readInt(); // Reserved for halftoning use
|
int size1 = stream.readInt(); // Reserved for halftoning use
|
||||||
int size2 = pStream.readInt(); // Reserved for halftoning use
|
int size2 = stream.readInt(); // Reserved for halftoning use
|
||||||
int colorEncoding = pStream.readInt(); // Color model used in bitmap
|
int colorEncoding = stream.readInt(); // Color model used in bitmap
|
||||||
int identifier = pStream.readInt(); // Reserved for application use
|
int identifier = stream.readInt(); // Reserved for application use
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
@@ -286,7 +300,6 @@ abstract class DIBHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
|
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
|
||||||
* This is the common format for persistent DIB structures, even if Windows
|
* This is the common format for persistent DIB structures, even if Windows
|
||||||
@@ -302,44 +315,46 @@ abstract class DIBHeader {
|
|||||||
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>.
|
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>.
|
||||||
*/
|
*/
|
||||||
static final class BitmapInfoHeader extends DIBHeader {
|
static final class BitmapInfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (!(pSize == DIB.BITMAP_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V2_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V3_INFO_HEADER_SIZE)) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_INFO_HEADER_SIZE));
|
if (!(size == DIB.BITMAP_INFO_HEADER_SIZE || size == DIB.BITMAP_V2_INFO_HEADER_SIZE || size == DIB.BITMAP_V3_INFO_HEADER_SIZE)) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
// Read masks if we have V2 or V3
|
// Read masks if we have V2 or V3
|
||||||
// or if we have compression BITFIELDS or ALPHA_BITFIELDS
|
// or if we have compression BITFIELDS or ALPHA_BITFIELDS
|
||||||
if (size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) {
|
if (this.size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) {
|
||||||
masks = readMasks(pStream, false);
|
masks = readMasks(stream, false);
|
||||||
}
|
}
|
||||||
else if (size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) {
|
else if (this.size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) {
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(final DataOutput stream) throws IOException {
|
@Override
|
||||||
|
protected void write(final DataOutput stream) throws IOException {
|
||||||
stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE);
|
stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE);
|
||||||
|
|
||||||
stream.writeInt(width);
|
stream.writeInt(width);
|
||||||
@@ -357,12 +372,16 @@ abstract class DIBHeader {
|
|||||||
stream.writeInt(colorsUsed);
|
stream.writeInt(colorsUsed);
|
||||||
stream.writeInt(colorsImportant);
|
stream.writeInt(colorsImportant);
|
||||||
|
|
||||||
// TODO: Write masks, if bitfields
|
// TODO: Write masks, if COMPRESSION_BITFIELDS/COMPRESSION_ALPHA_BITFIELDS
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
// This is to be compatible with the native metadata of the original com.sun....BMPMetadata
|
// This is to be compatible with the native metadata of the original com.sun....BMPMetadata
|
||||||
return compression == DIB.COMPRESSION_BITFIELDS ? "BMP v. 3.x NT" : "BMP v. 3.x";
|
return size > DIB.BITMAP_INFO_HEADER_SIZE
|
||||||
|
? "BMP V2/V3 INFO"
|
||||||
|
: compression == DIB.COMPRESSION_BITFIELDS || compression == DIB.COMPRESSION_ALPHA_BITFIELDS
|
||||||
|
? "BMP v. 3.x NT"
|
||||||
|
: "BMP v. 3.x";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,105 +389,160 @@ abstract class DIBHeader {
|
|||||||
* Represents the BITMAPV4INFOHEADER structure.
|
* Represents the BITMAPV4INFOHEADER structure.
|
||||||
*/
|
*/
|
||||||
static final class BitmapV4InfoHeader extends DIBHeader {
|
static final class BitmapV4InfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (pSize != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V4_INFO_HEADER_SIZE));
|
if (size != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V4_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
|
|
||||||
colorSpaceType = pStream.readInt(); // Should be 0 for V4
|
colorSpaceType = stream.readInt(); // Should be 0 for V4
|
||||||
cieXYZEndpoints = new double[9];
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...?
|
||||||
}
|
}
|
||||||
|
|
||||||
gamma = new int[3];
|
gamma = new int[3];
|
||||||
|
|
||||||
for (int i = 0; i < gamma.length; i++) {
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
gamma[i] = pStream.readInt();
|
gamma[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
return "BMP v. 4.x";
|
return "BMP v. 4.x";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) throws IOException {
|
||||||
|
stream.writeInt(DIB.BITMAP_V4_INFO_HEADER_SIZE);
|
||||||
|
|
||||||
|
stream.writeInt(width);
|
||||||
|
stream.writeInt(topDown ? -height : height);
|
||||||
|
|
||||||
|
stream.writeShort(planes);
|
||||||
|
stream.writeShort(bitCount);
|
||||||
|
stream.writeInt(compression);
|
||||||
|
|
||||||
|
stream.writeInt(imageSize);
|
||||||
|
|
||||||
|
stream.writeInt(xPixelsPerMeter);
|
||||||
|
stream.writeInt(yPixelsPerMeter);
|
||||||
|
|
||||||
|
stream.writeInt(colorsUsed);
|
||||||
|
stream.writeInt(colorsImportant);
|
||||||
|
|
||||||
|
// Red, Green, Blue, Alpha masks
|
||||||
|
stream.writeInt(masks[0]);
|
||||||
|
stream.writeInt(masks[1]);
|
||||||
|
stream.writeInt(masks[2]);
|
||||||
|
stream.writeInt(masks[3]);
|
||||||
|
|
||||||
|
// color space ("sRGB" LITTLE endian)
|
||||||
|
stream.writeInt(DIB.LCS_sRGB);
|
||||||
|
|
||||||
|
// 36 bytes CIE XYZ triples, unused for sRGB
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
// Red gamma, unused for sRGB
|
||||||
|
// Green gamma, unused for sRGB
|
||||||
|
// Blue gamma, unused for sRGB
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the BITMAPV5INFOHEADER structure.
|
* Represents the BITMAPV5INFOHEADER structure.
|
||||||
*/
|
*/
|
||||||
static final class BitmapV5InfoHeader extends DIBHeader {
|
static final class BitmapV5InfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
if (pSize != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
|
if (size != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V5_INFO_HEADER_SIZE));
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V5_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
|
|
||||||
colorSpaceType = pStream.readInt();
|
colorSpaceType = stream.readInt();
|
||||||
|
|
||||||
cieXYZEndpoints = new double[9];
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...?
|
||||||
}
|
}
|
||||||
|
|
||||||
gamma = new int[3];
|
gamma = new int[3];
|
||||||
|
|
||||||
for (int i = 0; i < gamma.length; i++) {
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
gamma[i] = pStream.readInt();
|
gamma[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
intent = pStream.readInt(); // TODO: Verify if this is same as ICC intent
|
intent = stream.readInt(); // TODO: Verify if this is same as ICC intent
|
||||||
profileData = pStream.readInt() & 0xffffffffL;
|
profileData = stream.readInt() & 0xffffffffL;
|
||||||
profileSize = pStream.readInt() & 0xffffffffL;
|
profileSize = stream.readInt() & 0xffffffffL;
|
||||||
pStream.readInt(); // Reserved
|
stream.readInt(); // Reserved
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
|
|||||||
+148
-144
@@ -36,20 +36,26 @@ import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
|||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.util.WeakWeakMap;
|
import com.twelvemonkeys.util.WeakWeakMap;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.event.WindowAdapter;
|
import java.awt.event.*;
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageReader for Microsoft Windows ICO (icon) format.
|
* ImageReader for Microsoft Windows ICO (icon) format.
|
||||||
@@ -73,13 +79,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
private Directory directory;
|
private Directory directory;
|
||||||
|
|
||||||
// TODO: Review these, make sure we don't have a memory leak
|
// TODO: Review these, make sure we don't have a memory leak
|
||||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
private final Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
private final Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||||
|
|
||||||
private ImageReader pngImageReader;
|
private ImageReader pngImageReader;
|
||||||
|
|
||||||
protected DIBImageReader(final ImageReaderSpi pProvider) {
|
protected DIBImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
@@ -94,8 +100,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||||
DirectoryEntry entry = getEntry(pImageIndex);
|
DirectoryEntry entry = getEntry(imageIndex);
|
||||||
|
|
||||||
// NOTE: Delegate to PNG reader
|
// NOTE: Delegate to PNG reader
|
||||||
if (isPNG(entry)) {
|
if (isPNG(entry)) {
|
||||||
@@ -147,39 +153,39 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return getDirectory().count();
|
return getDirectory().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(final int pImageIndex) throws IOException {
|
public int getWidth(final int imageIndex) throws IOException {
|
||||||
return getEntry(pImageIndex).getWidth();
|
return getEntry(imageIndex).getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(final int pImageIndex) throws IOException {
|
public int getHeight(final int imageIndex) throws IOException {
|
||||||
return getEntry(pImageIndex).getHeight();
|
return getEntry(imageIndex).getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
|
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
processImageStarted(pImageIndex);
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
DirectoryEntry entry = getEntry(pImageIndex);
|
DirectoryEntry entry = getEntry(imageIndex);
|
||||||
|
|
||||||
BufferedImage destination;
|
BufferedImage destination;
|
||||||
|
|
||||||
if (isPNG(entry)) {
|
if (isPNG(entry)) {
|
||||||
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
||||||
destination = readPNG(entry, pParam);
|
destination = readPNG(entry, param);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
||||||
// to allow for storing the cursor hotspot for CUR images
|
// to allow for storing the cursor hotspot for CUR images
|
||||||
destination = hasExplicitDestination(pParam) ?
|
destination = hasExplicitDestination(param) ?
|
||||||
getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) : null;
|
getDestination(param, getImageTypes(imageIndex), getWidth(imageIndex), getHeight(imageIndex)) : null;
|
||||||
|
|
||||||
BufferedImage image = readBitmap(entry);
|
BufferedImage image = readBitmap(entry);
|
||||||
|
|
||||||
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
||||||
if (pParam != null) {
|
if (param != null) {
|
||||||
image = fakeAOI(image, pParam);
|
image = fakeAOI(image, param);
|
||||||
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
|
image = ImageUtil.toBuffered(fakeSubsampling(image, param));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
@@ -205,10 +211,10 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
|
private boolean isPNG(final DirectoryEntry entry) throws IOException {
|
||||||
long magic;
|
long magic;
|
||||||
|
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -221,22 +227,20 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return magic == DIB.PNG_MAGIC;
|
return magic == DIB.PNG_MAGIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
|
private BufferedImage readPNG(final DirectoryEntry entry, final ImageReadParam param) throws IOException {
|
||||||
// TODO: Consider delegating listener calls
|
// TODO: Consider delegating listener calls
|
||||||
return initPNGReader(pEntry).read(0, pParam);
|
return initPNGReader(entry).read(0, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
|
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry entry) throws IOException {
|
||||||
return initPNGReader(pEntry).getImageTypes(0);
|
return initPNGReader(entry).getImageTypes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
|
private ImageReader initPNGReader(final DirectoryEntry entry) throws IOException {
|
||||||
ImageReader pngReader = getPNGReader();
|
ImageReader pngReader = getPNGReader();
|
||||||
|
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
// InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize());
|
ImageInputStream stream = new SubImageInputStream(imageInput, entry.getSize());
|
||||||
// ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
|
|
||||||
ImageInputStream stream = new SubImageInputStream(imageInput, pEntry.getSize());
|
|
||||||
|
|
||||||
// NOTE: Will throw IOException on later reads if input is not PNG
|
// NOTE: Will throw IOException on later reads if input is not PNG
|
||||||
pngReader.setInput(stream);
|
pngReader.setInput(stream);
|
||||||
@@ -263,31 +267,31 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return pngImageReader;
|
return pngImageReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
|
private DIBHeader getHeader(final DirectoryEntry entry) throws IOException {
|
||||||
if (!headers.containsKey(pEntry)) {
|
if (!headers.containsKey(entry)) {
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
DIBHeader header = DIBHeader.read(imageInput);
|
DIBHeader header = DIBHeader.read(imageInput);
|
||||||
headers.put(pEntry, header);
|
headers.put(entry, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers.get(pEntry);
|
return headers.get(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
|
private BufferedImage readBitmap(final DirectoryEntry entry) throws IOException {
|
||||||
// TODO: Get rid of the caching, as the images are mutable
|
// TODO: Get rid of the caching, as the images are mutable
|
||||||
BitmapDescriptor descriptor = descriptors.get(pEntry);
|
BitmapDescriptor descriptor = descriptors.get(entry);
|
||||||
|
|
||||||
if (descriptor == null || !descriptors.containsKey(pEntry)) {
|
if (descriptor == null || !descriptors.containsKey(entry)) {
|
||||||
DIBHeader header = getHeader(pEntry);
|
DIBHeader header = getHeader(entry);
|
||||||
|
|
||||||
int offset = pEntry.getOffset() + header.getSize();
|
int offset = entry.getOffset() + header.getSize();
|
||||||
if (offset != imageInput.getStreamPosition()) {
|
if (offset != imageInput.getStreamPosition()) {
|
||||||
imageInput.seek(offset);
|
imageInput.seek(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
||||||
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
||||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression()));
|
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported compression: %d", header.getCompression()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int bitCount = header.getBitCount();
|
int bitCount = header.getBitCount();
|
||||||
@@ -297,75 +301,75 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
case 1:
|
case 1:
|
||||||
case 4:
|
case 4:
|
||||||
case 8: // TODO: Gray!
|
case 8: // TODO: Gray!
|
||||||
descriptor = new BitmapIndexed(pEntry, header);
|
descriptor = new BitmapIndexed(entry, header);
|
||||||
readBitmapIndexed((BitmapIndexed) descriptor);
|
readBitmapIndexed((BitmapIndexed) descriptor);
|
||||||
break;
|
break;
|
||||||
// RGB style
|
// RGB style
|
||||||
case 16:
|
case 16:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap16(descriptor);
|
readBitmap16(descriptor);
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap24(descriptor);
|
readBitmap24(descriptor);
|
||||||
break;
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap32(descriptor);
|
readBitmap32(descriptor);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount));
|
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported bit count %d", bitCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptors.put(pEntry, descriptor);
|
descriptors.put(entry, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return descriptor.getImage();
|
return descriptor.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed(final BitmapIndexed bitmap) throws IOException {
|
||||||
readColorMap(pBitmap);
|
readColorMap(bitmap);
|
||||||
|
|
||||||
switch (pBitmap.getBitCount()) {
|
switch (bitmap.getBitCount()) {
|
||||||
case 1:
|
case 1:
|
||||||
readBitmapIndexed1(pBitmap, false);
|
readBitmapIndexed1(bitmap, false);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
readBitmapIndexed4(pBitmap);
|
readBitmapIndexed4(bitmap);
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
readBitmapIndexed8(pBitmap);
|
readBitmapIndexed8(bitmap);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header);
|
||||||
readBitmapIndexed1(mask.bitMask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
pBitmap.setMask(mask);
|
bitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
|
private void readColorMap(final BitmapIndexed bitmap) throws IOException {
|
||||||
int colorCount = pBitmap.getColorCount();
|
int colorCount = bitmap.getColorCount();
|
||||||
|
|
||||||
for (int i = 0; i < colorCount; i++) {
|
for (int i = 0; i < colorCount; i++) {
|
||||||
// aRGB (a is "Reserved")
|
// aRGB (a is "Reserved")
|
||||||
pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
|
bitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
|
private void readBitmapIndexed1(final BitmapIndexed bitmap, final boolean asMask) throws IOException {
|
||||||
int width = adjustToPadding(pBitmap.getWidth() >> 3);
|
int width = adjustToPadding((bitmap.getWidth() + 7) >> 3);
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
int xOrVal = 0x80;
|
int xOrVal = 0x80;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
pBitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
bitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
||||||
|
|
||||||
if (xOrVal == 1) {
|
if (xOrVal == 1) {
|
||||||
xOrVal = 0x80;
|
xOrVal = 0x80;
|
||||||
@@ -376,29 +380,29 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: If we are reading the mask, we don't abort or report progress
|
// NOTE: If we are reading the mask, we can't abort or report progress
|
||||||
if (!pAsMask) {
|
if (!asMask) {
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed4(final BitmapIndexed bitmap) throws IOException {
|
||||||
int width = adjustToPadding(pBitmap.getWidth() >> 1);
|
int width = adjustToPadding((bitmap.getWidth() + 1) >> 1);
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
boolean high4 = true;
|
boolean high4 = true;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
int value;
|
int value;
|
||||||
|
|
||||||
if (high4) {
|
if (high4) {
|
||||||
@@ -409,7 +413,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
rowPos++;
|
rowPos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBitmap.bits[pos++] = value & 0xFF;
|
bitmap.bits[pos++] = value & 0xFF;
|
||||||
high4 = !high4;
|
high4 = !high4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,22 +422,22 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed8(final BitmapIndexed bitmap) throws IOException {
|
||||||
int width = adjustToPadding(pBitmap.getWidth());
|
int width = adjustToPadding(bitmap.getWidth());
|
||||||
|
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
pBitmap.bits[pos++] = row[rowPos++] & 0xFF;
|
bitmap.bits[pos++] = row[rowPos++] & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
@@ -441,41 +445,41 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
|
* @param width Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 2)
|
||||||
* @return padded width
|
* @return padded width
|
||||||
*/
|
*/
|
||||||
private static int adjustToPadding(final int pWidth) {
|
private static int adjustToPadding(final int width) {
|
||||||
if ((pWidth & 0x03) != 0) {
|
if ((width & 0x03) != 0) {
|
||||||
return (pWidth & ~0x03) + 4;
|
return (width & ~0x03) + 4;
|
||||||
}
|
}
|
||||||
return pWidth;
|
|
||||||
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap16(final BitmapDescriptor bitmap) throws IOException {
|
||||||
// TODO: No idea if this actually works..
|
short[] pixels = new short[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
|
||||||
|
|
||||||
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
||||||
// Will create TYPE_USHORT_555
|
// Will create TYPE_USHORT_555
|
||||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferUShort(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
imageInput.readFully(pixels, offset, bitmap.getWidth());
|
||||||
|
|
||||||
|
|
||||||
// Skip to 32 bit boundary
|
// Skip to 32 bit boundary
|
||||||
if (pBitmap.getWidth() % 2 != 0) {
|
if (bitmap.getWidth() % 2 != 0) {
|
||||||
imageInput.readShort();
|
imageInput.readShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,14 +488,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Might be mask!?
|
// TODO: Might be mask!?
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap24(final BitmapDescriptor bitmap) throws IOException {
|
||||||
byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
|
byte[] pixels = new byte[bitmap.getWidth() * bitmap.getHeight() * 3];
|
||||||
|
|
||||||
// Create TYPE_3BYTE_BGR
|
// Create TYPE_3BYTE_BGR
|
||||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
||||||
@@ -502,17 +506,17 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||||
);
|
);
|
||||||
|
|
||||||
int scanlineStride = pBitmap.getWidth() * 3;
|
int scanlineStride = bitmap.getWidth() * 3;
|
||||||
// BMP rows are padded to 4 byte boundary
|
// BMP rows are padded to 4 byte boundary
|
||||||
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||||
|
|
||||||
WritableRaster raster = Raster.createInterleavedRaster(
|
WritableRaster raster = Raster.createInterleavedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
int offset = (bitmap.getHeight() - y - 1) * scanlineStride;
|
||||||
imageInput.readFully(pixels, offset, scanlineStride);
|
imageInput.readFully(pixels, offset, scanlineStride);
|
||||||
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||||
|
|
||||||
@@ -521,38 +525,38 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 24 bit icons usually have a bit mask
|
// 24 bit icons usually have a bit mask
|
||||||
if (pBitmap.hasMask()) {
|
if (bitmap.hasMask()) {
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header);
|
||||||
readBitmapIndexed1(mask.bitMask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
|
|
||||||
pBitmap.setMask(mask);
|
bitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap32(final BitmapDescriptor bitmap) throws IOException {
|
||||||
int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
|
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
|
|
||||||
// Will create TYPE_INT_ARGB
|
// Will create TYPE_INT_ARGB
|
||||||
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||||
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
imageInput.readFully(pixels, offset, bitmap.getWidth());
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// There might be a mask here as well, but we'll ignore it,
|
// There might be a mask here as well, but we'll ignore it,
|
||||||
@@ -583,18 +587,18 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
directory = Directory.read(type, imageCount, imageInput);
|
directory = Directory.read(type, imageCount, imageInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
|
final DirectoryEntry getEntry(final int imageIndex) throws IOException {
|
||||||
Directory directory = getDirectory();
|
Directory directory = getDirectory();
|
||||||
if (pImageIndex < 0 || pImageIndex >= directory.count()) {
|
if (imageIndex < 0 || imageIndex >= directory.count()) {
|
||||||
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
|
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", imageIndex, directory.count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return directory.getEntry(pImageIndex);
|
return directory.getEntry(imageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test code below, ignore.. :-)
|
/// Test code below, ignore.. :-)
|
||||||
public static void main(final String[] pArgs) throws IOException {
|
public static void main(final String[] args) throws IOException {
|
||||||
if (pArgs.length == 0) {
|
if (args.length == 0) {
|
||||||
System.err.println("Please specify the icon file name");
|
System.err.println("Please specify the icon file name");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
@@ -606,7 +610,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
String title = new File(pArgs[0]).getName();
|
String title = new File(args[0]).getName();
|
||||||
JFrame frame = createWindow(title);
|
JFrame frame = createWindow(title);
|
||||||
JPanel root = new JPanel(new FlowLayout());
|
JPanel root = new JPanel(new FlowLayout());
|
||||||
JScrollPane scroll =
|
JScrollPane scroll =
|
||||||
@@ -622,7 +626,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
|
|
||||||
for (String arg : pArgs) {
|
for (String arg : args) {
|
||||||
JPanel panel = new JPanel(null);
|
JPanel panel = new JPanel(null);
|
||||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||||
readImagesInFile(arg, reader, panel);
|
readImagesInFile(arg, reader, panel);
|
||||||
@@ -633,28 +637,28 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
|
private static void readImagesInFile(String fileName, ImageReader reader, final Container container) throws IOException {
|
||||||
File file = new File(pFileName);
|
File file = new File(fileName);
|
||||||
if (!file.isFile()) {
|
if (!file.isFile()) {
|
||||||
System.err.println(pFileName + " not found, or is no file");
|
System.err.println(fileName + " not found, or is no file");
|
||||||
}
|
}
|
||||||
|
|
||||||
pReader.setInput(ImageIO.createImageInputStream(file));
|
reader.setInput(ImageIO.createImageInputStream(file));
|
||||||
int imageCount = pReader.getNumImages(true);
|
int imageCount = reader.getNumImages(true);
|
||||||
for (int i = 0; i < imageCount; i++) {
|
for (int i = 0; i < imageCount; i++) {
|
||||||
try {
|
try {
|
||||||
addImage(pContainer, pReader, i);
|
addImage(container, reader, i);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
System.err.println("FileName: " + pFileName);
|
System.err.println("FileName: " + fileName);
|
||||||
System.err.println("Icon: " + i);
|
System.err.println("Icon: " + i);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JFrame createWindow(final String pTitle) {
|
private static JFrame createWindow(final String title) {
|
||||||
JFrame frame = new JFrame(pTitle);
|
JFrame frame = new JFrame(title);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
public void windowClosed(WindowEvent e) {
|
public void windowClosed(WindowEvent e) {
|
||||||
@@ -664,15 +668,15 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
|
private static void addImage(final Container parent, final ImageReader reader, final int imageNo) throws IOException {
|
||||||
final JButton button = new JButton();
|
final JButton button = new JButton();
|
||||||
|
|
||||||
BufferedImage image = pReader.read(pImageNo);
|
BufferedImage image = reader.read(imageNo);
|
||||||
button.setIcon(new ImageIcon(image) {
|
button.setIcon(new ImageIcon(image) {
|
||||||
TexturePaint texture;
|
TexturePaint texture;
|
||||||
|
|
||||||
private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
|
private void createTexture(final GraphicsConfiguration graphicsConfiguration) {
|
||||||
BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
|
BufferedImage pattern = graphicsConfiguration.createCompatibleImage(20, 20);
|
||||||
Graphics2D g = pattern.createGraphics();
|
Graphics2D g = pattern.createGraphics();
|
||||||
try {
|
try {
|
||||||
g.setColor(Color.LIGHT_GRAY);
|
g.setColor(Color.LIGHT_GRAY);
|
||||||
@@ -707,6 +711,6 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
|
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
|
||||||
"TrueColor"));
|
"TrueColor"));
|
||||||
|
|
||||||
pParent.add(button);
|
parent.add(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -44,24 +44,25 @@ import java.util.List;
|
|||||||
class Directory {
|
class Directory {
|
||||||
private final List<DirectoryEntry> entries;
|
private final List<DirectoryEntry> entries;
|
||||||
|
|
||||||
private Directory(int pImageCount) {
|
private Directory(int imageCount) {
|
||||||
entries = Arrays.asList(new DirectoryEntry[pImageCount]);
|
entries = Arrays.asList(new DirectoryEntry[imageCount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Directory read(final int pType, final int pImageCount, final DataInput pStream) throws IOException {
|
public static Directory read(final int type, final int imageCount, final DataInput stream) throws IOException {
|
||||||
Directory directory = new Directory(pImageCount);
|
Directory directory = new Directory(imageCount);
|
||||||
directory.readEntries(pType, pStream);
|
directory.readEntries(type, stream);
|
||||||
|
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEntries(final int pType, final DataInput pStream) throws IOException {
|
private void readEntries(final int type, final DataInput stream) throws IOException {
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
entries.set(i, DirectoryEntry.read(pType, pStream));
|
entries.set(i, DirectoryEntry.read(type, stream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryEntry getEntry(final int pEntryIndex) {
|
public DirectoryEntry getEntry(final int entryIndex) {
|
||||||
return entries.get(pEntryIndex);
|
return entries.get(entryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int count() {
|
public int count() {
|
||||||
|
|||||||
+19
-24
@@ -32,8 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.ColorModel;
|
import java.awt.image.*;
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -58,47 +57,43 @@ abstract class DirectoryEntry {
|
|||||||
DirectoryEntry() {
|
DirectoryEntry() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {
|
public static DirectoryEntry read(final int type, final DataInput stream) throws IOException {
|
||||||
DirectoryEntry entry = createEntry(pType);
|
DirectoryEntry entry = createEntry(type);
|
||||||
entry.read(pStream);
|
entry.read(stream);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DirectoryEntry createEntry(int pType) throws IIOException {
|
private static DirectoryEntry createEntry(int type) throws IIOException {
|
||||||
switch (pType) {
|
switch (type) {
|
||||||
case DIB.TYPE_ICO:
|
case DIB.TYPE_ICO:
|
||||||
return new ICOEntry();
|
return new ICOEntry();
|
||||||
case DIB.TYPE_CUR:
|
case DIB.TYPE_CUR:
|
||||||
return new CUREntry();
|
return new CUREntry();
|
||||||
default:
|
default:
|
||||||
throw new IIOException(
|
throw new IIOException(String.format("Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)", type, DIB.TYPE_ICO, DIB.TYPE_CUR));
|
||||||
String.format(
|
|
||||||
"Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)",
|
|
||||||
pType, DIB.TYPE_ICO, DIB.TYPE_CUR
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void read(final DataInput pStream) throws IOException {
|
protected void read(final DataInput stream) throws IOException {
|
||||||
// Width/height = 0, means 256
|
// Width/height = 0, means 256
|
||||||
int w = pStream.readUnsignedByte();
|
int w = stream.readUnsignedByte();
|
||||||
width = w == 0 ? 256 : w;
|
width = w == 0 ? 256 : w;
|
||||||
int h = pStream.readUnsignedByte();
|
int h = stream.readUnsignedByte();
|
||||||
height = h == 0 ? 256 : h;
|
height = h == 0 ? 256 : h;
|
||||||
|
|
||||||
// Color count = 0, means 256 or more colors
|
// Color count = 0, means 256 or more colors
|
||||||
colorCount = pStream.readUnsignedByte();
|
colorCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
|
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
|
||||||
pStream.readUnsignedByte();
|
stream.readUnsignedByte();
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
planes = stream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
||||||
bitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
bitCount = stream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
||||||
|
|
||||||
// Size of bitmap in bytes
|
// Size of bitmap in bytes
|
||||||
size = pStream.readInt();
|
size = stream.readInt();
|
||||||
offset = pStream.readInt();
|
offset = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(final DataOutput output) throws IOException {
|
void write(final DataOutput output) throws IOException {
|
||||||
@@ -157,8 +152,8 @@ abstract class DirectoryEntry {
|
|||||||
private int yHotspot;
|
private int yHotspot;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read(final DataInput pStream) throws IOException {
|
protected void read(final DataInput stream) throws IOException {
|
||||||
super.read(pStream);
|
super.read(stream);
|
||||||
|
|
||||||
// NOTE: This is a hack...
|
// NOTE: This is a hack...
|
||||||
xHotspot = planes;
|
xHotspot = planes;
|
||||||
|
|||||||
+2
-2
@@ -46,7 +46,7 @@ public final class ICOImageReader extends DIBImageReader {
|
|||||||
super(new ICOImageReaderSpi());
|
super(new ICOImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ICOImageReader(final ImageReaderSpi pProvider) {
|
ICOImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -49,32 +48,32 @@ public final class ICOImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new ICOProviderInfo());
|
super(new ICOProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource, DIB.TYPE_ICO);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source, DIB.TYPE_ICO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean canDecode(final ImageInputStream pInput, final int pType) throws IOException {
|
static boolean canDecode(final ImageInputStream input, final int type) throws IOException {
|
||||||
byte[] signature = new byte[4];
|
byte[] signature = new byte[4];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
pInput.readFully(signature);
|
input.readFully(signature);
|
||||||
|
|
||||||
int count = pInput.readByte() + (pInput.readByte() << 8);
|
int count = input.readByte() + (input.readByte() << 8);
|
||||||
|
|
||||||
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == pType
|
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == type
|
||||||
&& signature[3] == 0x0 && count > 0);
|
&& signature[3] == 0x0 && count > 0);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public ICOImageReader createReaderInstance(final Object extension) {
|
||||||
return new ICOImageReader(this);
|
return new ICOImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Icon Format (ICO) Reader";
|
return "Windows Icon Format (ICO) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -33,15 +33,19 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.event.IIOWriteWarningListener;
|
import javax.imageio.event.IIOWriteWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.RenderedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -64,7 +68,7 @@ public final class ICOImageWriter extends DIBImageWriter {
|
|||||||
|
|
||||||
private ImageWriter pngDelegate;
|
private ImageWriter pngDelegate;
|
||||||
|
|
||||||
protected ICOImageWriter(final ImageWriterSpi provider) {
|
ICOImageWriter(final ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +128,7 @@ public final class ICOImageWriter extends DIBImageWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endWriteSequence() throws IOException {
|
public void endWriteSequence() {
|
||||||
assertOutput();
|
assertOutput();
|
||||||
|
|
||||||
if (sequenceIndex < 0) {
|
if (sequenceIndex < 0) {
|
||||||
|
|||||||
+4
-4
@@ -41,8 +41,8 @@ import java.util.Arrays;
|
|||||||
* @version $Id: RLE4Decoder.java#1 $
|
* @version $Id: RLE4Decoder.java#1 $
|
||||||
*/
|
*/
|
||||||
final class RLE4Decoder extends AbstractRLEDecoder {
|
final class RLE4Decoder extends AbstractRLEDecoder {
|
||||||
final static int BIT_MASKS[] = {0xf0, 0x0f};
|
final static int[] BIT_MASKS = {0xf0, 0x0f};
|
||||||
final static int BIT_SHIFTS[] = {4, 0};
|
final static int[] BIT_SHIFTS = {4, 0};
|
||||||
|
|
||||||
public RLE4Decoder(final int width) {
|
public RLE4Decoder(final int width) {
|
||||||
super(width, 4);
|
super(width, 4);
|
||||||
@@ -94,7 +94,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
||||||
|
|
||||||
int packed = 0;
|
int packed = 0;
|
||||||
for (int i = 0; i < byte2; i++) {
|
for (int i = 0; i < byte2 && srcX / 2 < row.length; i++) {
|
||||||
if (i % 2 == 0) {
|
if (i % 2 == 0) {
|
||||||
packed = checkEOF(stream.read());
|
packed = checkEOF(stream.read());
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
|
|||||||
else {
|
else {
|
||||||
// Encoded mode
|
// Encoded mode
|
||||||
// Replicate the two samples in byte2 as many times as byte1 says
|
// Replicate the two samples in byte2 as many times as byte1 says
|
||||||
for (int i = 0; i < byte1; i++) {
|
for (int i = 0; i < byte1 && srcX / 2 < row.length; i++) {
|
||||||
row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]);
|
row[srcX / 2] |= (byte) (((byte2 & BIT_MASKS[i % 2]) >> BIT_SHIFTS[i % 2]) << BIT_SHIFTS[srcX % 2]);
|
||||||
srcX++;
|
srcX++;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -94,7 +94,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
|||||||
// an additional padding byte is in the stream and must be skipped
|
// an additional padding byte is in the stream and must be skipped
|
||||||
boolean paddingByte = (byte2 % 2) != 0;
|
boolean paddingByte = (byte2 % 2) != 0;
|
||||||
|
|
||||||
while (byte2-- > 0) {
|
while (byte2-- > 0 && srcX < row.length) {
|
||||||
row[srcX++] = (byte) checkEOF(stream.read());
|
row[srcX++] = (byte) checkEOF(stream.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
|
|||||||
// Encoded mode
|
// Encoded mode
|
||||||
// Replicate byte2 as many times as byte1 says
|
// Replicate byte2 as many times as byte1 says
|
||||||
byte value = (byte) byte2;
|
byte value = (byte) byte2;
|
||||||
while (byte1-- > 0) {
|
while (byte1-- > 0 && srcX < row.length) {
|
||||||
row[srcX++] = value;
|
row[srcX++] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-31
@@ -32,23 +32,28 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.event.IIOReadProgressListener;
|
import javax.imageio.event.IIOReadProgressListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.spi.IIORegistry;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -56,9 +61,11 @@ import java.util.List;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assume.assumeNoException;
|
import static org.junit.Assume.assumeNoException;
|
||||||
import static org.mockito.Matchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BMPImageReaderTest
|
* BMPImageReaderTest
|
||||||
@@ -68,6 +75,12 @@ import static org.mockito.Mockito.*;
|
|||||||
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader> {
|
public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader> {
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new BMPImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<TestData> getTestData() {
|
protected List<TestData> getTestData() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// BMP Suite "Good"
|
// BMP Suite "Good"
|
||||||
@@ -144,27 +157,17 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImageReaderSpi createProvider() {
|
|
||||||
return new BMPImageReaderSpi();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BMPImageReader createReader() {
|
|
||||||
return new BMPImageReader(createProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<BMPImageReader> getReaderClass() {
|
|
||||||
return BMPImageReader.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("bmp");
|
return Collections.singletonList("bmp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getSuffixes() {
|
protected List<String> getSuffixes() {
|
||||||
return Arrays.asList("bmp", "rle");
|
return Arrays.asList("bmp", "rle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Collections.singletonList("image/bmp");
|
return Collections.singletonList("image/bmp");
|
||||||
}
|
}
|
||||||
@@ -178,7 +181,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
|
|
||||||
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||||
|
|
||||||
// As the JPEGImageReader we delegate to returns null for YCbCr, we'll have to do the same
|
// As the JPEGImageReader we delegate to may return null for YCbCr, we'll have to do the same
|
||||||
if (rawType == null && data.getInput().toString().contains("jpeg")) {
|
if (rawType == null && data.getInput().toString().contains("jpeg")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -273,7 +276,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddIIOReadProgressListenerCallbacksJPEG() {
|
public void testAddIIOReadProgressListenerCallbacksJPEG() throws IOException {
|
||||||
ImageReader reader = createReader();
|
ImageReader reader = createReader();
|
||||||
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64));
|
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64));
|
||||||
reader.setInput(data.getInputStream());
|
reader.setInput(data.getInputStream());
|
||||||
@@ -291,12 +294,12 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(reader, 0);
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddIIOReadProgressListenerCallbacksPNG() {
|
public void testAddIIOReadProgressListenerCallbacksPNG() throws IOException {
|
||||||
ImageReader reader = createReader();
|
ImageReader reader = createReader();
|
||||||
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64));
|
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64));
|
||||||
reader.setInput(data.getInputStream());
|
reader.setInput(data.getInputStream());
|
||||||
@@ -314,18 +317,16 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||||
InOrder ordered = inOrder(listener);
|
InOrder ordered = inOrder(listener);
|
||||||
ordered.verify(listener).imageStarted(reader, 0);
|
ordered.verify(listener).imageStarted(reader, 0);
|
||||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||||
ordered.verify(listener).imageComplete(reader);
|
ordered.verify(listener).imageComplete(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMetadataEqualsJRE() throws IOException, URISyntaxException {
|
public void testMetadataEqualsJRE() throws IOException {
|
||||||
ImageReader jreReader;
|
ImageReader jreReader;
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
ImageReaderSpi provider = (ImageReaderSpi) IIORegistry.getDefaultInstance().getServiceProviderByClass(Class.forName("com.sun.imageio.plugins.bmp.BMPImageReaderSpi"));
|
||||||
Class<ImageReader> jreReaderClass = (Class<ImageReader>) Class.forName("com.sun.imageio.plugins.bmp.BMPImageReader");
|
jreReader = provider.createReaderInstance();
|
||||||
Constructor<ImageReader> constructor = jreReaderClass.getConstructor(ImageReaderSpi.class);
|
|
||||||
jreReader = constructor.newInstance(new Object[] {null});
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -338,6 +339,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
|
|
||||||
for (TestData data : getTestData()) {
|
for (TestData data : getTestData()) {
|
||||||
if (data.getInput().toString().contains("pal8offs")) {
|
if (data.getInput().toString().contains("pal8offs")) {
|
||||||
|
// Skip: Contains extra bogus PaletteEntry nodes
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,9 +356,10 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage());
|
System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IIOMetadata jreMetadata = jreReader.getImageMetadata(0);
|
IIOMetadata jreMetadata = jreReader.getImageMetadata(0);
|
||||||
|
|
||||||
assertEquals(true, metadata.isStandardMetadataFormatSupported());
|
assertTrue(metadata.isStandardMetadataFormatSupported());
|
||||||
assertEquals(jreMetadata.getNativeMetadataFormatName(), metadata.getNativeMetadataFormatName());
|
assertEquals(jreMetadata.getNativeMetadataFormatName(), metadata.getNativeMetadataFormatName());
|
||||||
assertArrayEquals(jreMetadata.getExtraMetadataFormatNames(), metadata.getExtraMetadataFormatNames());
|
assertArrayEquals(jreMetadata.getExtraMetadataFormatNames(), metadata.getExtraMetadataFormatNames());
|
||||||
|
|
||||||
@@ -366,6 +369,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
String absolutePath = data.toString();
|
String absolutePath = data.toString();
|
||||||
String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12);
|
String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12);
|
||||||
|
|
||||||
|
// TODO: blauesglas_16_bitmask444 fails BMP Version for 11+
|
||||||
Node expectedTree = jreMetadata.getAsTree(format);
|
Node expectedTree = jreMetadata.getAsTree(format);
|
||||||
Node actualTree = metadata.getAsTree(format);
|
Node actualTree = metadata.getAsTree(format);
|
||||||
|
|
||||||
@@ -379,7 +383,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
new XMLSerializer(expected, "UTF-8").serialize(expectedTree, false);
|
new XMLSerializer(expected, "UTF-8").serialize(expectedTree, false);
|
||||||
new XMLSerializer(actual, "UTF-8").serialize(actualTree, false);
|
new XMLSerializer(actual, "UTF-8").serialize(actualTree, false);
|
||||||
|
|
||||||
assertEquals(e.getMessage(), new String(expected.toByteArray(), "UTF-8"), new String(actual.toByteArray(), "UTF-8"));
|
assertEquals(e.getMessage(), new String(expected.toByteArray(), StandardCharsets.UTF_8), new String(actual.toByteArray(), StandardCharsets.UTF_8));
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -424,6 +428,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
private boolean excludeEqualValueTest(final Node expected) {
|
private boolean excludeEqualValueTest(final Node expected) {
|
||||||
if (expected.getLocalName().equals("ImageSize")) {
|
if (expected.getLocalName().equals("ImageSize")) {
|
||||||
// JRE metadata returns 0, even if known in reader...
|
// JRE metadata returns 0, even if known in reader...
|
||||||
|
|||||||
+6
-9
@@ -2,10 +2,10 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||||
|
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,18 +15,15 @@ import java.util.List;
|
|||||||
* @author last modified by : harald.kuhr$
|
* @author last modified by : harald.kuhr$
|
||||||
* @version : BMPImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
|
* @version : BMPImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
|
||||||
*/
|
*/
|
||||||
public class BMPImageWriterTest extends ImageWriterAbstractTest {
|
public class BMPImageWriterTest extends ImageWriterAbstractTest<BMPImageWriter> {
|
||||||
|
|
||||||
private final BMPImageWriterSpi provider = new BMPImageWriterSpi();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ImageWriter createImageWriter() {
|
protected ImageWriterSpi createProvider() {
|
||||||
return provider.createWriterInstance(null);
|
return new BMPImageWriterSpi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<? extends RenderedImage> getTestData() {
|
protected List<? extends RenderedImage> getTestData() {
|
||||||
return Arrays.asList(
|
return Collections.singletonList(
|
||||||
new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR)
|
new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-13
@@ -31,6 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -53,6 +54,12 @@ import static org.junit.Assert.*;
|
|||||||
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader> {
|
public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader> {
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new CURImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<TestData> getTestData() {
|
protected List<TestData> getTestData() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
|
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
|
||||||
@@ -60,27 +67,17 @@ public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImageReaderSpi createProvider() {
|
|
||||||
return new CURImageReaderSpi();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CURImageReader createReader() {
|
|
||||||
return new CURImageReader();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<CURImageReader> getReaderClass() {
|
|
||||||
return CURImageReader.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("cur");
|
return Collections.singletonList("cur");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getSuffixes() {
|
protected List<String> getSuffixes() {
|
||||||
return Collections.singletonList("cur");
|
return Collections.singletonList("cur");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Arrays.asList("image/vnd.microsoft.cursor", "image/cursor", "image/x-cursor");
|
return Arrays.asList("image/vnd.microsoft.cursor", "image/cursor", "image/x-cursor");
|
||||||
}
|
}
|
||||||
@@ -99,7 +96,7 @@ public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader>
|
|||||||
assertNotNull("Hotspot for cursor not present", hotspot);
|
assertNotNull("Hotspot for cursor not present", hotspot);
|
||||||
|
|
||||||
// Image weirdness
|
// Image weirdness
|
||||||
assertTrue("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty != hotspot);
|
assertNotSame("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty, hotspot);
|
||||||
|
|
||||||
assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point);
|
assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point);
|
||||||
assertEquals(pExpected, hotspot);
|
assertEquals(pExpected, hotspot);
|
||||||
|
|||||||
+9
-12
@@ -31,6 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@@ -49,6 +50,12 @@ import java.util.List;
|
|||||||
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader> {
|
public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader> {
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new ICOImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<TestData> getTestData() {
|
protected List<TestData> getTestData() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new TestData(
|
new TestData(
|
||||||
@@ -75,27 +82,17 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ImageReaderSpi createProvider() {
|
|
||||||
return new ICOImageReaderSpi();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ICOImageReader createReader() {
|
|
||||||
return new ICOImageReader();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<ICOImageReader> getReaderClass() {
|
|
||||||
return ICOImageReader.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> getFormatNames() {
|
protected List<String> getFormatNames() {
|
||||||
return Collections.singletonList("ico");
|
return Collections.singletonList("ico");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getSuffixes() {
|
protected List<String> getSuffixes() {
|
||||||
return Collections.singletonList("ico");
|
return Collections.singletonList("ico");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon");
|
return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon");
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-6
@@ -2,7 +2,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||||
|
|
||||||
import javax.imageio.ImageWriter;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -15,12 +15,10 @@ import java.util.List;
|
|||||||
* @author last modified by : harald.kuhr$
|
* @author last modified by : harald.kuhr$
|
||||||
* @version : ICOImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
|
* @version : ICOImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
|
||||||
*/
|
*/
|
||||||
public class ICOImageWriterTest extends ImageWriterAbstractTest {
|
public class ICOImageWriterTest extends ImageWriterAbstractTest<ICOImageWriter> {
|
||||||
private final ICOImageWriterSpi provider = new ICOImageWriterSpi();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ImageWriter createImageWriter() {
|
protected ImageWriterSpi createProvider() {
|
||||||
return provider.createWriterInstance(null);
|
return new ICOImageWriterSpi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 257 KiB |
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-clippath</artifactId>
|
<artifactId>imageio-clippath</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||||
@@ -12,6 +12,10 @@
|
|||||||
Photoshop Clipping Path Support.
|
Photoshop Clipping Path Support.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.imageio.clippath</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
@@ -21,10 +25,20 @@
|
|||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-metadata</artifactId>
|
<artifactId>imageio-metadata</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+1
@@ -39,6 +39,7 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public final class AdobePathBuilder {
|
public final class AdobePathBuilder {
|
||||||
|
|
||||||
private final AdobePathReader delegate;
|
private final AdobePathReader delegate;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
|||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
@@ -56,6 +57,8 @@ import java.awt.image.BufferedImage;
|
|||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.SequenceInputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -131,7 +134,13 @@ public final class Paths {
|
|||||||
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
|
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
|
||||||
|
|
||||||
if (!photoshop.isEmpty()) {
|
if (!photoshop.isEmpty()) {
|
||||||
return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(photoshop.get(0).data()));
|
InputStream data = null;
|
||||||
|
|
||||||
|
for (JPEGSegment ps : photoshop) {
|
||||||
|
data = data == null ? ps.data() : new SequenceInputStream(data, ps.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC
|
else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC
|
||||||
@@ -299,7 +308,7 @@ public final class Paths {
|
|||||||
throw new IllegalArgumentException("output == null!");
|
throw new IllegalArgumentException("output == null!");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
|
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromRenderedImage(image);
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName);
|
Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName);
|
||||||
|
|
||||||
if (writers.hasNext()) {
|
if (writers.hasNext()) {
|
||||||
@@ -350,10 +359,10 @@ public final class Paths {
|
|||||||
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
|
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
|
||||||
unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF));
|
unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF));
|
||||||
|
|
||||||
byte[] identfier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
|
byte[] identifier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
|
||||||
byte[] data = new byte[identfier.length + 1 + pathResource.length];
|
byte[] data = new byte[identifier.length + 1 + pathResource.length];
|
||||||
System.arraycopy(identfier, 0, data, 0, identfier.length);
|
System.arraycopy(identifier, 0, data, 0, identifier.length);
|
||||||
System.arraycopy(pathResource, 0, data, identfier.length + 1, pathResource.length);
|
System.arraycopy(pathResource, 0, data, identifier.length + 1, pathResource.length);
|
||||||
|
|
||||||
unknown.setUserObject(data);
|
unknown.setUserObject(data);
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,17 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.6</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||||
|
<description>
|
||||||
|
TwelveMonkeys ImageIO core support classes.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.jpms.module.name>com.twelvemonkeys.imageio.core</project.jpms.module.name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -24,4 +31,20 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageInputStreamSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -265,8 +265,9 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
// - transferType is ok
|
// - transferType is ok
|
||||||
// - bands are ok
|
// - bands are ok
|
||||||
// TODO: Test if color model is ok?
|
// TODO: Test if color model is ok?
|
||||||
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() &&
|
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType()
|
||||||
specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
|
&& Arrays.equals(specifier.getSampleModel().getSampleSize(), dest.getSampleModel().getSampleSize())
|
||||||
|
&& specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -314,12 +315,12 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
|
|
||||||
long dimension = (long) destWidth * destHeight;
|
long dimension = (long) destWidth * destHeight;
|
||||||
if (dimension > Integer.MAX_VALUE) {
|
if (dimension > Integer.MAX_VALUE) {
|
||||||
throw new IllegalArgumentException(String.format("destination width * height > Integer.MAX_VALUE: %d", dimension));
|
throw new IIOException(String.format("destination width * height > Integer.MAX_VALUE: %d", dimension));
|
||||||
}
|
}
|
||||||
|
|
||||||
long size = dimension * imageType.getSampleModel().getNumDataElements();
|
long size = dimension * imageType.getSampleModel().getNumDataElements();
|
||||||
if (size > Integer.MAX_VALUE) {
|
if (size > Integer.MAX_VALUE) {
|
||||||
throw new IllegalArgumentException(String.format("destination width * height * samplesPerPixel > Integer.MAX_VALUE: %d", size));
|
throw new IIOException(String.format("destination width * height * samplesPerPixel > Integer.MAX_VALUE: %d", size));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new image based on the type specifier
|
// Create a new image based on the type specifier
|
||||||
@@ -440,6 +441,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
static final String ZOOM_IN = "zoom-in";
|
static final String ZOOM_IN = "zoom-in";
|
||||||
static final String ZOOM_OUT = "zoom-out";
|
static final String ZOOM_OUT = "zoom-out";
|
||||||
static final String ZOOM_ACTUAL = "zoom-actual";
|
static final String ZOOM_ACTUAL = "zoom-actual";
|
||||||
|
static final String ZOOM_FIT = "zoom-fit";
|
||||||
|
|
||||||
private BufferedImage image;
|
private BufferedImage image;
|
||||||
|
|
||||||
@@ -515,9 +517,20 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
|
|
||||||
private void setupActions() {
|
private void setupActions() {
|
||||||
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
|
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
|
||||||
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
|
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN,
|
||||||
bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
|
KeyStroke.getKeyStroke('+'),
|
||||||
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
|
KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_ADD, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
|
bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT,
|
||||||
|
KeyStroke.getKeyStroke('-'),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
|
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL,
|
||||||
|
KeyStroke.getKeyStroke('0'),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
|
bindAction(new ZoomToFitAction("Zoom fit"), ZOOM_FIT,
|
||||||
|
KeyStroke.getKeyStroke('9'),
|
||||||
|
KeyStroke.getKeyStroke(KeyEvent.VK_9, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
|
|
||||||
bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||||
@@ -534,6 +547,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
private JPopupMenu createPopupMenu() {
|
private JPopupMenu createPopupMenu() {
|
||||||
JPopupMenu popup = new JPopupMenu();
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
|
||||||
|
popup.add(getActionMap().get(ZOOM_FIT));
|
||||||
popup.add(getActionMap().get(ZOOM_ACTUAL));
|
popup.add(getActionMap().get(ZOOM_ACTUAL));
|
||||||
popup.add(getActionMap().get(ZOOM_IN));
|
popup.add(getActionMap().get(ZOOM_IN));
|
||||||
popup.add(getActionMap().get(ZOOM_OUT));
|
popup.add(getActionMap().get(ZOOM_OUT));
|
||||||
@@ -554,7 +568,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group);
|
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group);
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group);
|
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group);
|
||||||
background.addSeparator();
|
background.addSeparator();
|
||||||
ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE);
|
ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : new Color(0xFF6600));
|
||||||
chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG);
|
chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG);
|
||||||
addCheckBoxItem(chooseBackgroundAction, background, group);
|
addCheckBoxItem(chooseBackgroundAction, background, group);
|
||||||
|
|
||||||
@@ -668,14 +682,41 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Icon current = getIcon();
|
Icon current = getIcon();
|
||||||
int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16);
|
int w = Math.max(Math.min((int) (current.getIconWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16);
|
||||||
int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16);
|
int h = Math.max(Math.min((int) (current.getIconHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16);
|
||||||
|
|
||||||
setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight()));
|
setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ZoomToFitAction extends ZoomAction {
|
||||||
|
public ZoomToFitAction(final String name) {
|
||||||
|
super(name, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void actionPerformed(final ActionEvent e) {
|
||||||
|
JComponent source = (JComponent) e.getSource();
|
||||||
|
|
||||||
|
if (source instanceof JMenuItem) {
|
||||||
|
JPopupMenu menu = (JPopupMenu) SwingUtilities.getAncestorOfClass(JPopupMenu.class, source);
|
||||||
|
source = (JComponent) menu.getInvoker();
|
||||||
|
}
|
||||||
|
|
||||||
|
Container container = SwingUtilities.getAncestorOfClass(JViewport.class, source);
|
||||||
|
|
||||||
|
double ratioX = container.getWidth() / (double) image.getWidth();
|
||||||
|
double ratioY = container.getHeight() / (double) image.getHeight();
|
||||||
|
|
||||||
|
double zoomFactor = Math.min(ratioX, ratioY);
|
||||||
|
|
||||||
|
int w = Math.max(Math.min((int) (image.getWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16);
|
||||||
|
int h = Math.max(Math.min((int) (image.getHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16);
|
||||||
|
|
||||||
|
setIcon(new BufferedImageIcon(image, w, h, zoomFactor > 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ImageTransferable implements Transferable {
|
private static class ImageTransferable implements Transferable {
|
||||||
private final BufferedImage image;
|
private final BufferedImage image;
|
||||||
|
|
||||||
@@ -694,7 +735,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException {
|
||||||
if (isDataFlavorSupported(flavor)) {
|
if (isDataFlavorSupported(flavor)) {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|||||||
+627
@@ -0,0 +1,627 @@
|
|||||||
|
package com.twelvemonkeys.imageio;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for easy read-only implementation of the standard image metadata format.
|
||||||
|
* Chroma, Data and Transparency nodes values are based on the required
|
||||||
|
* {@link ImageTypeSpecifier}.
|
||||||
|
* Other values or overrides may be specified using the builder.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
*/
|
||||||
|
public class StandardImageMetadataSupport extends AbstractMetadata {
|
||||||
|
|
||||||
|
// The only required field, most standard metadata can be extracted from the type
|
||||||
|
private final ImageTypeSpecifier type;
|
||||||
|
protected final ColorSpaceType colorSpaceType;
|
||||||
|
protected final boolean blackIsZero;
|
||||||
|
private final IndexColorModel palette;
|
||||||
|
protected final String compressionName;
|
||||||
|
protected final boolean compressionLossless;
|
||||||
|
protected final PlanarConfiguration planarConfiguration;
|
||||||
|
private final int[] bitsPerSample;
|
||||||
|
private final int[] significantBits;
|
||||||
|
private final int[] sampleMSB;
|
||||||
|
protected final Double pixelAspectRatio;
|
||||||
|
protected final ImageOrientation orientation;
|
||||||
|
protected final String formatVersion;
|
||||||
|
protected final SubimageInterpretation subimageInterpretation;
|
||||||
|
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
|
||||||
|
private final Collection<TextEntry> textEntries;
|
||||||
|
|
||||||
|
protected StandardImageMetadataSupport(Builder builder) {
|
||||||
|
notNull(builder, "builder");
|
||||||
|
|
||||||
|
// Baseline
|
||||||
|
type = builder.type;
|
||||||
|
|
||||||
|
// Chroma
|
||||||
|
colorSpaceType = builder.colorSpaceType;
|
||||||
|
blackIsZero = builder.blackIsZero;
|
||||||
|
palette = builder.palette;
|
||||||
|
|
||||||
|
// Compression
|
||||||
|
compressionName = builder.compressionName;
|
||||||
|
compressionLossless = builder.compressionLossless;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
planarConfiguration = builder.planarConfiguration;
|
||||||
|
bitsPerSample = builder.bitsPerSample;
|
||||||
|
significantBits = builder.significantBits;
|
||||||
|
sampleMSB = builder.sampleMSB;
|
||||||
|
|
||||||
|
// Dimension
|
||||||
|
orientation = builder.orientation;
|
||||||
|
pixelAspectRatio = builder.pixelAspectRatio;
|
||||||
|
|
||||||
|
// Document
|
||||||
|
formatVersion = builder.formatVersion;
|
||||||
|
documentCreationTime = builder.documentCreationTime;
|
||||||
|
subimageInterpretation = builder.subimageInterpretation;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
textEntries = builder.textEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(ImageTypeSpecifier type) {
|
||||||
|
return new Builder(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final ImageTypeSpecifier type;
|
||||||
|
private ColorSpaceType colorSpaceType;
|
||||||
|
private boolean blackIsZero = true;
|
||||||
|
private IndexColorModel palette;
|
||||||
|
private String compressionName;
|
||||||
|
private boolean compressionLossless = true;
|
||||||
|
private PlanarConfiguration planarConfiguration;
|
||||||
|
public int[] bitsPerSample;
|
||||||
|
private int[] significantBits;
|
||||||
|
private int[] sampleMSB;
|
||||||
|
private Double pixelAspectRatio;
|
||||||
|
private ImageOrientation orientation = ImageOrientation.Normal;
|
||||||
|
private String formatVersion;
|
||||||
|
private SubimageInterpretation subimageInterpretation;
|
||||||
|
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
|
||||||
|
private final Collection<TextEntry> textEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
protected Builder(ImageTypeSpecifier type) {
|
||||||
|
this.type = notNull(type, "type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
|
||||||
|
this.colorSpaceType = colorSpaceType;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withBlackIsZero(boolean blackIsZero) {
|
||||||
|
this.blackIsZero = blackIsZero;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPalette(IndexColorModel palette) {
|
||||||
|
this.palette = palette;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCompressionTypeName(String compressionName) {
|
||||||
|
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCompressionLossless(boolean lossless) {
|
||||||
|
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
|
||||||
|
this.planarConfiguration = planarConfiguration;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withBitsPerSample(int... bitsPerSample) {
|
||||||
|
this.bitsPerSample = bitsPerSample;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSignificantBitsPerSample(int... significantBits) {
|
||||||
|
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
|
||||||
|
significantBits,
|
||||||
|
String.format("single value or %d values expected", type.getNumBands()));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSampleMSB(int... sampleMSB) {
|
||||||
|
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
|
||||||
|
sampleMSB,
|
||||||
|
String.format("single value or %d values expected", type.getNumBands()));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
|
||||||
|
this.pixelAspectRatio = pixelAspectRatio;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withOrientation(ImageOrientation orientation) {
|
||||||
|
this.orientation = notNull(orientation, "orientation");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withFormatVersion(String formatVersion) {
|
||||||
|
this.formatVersion = notNull(formatVersion, "formatVersion");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
|
||||||
|
this.subimageInterpretation = interpretation;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withDocumentCreationTime(Calendar creationTime) {
|
||||||
|
this.documentCreationTime = creationTime;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntries(Map<String, String> entries) {
|
||||||
|
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
|
||||||
|
TextEntry[] result = new TextEntry[entries.size()];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, String> entry : entries) {
|
||||||
|
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.asList(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntries(Collection<TextEntry> entries) {
|
||||||
|
this.textEntries.addAll(notNull(entries, "entries"));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntry(String keyword, String value) {
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IIOMetadata build() {
|
||||||
|
return new StandardImageMetadataSupport(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ColorSpaceType {
|
||||||
|
XYZ(3),
|
||||||
|
Lab(3),
|
||||||
|
Luv(3),
|
||||||
|
YCbCr(3),
|
||||||
|
Yxy(3),
|
||||||
|
YCCK(4),
|
||||||
|
PhotoYCC(3),
|
||||||
|
RGB(3),
|
||||||
|
GRAY(1),
|
||||||
|
HSV(3),
|
||||||
|
HLS(3),
|
||||||
|
CMYK(3),
|
||||||
|
CMY(3),
|
||||||
|
|
||||||
|
// Generic types (so much extra work, because Java names can't start with a number, phew...)
|
||||||
|
GENERIC_2CLR(2, "2CLR"),
|
||||||
|
GENERIC_3CLR(3, "3CLR"),
|
||||||
|
GENERIC_4CLR(4, "4CLR"),
|
||||||
|
GENERIC_5CLR(5, "5CLR"),
|
||||||
|
GENERIC_6CLR(6, "6CLR"),
|
||||||
|
GENERIC_7CLR(7, "7CLR"),
|
||||||
|
GENERIC_8CLR(8, "8CLR"),
|
||||||
|
GENERIC_9CLR(9, "9CLR"),
|
||||||
|
GENERIC_ACLR(0xA, "ACLR"),
|
||||||
|
GENERIC_BCLR(0xB, "BCLR"),
|
||||||
|
GENERIC_CCLR(0xC, "CCLR"),
|
||||||
|
GENERIC_DCLR(0xD, "DCLR"),
|
||||||
|
GENERIC_ECLR(0xE, "ECLR"),
|
||||||
|
GENERIC_FCLR(0xF, "FCLR");
|
||||||
|
|
||||||
|
final int numChannels;
|
||||||
|
private final String nameOverride;
|
||||||
|
|
||||||
|
ColorSpaceType(int numChannels) {
|
||||||
|
this(numChannels, null);
|
||||||
|
}
|
||||||
|
ColorSpaceType(int numChannels, String nameOverride) {
|
||||||
|
this.numChannels = numChannels;
|
||||||
|
this.nameOverride = nameOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return nameOverride != null ? nameOverride : super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum PlanarConfiguration {
|
||||||
|
PixelInterleaved,
|
||||||
|
PlaneInterleaved,
|
||||||
|
LineInterleaved,
|
||||||
|
TileInterleaved
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ImageOrientation {
|
||||||
|
Normal,
|
||||||
|
Rotate90,
|
||||||
|
Rotate180,
|
||||||
|
Rotate270,
|
||||||
|
FlipH,
|
||||||
|
FlipV,
|
||||||
|
FlipHRotate90,
|
||||||
|
FlipVRotate90
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum SubimageInterpretation {
|
||||||
|
Standalone,
|
||||||
|
SinglePage,
|
||||||
|
FullResolution,
|
||||||
|
ReducedResolution,
|
||||||
|
PyramidLayer,
|
||||||
|
Preview,
|
||||||
|
VolumeSlice,
|
||||||
|
ObjectView,
|
||||||
|
Panorama,
|
||||||
|
AnimationFrame,
|
||||||
|
TransparencyMask,
|
||||||
|
CompositingLayer,
|
||||||
|
SpectralSlice,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
|
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
|
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
|
||||||
|
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
|
||||||
|
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
|
||||||
|
|
||||||
|
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chromaNode.appendChild(colorSpaceTypeNode);
|
||||||
|
colorSpaceTypeNode.setAttribute("name", csType.toString());
|
||||||
|
|
||||||
|
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
|
||||||
|
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
|
||||||
|
chromaNode.appendChild(numChannelsNode);
|
||||||
|
|
||||||
|
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
|
||||||
|
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
|
||||||
|
chromaNode.appendChild(blackIsZeroNode);
|
||||||
|
|
||||||
|
if (colorModel instanceof IndexColorModel || palette != null) {
|
||||||
|
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
|
||||||
|
|
||||||
|
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
|
||||||
|
chromaNode.appendChild(paletteNode);
|
||||||
|
|
||||||
|
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||||
|
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
|
||||||
|
paletteNode.appendChild(paletteEntryNode);
|
||||||
|
|
||||||
|
paletteEntryNode.setAttribute("index", Integer.toString(i));
|
||||||
|
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||||
|
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||||
|
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||||
|
|
||||||
|
// Assumption: BITMASK transparency will use single transparent pixel
|
||||||
|
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
|
||||||
|
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorMap.getTransparentPixel() != -1) {
|
||||||
|
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
|
||||||
|
chromaNode.appendChild(backgroundIndexNode);
|
||||||
|
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: BackgroundColor?
|
||||||
|
|
||||||
|
return chromaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
|
||||||
|
switch (colorSpace.getType()) {
|
||||||
|
case ColorSpace.TYPE_XYZ:
|
||||||
|
return XYZ;
|
||||||
|
case ColorSpace.TYPE_Lab:
|
||||||
|
return Lab;
|
||||||
|
case ColorSpace.TYPE_Luv:
|
||||||
|
return Luv;
|
||||||
|
case ColorSpace.TYPE_YCbCr:
|
||||||
|
return YCbCr;
|
||||||
|
case ColorSpace.TYPE_Yxy:
|
||||||
|
return Yxy;
|
||||||
|
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
|
||||||
|
case ColorSpace.TYPE_RGB:
|
||||||
|
return RGB;
|
||||||
|
case ColorSpace.TYPE_GRAY:
|
||||||
|
return GRAY;
|
||||||
|
case ColorSpace.TYPE_HSV:
|
||||||
|
return HSV;
|
||||||
|
case ColorSpace.TYPE_HLS:
|
||||||
|
return HLS;
|
||||||
|
case ColorSpace.TYPE_CMYK:
|
||||||
|
return CMYK;
|
||||||
|
case ColorSpace.TYPE_CMY:
|
||||||
|
return CMY;
|
||||||
|
default:
|
||||||
|
int numComponents = colorSpace.getNumComponents();
|
||||||
|
if (numComponents == 1) {
|
||||||
|
return GRAY;
|
||||||
|
}
|
||||||
|
else if (numComponents < 16) {
|
||||||
|
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final class TextEntry {
|
||||||
|
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
|
||||||
|
|
||||||
|
final String keyword;
|
||||||
|
final String value;
|
||||||
|
final String language;
|
||||||
|
final String encoding;
|
||||||
|
final String compression;
|
||||||
|
|
||||||
|
public TextEntry(final String keyword, final String value) {
|
||||||
|
this(keyword, value, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
|
||||||
|
this.keyword = keyword;
|
||||||
|
this.value = notNull(value, "value");
|
||||||
|
this.language = language;
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
|
if (compressionName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
compressionTypeName.setAttribute("value", compressionName);
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
|
|
||||||
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
|
lossless.setAttribute("value", booleanString(compressionLossless));
|
||||||
|
node.appendChild(lossless);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String booleanString(boolean booleanValue) {
|
||||||
|
return booleanValue ? "TRUE" : "FALSE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
|
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
|
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
|
||||||
|
dataNode.appendChild(planarConfigurationNode);
|
||||||
|
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
|
||||||
|
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
|
||||||
|
|
||||||
|
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
|
||||||
|
? "Index"
|
||||||
|
: sampleFormat(type.getSampleModel());
|
||||||
|
|
||||||
|
if (sampleFormatValue != null) {
|
||||||
|
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
|
||||||
|
sampleFormatNode.setAttribute("value", sampleFormatValue);
|
||||||
|
dataNode.appendChild(sampleFormatNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
|
||||||
|
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
|
||||||
|
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
|
||||||
|
dataNode.appendChild(bitsPerSampleNode);
|
||||||
|
|
||||||
|
if (significantBits != null) {
|
||||||
|
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
|
||||||
|
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
|
||||||
|
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
|
||||||
|
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
|
||||||
|
dataNode.appendChild(significantBitsPerSampleNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleMSB != null) {
|
||||||
|
// TODO: Only if different from default!
|
||||||
|
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
|
||||||
|
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
|
||||||
|
dataNode.appendChild(sampleMSBNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createListValue(final int itemCount, final int... values) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
if (buffer.length() > 0) {
|
||||||
|
buffer.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append(values[i % values.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sampleFormat(SampleModel sampleModel) {
|
||||||
|
switch (sampleModel.getDataType()) {
|
||||||
|
case DataBuffer.TYPE_SHORT:
|
||||||
|
case DataBuffer.TYPE_INT:
|
||||||
|
if (sampleModel instanceof ComponentSampleModel) {
|
||||||
|
return "SignedIntegral";
|
||||||
|
}
|
||||||
|
// Otherwise fall-through, most likely a *PixelPackedSampleModel
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
case DataBuffer.TYPE_USHORT:
|
||||||
|
return "UnsignedIntegral";
|
||||||
|
case DataBuffer.TYPE_FLOAT:
|
||||||
|
case DataBuffer.TYPE_DOUBLE:
|
||||||
|
return "Real";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
|
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
|
if (pixelAspectRatio != null) {
|
||||||
|
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
|
||||||
|
dimensionNode.appendChild(pixelAspectRatioNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
|
||||||
|
imageOrientationNode.setAttribute("value", orientation.toString());
|
||||||
|
dimensionNode.appendChild(imageOrientationNode);
|
||||||
|
|
||||||
|
return dimensionNode.hasChildNodes() ? dimensionNode : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDocumentNode() {
|
||||||
|
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
|
||||||
|
|
||||||
|
if (formatVersion != null) {
|
||||||
|
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
|
||||||
|
documentNode.appendChild(formatVersionNode);
|
||||||
|
formatVersionNode.setAttribute("value", formatVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subimageInterpretation != null) {
|
||||||
|
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
|
||||||
|
documentNode.appendChild(subImageInterpretationNode);
|
||||||
|
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentCreationTime != null) {
|
||||||
|
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
|
||||||
|
documentNode.appendChild(imageCreationTimeNode);
|
||||||
|
|
||||||
|
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
|
||||||
|
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
|
||||||
|
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
|
||||||
|
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
|
||||||
|
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
|
||||||
|
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return documentNode.hasChildNodes() ? documentNode : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
|
if (textEntries.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||||
|
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||||
|
|
||||||
|
for (TextEntry entry : textEntries) {
|
||||||
|
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
|
||||||
|
textNode.appendChild(textEntryNode);
|
||||||
|
if (entry.keyword != null) {
|
||||||
|
textEntryNode.setAttribute("keyword", entry.keyword);
|
||||||
|
}
|
||||||
|
textEntryNode.setAttribute("value", entry.value);
|
||||||
|
if (entry.language != null) {
|
||||||
|
textEntryNode.setAttribute("language", entry.language);
|
||||||
|
}
|
||||||
|
if (entry.encoding != null) {
|
||||||
|
textEntryNode.setAttribute("encoding", entry.encoding);
|
||||||
|
}
|
||||||
|
if (entry.compression != null) {
|
||||||
|
textEntryNode.setAttribute("compression", entry.compression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textNode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||||
|
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
|
||||||
|
|
||||||
|
ColorModel colorModel = type.getColorModel();
|
||||||
|
|
||||||
|
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
|
||||||
|
transparencyNode.appendChild(alphaNode);
|
||||||
|
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
|
||||||
|
|
||||||
|
if (colorModel instanceof IndexColorModel) {
|
||||||
|
IndexColorModel icm = (IndexColorModel) colorModel;
|
||||||
|
if (icm.getTransparentPixel() != -1) {
|
||||||
|
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
|
||||||
|
transparencyNode.appendChild(transparentIndexNode);
|
||||||
|
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transparencyNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
+563
@@ -0,0 +1,563 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
import com.twelvemonkeys.lang.Platform;
|
||||||
|
import com.twelvemonkeys.lang.SystemUtil;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.color.ICC_ColorSpace;
|
||||||
|
import java.awt.color.ICC_Profile;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class for working with ICC color profiles.
|
||||||
|
* <p>
|
||||||
|
* Standard ICC color profiles are read from system-specific locations
|
||||||
|
* for known operating systems.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Color profiles may be configured by placing a property-file
|
||||||
|
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||||
|
* on the classpath, specifying the full path to the profiles.
|
||||||
|
* ICC color profiles are probably already present on your system, or
|
||||||
|
* can be downloaded from
|
||||||
|
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||||
|
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
|
||||||
|
* * </p>
|
||||||
|
* <p>
|
||||||
|
* Example property file:
|
||||||
|
* </p>
|
||||||
|
* <pre>
|
||||||
|
* # icc_profiles.properties
|
||||||
|
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
|
||||||
|
* GENERIC_CMYK=/path/to/Generic CMYK.icc
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class ColorProfiles {
|
||||||
|
/**
|
||||||
|
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
|
||||||
|
*/
|
||||||
|
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||||
|
|
||||||
|
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
|
||||||
|
static final int ICC_PROFILE_HEADER_SIZE = 128;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// In case we didn't activate through SPI already
|
||||||
|
ProfileDeferralActivator.activateProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ColorProfiles() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||||
|
// Get *entire profile data*... :-/
|
||||||
|
return getProfileHeaderWithProfileId(profile.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] getProfileHeaderWithProfileId(byte[] data) {
|
||||||
|
// ICC profile header is the first 128 bytes
|
||||||
|
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
|
||||||
|
|
||||||
|
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
|
||||||
|
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||||
|
// - KCMS keeps the values in the file...
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||||
|
// + Clear out rendering intent, as this may be updated by application
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||||
|
|
||||||
|
// Clear out any existing MD5, as it is no longer correct
|
||||||
|
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||||
|
|
||||||
|
// Generate new MD5 and store in header
|
||||||
|
byte[] md5 = computeMD5(header, data);
|
||||||
|
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] computeMD5(byte[] header, byte[] data) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||||
|
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
|
||||||
|
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
|
||||||
|
return digest.digest();
|
||||||
|
}
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||||
|
*
|
||||||
|
* @param profile the ICC profile to test. May not be {@code null}.
|
||||||
|
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @see java.awt.color.ColorSpace#CS_sRGB
|
||||||
|
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||||
|
*/
|
||||||
|
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is equal to the default GRAY profile.
|
||||||
|
*
|
||||||
|
* @param profile the ICC profile to test. May not be {@code null}.
|
||||||
|
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @see java.awt.color.ColorSpace#CS_GRAY
|
||||||
|
*/
|
||||||
|
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||||
|
* <p>
|
||||||
|
* <em>
|
||||||
|
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||||
|
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||||
|
* </em>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param profile the ICC color profile. May not be {@code null}.
|
||||||
|
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
*/
|
||||||
|
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||||
|
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||||
|
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||||
|
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||||
|
// (that is 1 single bit of difference right there.. ;-)
|
||||||
|
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||||
|
|
||||||
|
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||||
|
// except the getRenderingIntent method is package protected in java.awt.color
|
||||||
|
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||||
|
|
||||||
|
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||||
|
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is valid.
|
||||||
|
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||||
|
* <p>
|
||||||
|
* <em>
|
||||||
|
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||||
|
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||||
|
* </em>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param profile the ICC color profile. May not be {@code null}.
|
||||||
|
* @return {@code profile} if valid.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||||
|
*/
|
||||||
|
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||||
|
// Fix profile before validation
|
||||||
|
profileCleaner.fixProfile(profile);
|
||||||
|
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given input stream, as-is, with no validation.
|
||||||
|
*
|
||||||
|
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
|
||||||
|
*
|
||||||
|
* @param input the input stream to read from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the stream.
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the stream does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(InputStream)
|
||||||
|
* @see #readProfile(InputStream)
|
||||||
|
*/
|
||||||
|
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
return ICC_Profile.getInstance(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given input stream, with extra validation.
|
||||||
|
*
|
||||||
|
* If a matching profile already exists in cache, the cached instance is returned.
|
||||||
|
*
|
||||||
|
* @param input the input stream to read from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the stream.
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the stream does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(InputStream)
|
||||||
|
*/
|
||||||
|
public static ICC_Profile readProfile(final InputStream input) throws IOException {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
DataInputStream dataInput = new DataInputStream(input);
|
||||||
|
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
|
||||||
|
try {
|
||||||
|
dataInput.readFully(header);
|
||||||
|
|
||||||
|
int size = validateHeaderAndGetSize(header);
|
||||||
|
byte[] data = Arrays.copyOf(header, size);
|
||||||
|
dataInput.readFully(data, header.length, size - header.length);
|
||||||
|
|
||||||
|
return createProfile(data);
|
||||||
|
}
|
||||||
|
catch (EOFException e) {
|
||||||
|
throw new IllegalArgumentException("Truncated ICC Profile data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ICC Profile from the given byte array, as-is, with no validation.
|
||||||
|
*
|
||||||
|
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
|
||||||
|
* except that extraneous bytes at the end of the array is ignored.
|
||||||
|
*
|
||||||
|
* @param input the byte array to create a profile from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object created from the byte array
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the byte array does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(byte[])
|
||||||
|
* @see #createProfile(byte[])
|
||||||
|
*/
|
||||||
|
public static ICC_Profile createProfileRaw(final byte[] input) {
|
||||||
|
int size = validateHeaderAndGetSize(input);
|
||||||
|
|
||||||
|
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
|
||||||
|
// does not discard extra bytes at the end. We'll chop them off here for convenience
|
||||||
|
return ICC_Profile.getInstance(limit(input, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an ICC Profile from the given byte array, with extra validation.
|
||||||
|
* Extraneous bytes at the end of the array are ignored.
|
||||||
|
*
|
||||||
|
* If a matching profile already exists in cache, the cached instance is returned.
|
||||||
|
*
|
||||||
|
* @param input the byte array to create a profile from, may not be {@code null}
|
||||||
|
* @return an {@code ICC_Profile} object created from the byte array
|
||||||
|
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||||
|
* or the byte array does not contain valid ICC Profile data.
|
||||||
|
* @see ICC_Profile#getInstance(byte[])
|
||||||
|
*/
|
||||||
|
public static ICC_Profile createProfile(final byte[] input) {
|
||||||
|
int size = validateAndGetSize(input);
|
||||||
|
|
||||||
|
// Look up in cache before returning, these are already validated
|
||||||
|
byte[] profileHeader = getProfileHeaderWithProfileId(input);
|
||||||
|
ICC_Profile internal = getInternalProfile(profileHeader);
|
||||||
|
if (internal != null) {
|
||||||
|
return internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
|
||||||
|
if (cached != null) {
|
||||||
|
return cached.getProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
|
||||||
|
|
||||||
|
// We'll validate & cache by creating a color space and returning its profile...
|
||||||
|
// TODO: Rewrite with separate cache for profiles...
|
||||||
|
return ColorSpaces.createColorSpace(profile).getProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] limit(byte[] input, int size) {
|
||||||
|
return input.length == size ? input : Arrays.copyOf(input, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateAndGetSize(byte[] input) {
|
||||||
|
int size = validateHeaderAndGetSize(input);
|
||||||
|
|
||||||
|
if (size < 0 || size > input.length) {
|
||||||
|
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateHeaderAndGetSize(byte[] input) {
|
||||||
|
Validate.notNull(input, "input");
|
||||||
|
|
||||||
|
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
|
||||||
|
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = intBigEndian(input, ICC_Profile.icHdrSize);
|
||||||
|
|
||||||
|
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
|
||||||
|
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
|
||||||
|
int profileCSType = getCsType(profileHeader);
|
||||||
|
|
||||||
|
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||||
|
}
|
||||||
|
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||||
|
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int intBigEndian(byte[] data, int index) {
|
||||||
|
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getCsType(byte[] profileHeader) {
|
||||||
|
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
|
||||||
|
|
||||||
|
switch (csSig) {
|
||||||
|
case ICC_Profile.icSigXYZData:
|
||||||
|
return ColorSpace.TYPE_XYZ;
|
||||||
|
case ICC_Profile.icSigLabData:
|
||||||
|
return ColorSpace.TYPE_Lab;
|
||||||
|
case ICC_Profile.icSigLuvData:
|
||||||
|
return ColorSpace.TYPE_Luv;
|
||||||
|
case ICC_Profile.icSigYCbCrData:
|
||||||
|
return ColorSpace.TYPE_YCbCr;
|
||||||
|
case ICC_Profile.icSigYxyData:
|
||||||
|
return ColorSpace.TYPE_Yxy;
|
||||||
|
case ICC_Profile.icSigRgbData:
|
||||||
|
return ColorSpace.TYPE_RGB;
|
||||||
|
case ICC_Profile.icSigGrayData:
|
||||||
|
return ColorSpace.TYPE_GRAY;
|
||||||
|
case ICC_Profile.icSigHsvData:
|
||||||
|
return ColorSpace.TYPE_HSV;
|
||||||
|
case ICC_Profile.icSigHlsData:
|
||||||
|
return ColorSpace.TYPE_HLS;
|
||||||
|
case ICC_Profile.icSigCmykData:
|
||||||
|
return ColorSpace.TYPE_CMYK;
|
||||||
|
// Note: There is no TYPE_* 10...
|
||||||
|
case ICC_Profile.icSigCmyData:
|
||||||
|
return ColorSpace.TYPE_CMY;
|
||||||
|
case ICC_Profile.icSigSpace2CLR:
|
||||||
|
return ColorSpace.TYPE_2CLR;
|
||||||
|
case ICC_Profile.icSigSpace3CLR:
|
||||||
|
return ColorSpace.TYPE_3CLR;
|
||||||
|
case ICC_Profile.icSigSpace4CLR:
|
||||||
|
return ColorSpace.TYPE_4CLR;
|
||||||
|
case ICC_Profile.icSigSpace5CLR:
|
||||||
|
return ColorSpace.TYPE_5CLR;
|
||||||
|
case ICC_Profile.icSigSpace6CLR:
|
||||||
|
return ColorSpace.TYPE_6CLR;
|
||||||
|
case ICC_Profile.icSigSpace7CLR:
|
||||||
|
return ColorSpace.TYPE_7CLR;
|
||||||
|
case ICC_Profile.icSigSpace8CLR:
|
||||||
|
return ColorSpace.TYPE_8CLR;
|
||||||
|
case ICC_Profile.icSigSpace9CLR:
|
||||||
|
return ColorSpace.TYPE_9CLR;
|
||||||
|
case ICC_Profile.icSigSpaceACLR:
|
||||||
|
return ColorSpace.TYPE_ACLR;
|
||||||
|
case ICC_Profile.icSigSpaceBCLR:
|
||||||
|
return ColorSpace.TYPE_BCLR;
|
||||||
|
case ICC_Profile.icSigSpaceCCLR:
|
||||||
|
return ColorSpace.TYPE_CCLR;
|
||||||
|
case ICC_Profile.icSigSpaceDCLR:
|
||||||
|
return ColorSpace.TYPE_DCLR;
|
||||||
|
case ICC_Profile.icSigSpaceECLR:
|
||||||
|
return ColorSpace.TYPE_ECLR;
|
||||||
|
case ICC_Profile.icSigSpaceFCLR:
|
||||||
|
return ColorSpace.TYPE_FCLR;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
|
||||||
|
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||||
|
|
||||||
|
if (stream != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ICC_Profile.getInstance(stream);
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||||
|
if (profilePath != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Loading profile from: " + profilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ICC_Profile.getInstance(profilePath);
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fixProfile(ICC_Profile profile) {
|
||||||
|
profileCleaner.fixProfile(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean validationAltersProfileHeader() {
|
||||||
|
return profileCleaner.validationAltersProfileHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
|
||||||
|
static class sRGB {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CIEXYZ {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class PYCC {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GRAY {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class LINEAR_RGB {
|
||||||
|
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Profiles {
|
||||||
|
// TODO: Honour java.iccprofile.path property?
|
||||||
|
private static final Properties PROFILES = loadProfiles();
|
||||||
|
|
||||||
|
private static Properties loadProfiles() {
|
||||||
|
Properties systemDefaults;
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||||
|
System.err.printf(
|
||||||
|
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||||
|
ignore.getMessage()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
systemDefaults = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create map with defaults and add user overrides if any
|
||||||
|
Properties profiles = new Properties(systemDefaults);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Properties userOverrides = SystemUtil.loadProperties(
|
||||||
|
ColorSpaces.class,
|
||||||
|
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||||
|
);
|
||||||
|
profiles.putAll(userOverrides);
|
||||||
|
}
|
||||||
|
catch (SecurityException | IOException ignore) {
|
||||||
|
// Most likely, this file won't be there
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("User ICC profiles: " + profiles);
|
||||||
|
System.out.println("System ICC profiles : " + systemDefaults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getPath(final String profileName) {
|
||||||
|
return PROFILES.getProperty(profileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
-244
@@ -30,23 +30,17 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.color;
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
import com.twelvemonkeys.io.FileUtil;
|
|
||||||
import com.twelvemonkeys.lang.Platform;
|
|
||||||
import com.twelvemonkeys.lang.SystemUtil;
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import com.twelvemonkeys.util.LRUHashMap;
|
import com.twelvemonkeys.util.LRUHashMap;
|
||||||
|
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.ColorSpace;
|
||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
|
||||||
|
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper class for working with ICC color profiles and color spaces.
|
* A helper class for working with ICC color profiles and color spaces.
|
||||||
@@ -83,9 +77,6 @@ public final class ColorSpaces {
|
|||||||
|
|
||||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||||
|
|
||||||
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
|
|
||||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
|
||||||
|
|
||||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||||
|
|
||||||
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
||||||
@@ -96,24 +87,17 @@ public final class ColorSpaces {
|
|||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static final int CS_GENERIC_CMYK = 5001;
|
public static final int CS_GENERIC_CMYK = 5001;
|
||||||
|
|
||||||
|
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
|
||||||
// Weak references to hold the color spaces while cached
|
// Weak references to hold the color spaces while cached
|
||||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||||
|
|
||||||
// Cache for the latest used color spaces
|
// Cache for the latest used color spaces
|
||||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
|
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
// In case we didn't activate through SPI already
|
||||||
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
|
ProfileDeferralActivator.activateProfiles();
|
||||||
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
|
|
||||||
}
|
|
||||||
catch (Throwable disasters) {
|
|
||||||
System.err.println("ICC Color Profile not properly activated due to the exception below.");
|
|
||||||
System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
|
|
||||||
|
|
||||||
disasters.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ColorSpaces() {}
|
private ColorSpaces() {}
|
||||||
@@ -134,7 +118,7 @@ public final class ColorSpaces {
|
|||||||
Validate.notNull(profile, "profile");
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
// Fix profile before lookup/create
|
// Fix profile before lookup/create
|
||||||
profileCleaner.fixProfile(profile);
|
fixProfile(profile);
|
||||||
|
|
||||||
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
||||||
|
|
||||||
@@ -146,53 +130,20 @@ public final class ColorSpaces {
|
|||||||
return getCachedOrCreateCS(profile, profileHeader);
|
return getCachedOrCreateCS(profile, profileHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||||
// Get *entire profile data*... :-/
|
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||||
byte[] data = profile.getData();
|
|
||||||
|
|
||||||
// Clear out preferred CMM, platform & creator, as these does not affect the profile in any way
|
|
||||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
|
||||||
// - KCMS keeps the values in the file...
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
|
||||||
// + Clear out rendering intent, as this may be updated by application
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
|
||||||
|
|
||||||
// Clear out any existing MD5, as it is no longer correct
|
|
||||||
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
|
||||||
|
|
||||||
// Generate new MD5 and store in header
|
|
||||||
byte[] md5 = computeMD5(data);
|
|
||||||
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
|
|
||||||
|
|
||||||
// ICC profile header is the first 128 bytes
|
|
||||||
return Arrays.copyOf(data, 128);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] computeMD5(byte[] data) {
|
|
||||||
try {
|
|
||||||
return MessageDigest.getInstance("MD5").digest(data);
|
|
||||||
}
|
|
||||||
catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
|
||||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
|
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
|
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
|
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
|
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||||
}
|
}
|
||||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
|
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,103 +154,67 @@ public final class ColorSpaces {
|
|||||||
Key key = new Key(profileHeader);
|
Key key = new Key(profileHeader);
|
||||||
|
|
||||||
synchronized (cache) {
|
synchronized (cache) {
|
||||||
ICC_ColorSpace cs = cache.get(key);
|
ICC_ColorSpace cs = getCachedCS(key);
|
||||||
|
|
||||||
if (cs == null) {
|
if (cs == null) {
|
||||||
cs = new ICC_ColorSpace(profile);
|
cs = new ICC_ColorSpace(profile);
|
||||||
|
|
||||||
validateColorSpace(cs);
|
validateColorSpace(cs);
|
||||||
|
|
||||||
// On LCMS, validation *alters* the profile header, need to re-generate key
|
|
||||||
key = profileCleaner.validationAltersProfileHeader()
|
|
||||||
? new Key(getProfileHeaderWithProfileId(cs.getProfile()))
|
|
||||||
: key;
|
|
||||||
|
|
||||||
cache.put(key, cs);
|
cache.put(key, cs);
|
||||||
|
|
||||||
|
// On LCMS, validation *alters* the profile header, need to re-generate key
|
||||||
|
if (ColorProfiles.validationAltersProfileHeader()) {
|
||||||
|
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cs;
|
return cs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateColorSpace(final ICC_ColorSpace cs) {
|
private static ICC_ColorSpace getCachedCS(Key profileKey) {
|
||||||
|
synchronized (cache) {
|
||||||
|
return cache.get(profileKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
|
||||||
|
return getCachedCS(new Key(profileHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||||
// Validate the color space, to avoid caching bad profiles/color spaces
|
// Validate the color space, to avoid caching bad profiles/color spaces
|
||||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||||
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
||||||
|
|
||||||
// This breaks *some times* after validation of bad profiles,
|
// This breaks *sometimes* after validation of bad profiles,
|
||||||
// we'll let it blow up early in this case
|
// we'll let it blow up early in this case
|
||||||
cs.getProfile().getData();
|
cs.getProfile().getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
|
||||||
*
|
|
||||||
* @param profile the ICC profile to test. May not be {@code null}.
|
|
||||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
*
|
|
||||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||||
Validate.notNull(profile, "profile");
|
return ColorProfiles.isCS_sRGB(profile);
|
||||||
|
|
||||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
|
||||||
* <p>
|
|
||||||
* <em>
|
|
||||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
|
||||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
|
||||||
* </em>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param profile the ICC color profile. May not be {@code null}.
|
|
||||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
*/
|
*/
|
||||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
@Deprecated
|
||||||
Validate.notNull(profile, "profile");
|
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||||
|
return ColorProfiles.isCS_GRAY(profile);
|
||||||
// NOTE:
|
|
||||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
|
||||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
|
||||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
|
||||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
|
||||||
// (that is 1 single bit of difference right there.. ;-)
|
|
||||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
|
||||||
|
|
||||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
|
||||||
// except the getRenderingIntent method is package protected in java.awt.color
|
|
||||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
|
||||||
|
|
||||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
|
||||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is valid.
|
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
|
||||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
|
||||||
* <p>
|
|
||||||
* <em>
|
|
||||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
|
||||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
|
||||||
* </em>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param profile the ICC color profile. May not be {@code null}.
|
|
||||||
* @return {@code profile} if valid.
|
|
||||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
|
||||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||||
// Fix profile before validation
|
return ColorProfiles.validateProfile(profile);
|
||||||
profileCleaner.fixProfile(profile);
|
|
||||||
validateColorSpace(new ICC_ColorSpace(profile));
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -382,50 +297,6 @@ public final class ColorSpaces {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
|
|
||||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
|
||||||
|
|
||||||
if (stream != null) {
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return ICC_Profile.getInstance(stream);
|
|
||||||
}
|
|
||||||
catch (IOException ignore) {
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
FileUtil.close(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICC_Profile readProfileFromPath(final String profilePath) {
|
|
||||||
if (profilePath != null) {
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("Loading profile from: " + profilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return ICC_Profile.getInstance(profilePath);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Key {
|
private static final class Key {
|
||||||
private final byte[] data;
|
private final byte[] data;
|
||||||
|
|
||||||
@@ -448,78 +319,4 @@ public final class ColorSpaces {
|
|||||||
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init
|
|
||||||
private static class sRGB {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CIEXYZ {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PYCC {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GRAY {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LINEAR_RGB {
|
|
||||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Profiles {
|
|
||||||
// TODO: Honour java.iccprofile.path property?
|
|
||||||
private static final Properties PROFILES = loadProfiles();
|
|
||||||
|
|
||||||
private static Properties loadProfiles() {
|
|
||||||
Properties systemDefaults;
|
|
||||||
|
|
||||||
try {
|
|
||||||
systemDefaults = SystemUtil.loadProperties(
|
|
||||||
ColorSpaces.class,
|
|
||||||
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
System.err.printf(
|
|
||||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
|
||||||
ignore.getMessage()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
ignore.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
systemDefaults = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create map with defaults and add user overrides if any
|
|
||||||
Properties profiles = new Properties(systemDefaults);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Properties userOverrides = SystemUtil.loadProperties(
|
|
||||||
ColorSpaces.class,
|
|
||||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
|
||||||
);
|
|
||||||
profiles.putAll(userOverrides);
|
|
||||||
}
|
|
||||||
catch (SecurityException | IOException ignore) {
|
|
||||||
// Most likely, this file won't be there
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
|
||||||
System.out.println("User ICC profiles: " + profiles);
|
|
||||||
System.out.println("System ICC profiles : " + systemDefaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return profiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getPath(final String profileName) {
|
|
||||||
return PROFILES.getProperty(profileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
// Our IndexColorModel delegate
|
// Our IndexColorModel delegate
|
||||||
private final IndexColorModel icm;
|
private final IndexColorModel icm;
|
||||||
|
|
||||||
|
private final int extraSamples;
|
||||||
private final int samples;
|
private final int samples;
|
||||||
private final boolean hasAlpha;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
||||||
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.icm = icm;
|
this.icm = icm;
|
||||||
|
this.extraSamples = extraSamples;
|
||||||
this.samples = 1 + extraSamples;
|
this.samples = 1 + extraSamples;
|
||||||
this.hasAlpha = hasAlpha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNumComponents() {
|
public int getNumComponents() {
|
||||||
return samples;
|
return getNumColorComponents() + extraSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getRed(final int pixel) {
|
public int getRed(final int pixel) {
|
||||||
return icm.getRed(pixel);
|
return icm.getRed(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getGreen(final int pixel) {
|
public int getGreen(final int pixel) {
|
||||||
return icm.getGreen(pixel);
|
return icm.getGreen(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getBlue(final int pixel) {
|
public int getBlue(final int pixel) {
|
||||||
return icm.getBlue(pixel);
|
return icm.getBlue(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getAlpha(final int pixel) {
|
public int getAlpha(final int pixel) {
|
||||||
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
|
return hasAlpha() ? (int) ((((float) pixel) / ((1 << getComponentSize(3)) - 1)) * 255.0f + 0.5f) : 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSample(final Object inData, final int index) {
|
private int getSample(final Object inData, final int index) {
|
||||||
@@ -120,15 +120,15 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
|
|
||||||
switch (transferType) {
|
switch (transferType) {
|
||||||
case DataBuffer.TYPE_BYTE:
|
case DataBuffer.TYPE_BYTE:
|
||||||
byte bdata[] = (byte[]) inData;
|
byte[] bdata = (byte[]) inData;
|
||||||
pixel = bdata[index] & 0xff;
|
pixel = bdata[index] & 0xff;
|
||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_USHORT:
|
case DataBuffer.TYPE_USHORT:
|
||||||
short sdata[] = (short[]) inData;
|
short[] sdata = (short[]) inData;
|
||||||
pixel = sdata[index] & 0xffff;
|
pixel = sdata[index] & 0xffff;
|
||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_INT:
|
case DataBuffer.TYPE_INT:
|
||||||
int idata[] = (int[]) inData;
|
int[] idata = (int[]) inData;
|
||||||
pixel = idata[index];
|
pixel = idata[index];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -139,27 +139,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getRed(final Object inData) {
|
public int getRed(final Object inData) {
|
||||||
return getRed(getSample(inData, 0));
|
return getRed(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getGreen(final Object inData) {
|
public int getGreen(final Object inData) {
|
||||||
return getGreen(getSample(inData, 0));
|
return getGreen(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getBlue(final Object inData) {
|
public int getBlue(final Object inData) {
|
||||||
return getBlue(getSample(inData, 0));
|
return getBlue(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getAlpha(final Object inData) {
|
public int getAlpha(final Object inData) {
|
||||||
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
|
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
|
public SampleModel createCompatibleSampleModel(final int w, final int h) {
|
||||||
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
|
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,17 +174,17 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCompatibleSampleModel(final SampleModel sm) {
|
public boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||||
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
public WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||||
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCompatibleRaster(final Raster raster) {
|
public boolean isCompatibleRaster(final Raster raster) {
|
||||||
int size = raster.getSampleModel().getSampleSize(0);
|
int size = raster.getSampleModel().getSampleSize(0);
|
||||||
return ((raster.getTransferType() == transferType) &&
|
return ((raster.getTransferType() == transferType) &&
|
||||||
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021, 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 of the copyright holder 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 HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.color;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class exists to force early invocation of {@code ProfileDeferralMgr.activateProfiles()},
|
||||||
|
* in an attempt to avoid JDK-6986863 and related bugs in Java < 17.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @see <a href="https://bugs.openjdk.java.net/browse/JDK-6986863">JDK-6986863</a>
|
||||||
|
*/
|
||||||
|
final class ProfileDeferralActivator {
|
||||||
|
|
||||||
|
static {
|
||||||
|
activateProfilesInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void activateProfilesInternal() {
|
||||||
|
try {
|
||||||
|
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863 and friends.
|
||||||
|
// Relies on static initializer in ColorConvertOp to actually invoke ProfileDeferralMgr.activateProfiles()
|
||||||
|
Class.forName("java.awt.image.ColorConvertOp");
|
||||||
|
}
|
||||||
|
catch (Throwable disasters) {
|
||||||
|
System.err.println("ProfileDeferralMgr.activateProfiles() failed. ICC Color Profiles may not work properly, see stack trace below.");
|
||||||
|
System.err.println("For more information, see https://bugs.openjdk.java.net/browse/JDK-6986863");
|
||||||
|
System.err.println("Please upgrade to Java 17 or later where this bug is fixed, or ask your JRE provider to backport the fix.");
|
||||||
|
System.err.println();
|
||||||
|
System.err.println("If you can't update to Java 17, a possible workaround is to add");
|
||||||
|
System.err.println("\tClass.forName(\"java.awt.image.ColorConvertOp\");");
|
||||||
|
System.err.println("*early* in your application startup code, to force profile activation before profiles are accessed.");
|
||||||
|
System.err.println();
|
||||||
|
|
||||||
|
disasters.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void activateProfiles() {
|
||||||
|
// This method exists for other classes in the package to
|
||||||
|
// ensure this class' static initializer is run.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not a service provider, but exploits the SPI mechanism as a hook to force early profile activation.
|
||||||
|
*/
|
||||||
|
public static final class Spi extends ImageInputStreamSpi {
|
||||||
|
@Override public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||||
|
activateProfiles();
|
||||||
|
|
||||||
|
deregisterProvider(registry, this, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String getDescription(Locale locale) {
|
||||||
|
return getClass().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -61,7 +61,7 @@ public final class UInt32ColorModel extends ComponentColorModel {
|
|||||||
// This class only supports DataBuffer.TYPE_INT, cast is safe
|
// This class only supports DataBuffer.TYPE_INT, cast is safe
|
||||||
int[] ipixel = (int[]) pixel;
|
int[] ipixel = (int[]) pixel;
|
||||||
for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
|
for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
|
||||||
normComponents[nc] = ((float) (ipixel[c] & 0xffffffffl)) / ((float) ((1l << getComponentSize(c)) - 1));
|
normComponents[nc] = ((float) (ipixel[c] & 0xFFFFFFFFL)) / ((float) ((1L << getComponentSize(c)) - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
int numColorComponents = getNumColorComponents();
|
int numColorComponents = getNumColorComponents();
|
||||||
|
|||||||
+87
-31
@@ -45,36 +45,82 @@ public final class YCbCrConverter {
|
|||||||
private final static int CENTERJSAMPLE = 128;
|
private final static int CENTERJSAMPLE = 128;
|
||||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||||
|
|
||||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
private final static class JPEG {
|
||||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes tables for YCC->RGB color space conversion.
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
*/
|
*/
|
||||||
private static void buildYCCtoRGBtable() {
|
private static void buildYCCtoRGBtable() {
|
||||||
if (ColorSpaces.DEBUG) {
|
if (ColorSpaces.DEBUG) {
|
||||||
System.err.println("Building YCC conversion table");
|
System.err.println("Building JPEG YCbCr conversion table");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||||
|
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||||
|
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||||
|
// Cr=>R value is nearest int to 1.40200 * x
|
||||||
|
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cb=>B value is nearest int to 1.77200 * x
|
||||||
|
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cr=>G value is scaled-up -0.71414 * x
|
||||||
|
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||||
|
// Cb=>G value is scaled-up -0.34414 * x
|
||||||
|
// We also add in ONE_HALF so that need not do it in inner loop
|
||||||
|
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
static {
|
||||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
buildYCCtoRGBtable();
|
||||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
|
||||||
// Cr=>R value is nearest int to 1.40200 * x
|
|
||||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
|
||||||
// Cb=>B value is nearest int to 1.77200 * x
|
|
||||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
|
||||||
// Cr=>G value is scaled-up -0.71414 * x
|
|
||||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
|
||||||
// Cb=>G value is scaled-up -0.34414 * x
|
|
||||||
// We also add in ONE_HALF so that need not do it in inner loop
|
|
||||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
private final static class ITU_R_601 {
|
||||||
buildYCCtoRGBtable();
|
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Y_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
|
*/
|
||||||
|
private static void buildYCCtoRGBtable() {
|
||||||
|
if (ColorSpaces.DEBUG) {
|
||||||
|
System.err.println("Building ITU-R REC.601 YCbCr conversion table");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||||
|
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||||
|
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||||
|
|
||||||
|
// Y'CbCr to RGB conversion, using values from BT.601 specification:
|
||||||
|
// R = 1.16438 * (Y'-16) + 1.59603 * (Cr-128)
|
||||||
|
// G = 1.16438 * (Y'-16) - 0.39176 * (Cb-128) - 0.81297 * (Cr-128)
|
||||||
|
// B = 1.16438 * (Y'-16) + 2.01723 * (Cb-128)
|
||||||
|
|
||||||
|
// Cr=>R value is nearest int to 1.59603 * x
|
||||||
|
Cr_R_LUT[i] = ((int) (1.59603 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cb=>B value is nearest int to 2.01723 * x
|
||||||
|
Cb_B_LUT[i] = ((int) (2.01723 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cr=>G value is scaled-up -0.81297 * x
|
||||||
|
Cr_G_LUT[i] = -(int) (0.81297 * (1 << SCALEBITS) + 0.5) * x;
|
||||||
|
// Cb=>G value is scaled-up -0.39176 * x
|
||||||
|
// We also add in ONE_HALF so that need not do it in inner loop
|
||||||
|
Cb_G_LUT[i] = -(int) ((0.39176) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||||
|
|
||||||
|
// Y`=>RGB
|
||||||
|
Y_LUT[i] = ((int) (1.16438 * (1 << SCALEBITS) + 0.5) * (i - 16) + ONE_HALF) >> SCALEBITS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
buildYCCtoRGBtable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
||||||
@@ -108,17 +154,27 @@ public final class YCbCrConverter {
|
|||||||
rgb[offset + 1] = clamp(green);
|
rgb[offset + 1] = clamp(green);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
public static void convertJPEGYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||||
int y = yCbCr[offset] & 0xff;
|
int y = yCbCr[offset ] & 0xff;
|
||||||
int cr = yCbCr[offset + 2] & 0xff;
|
|
||||||
int cb = yCbCr[offset + 1] & 0xff;
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
|
|
||||||
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
|
rgb[offset ] = clamp(y + JPEG.Cr_R_LUT[cr]);
|
||||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
rgb[offset + 1] = clamp(y + (JPEG.Cb_G_LUT[cb] + JPEG.Cr_G_LUT[cr] >> SCALEBITS));
|
||||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
rgb[offset + 2] = clamp(y + JPEG.Cb_B_LUT[cb]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte clamp(int val) {
|
public static void convertRec601YCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||||
|
int y = yCbCr[offset ] & 0xff;
|
||||||
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
|
|
||||||
|
rgb[offset ] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cr_R_LUT[cr]);
|
||||||
|
rgb[offset + 1] = clamp(ITU_R_601.Y_LUT[y] + (ITU_R_601.Cr_G_LUT[cr] + ITU_R_601.Cb_G_LUT[cb] >> SCALEBITS));
|
||||||
|
rgb[offset + 2] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cb_B_LUT[cb]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte clamp(final int val) {
|
||||||
return (byte) Math.max(0, Math.min(255, val));
|
return (byte) Math.max(0, Math.min(255, val));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -49,10 +49,10 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
|||||||
private final String[] mimeTypes;
|
private final String[] mimeTypes;
|
||||||
private final String readerClassName;
|
private final String readerClassName;
|
||||||
private final String[] readerSpiClassNames;
|
private final String[] readerSpiClassNames;
|
||||||
private final Class[] inputTypes = new Class[] {ImageInputStream.class};
|
private final Class<?>[] inputTypes = new Class<?>[] {ImageInputStream.class};
|
||||||
private final String writerClassName;
|
private final String writerClassName;
|
||||||
private final String[] writerSpiClassNames;
|
private final String[] writerSpiClassNames;
|
||||||
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
|
private final Class<?>[] outputTypes = new Class<?>[] {ImageOutputStream.class};
|
||||||
private final boolean supportsStandardStreamMetadata;
|
private final boolean supportsStandardStreamMetadata;
|
||||||
private final String nativeStreamMetadataFormatName;
|
private final String nativeStreamMetadataFormatName;
|
||||||
private final String nativeStreamMetadataFormatClassName;
|
private final String nativeStreamMetadataFormatClassName;
|
||||||
@@ -80,7 +80,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
|||||||
final String writerClassName,
|
final String writerClassName,
|
||||||
final String[] writerSpiClassNames,
|
final String[] writerSpiClassNames,
|
||||||
final boolean supportsStandardStreamMetadata,
|
final boolean supportsStandardStreamMetadata,
|
||||||
final String nativeStreameMetadataFormatName,
|
final String nativeStreamMetadataFormatName,
|
||||||
final String nativeStreamMetadataFormatClassName,
|
final String nativeStreamMetadataFormatClassName,
|
||||||
final String[] extraStreamMetadataFormatNames,
|
final String[] extraStreamMetadataFormatNames,
|
||||||
final String[] extraStreamMetadataFormatClassNames,
|
final String[] extraStreamMetadataFormatClassNames,
|
||||||
@@ -99,7 +99,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
|||||||
this.writerClassName = writerClassName;
|
this.writerClassName = writerClassName;
|
||||||
this.writerSpiClassNames = writerSpiClassNames;
|
this.writerSpiClassNames = writerSpiClassNames;
|
||||||
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
|
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
|
||||||
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
|
this.nativeStreamMetadataFormatName = nativeStreamMetadataFormatName;
|
||||||
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
||||||
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
||||||
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
||||||
|
|||||||
+348
@@ -0,0 +1,348 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
|
||||||
|
* and provides greatly improved performance
|
||||||
|
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
|
||||||
|
* for shorter reads, like single byte or bit reads.
|
||||||
|
*/
|
||||||
|
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
|
||||||
|
private static final Closeable CLOSEABLE_STUB = new Closeable() {
|
||||||
|
@Override public void close() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
|
||||||
|
private byte[] buffer = byteBuffer.array();
|
||||||
|
private int bufferPos;
|
||||||
|
private int bufferLimit;
|
||||||
|
|
||||||
|
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||||
|
private final byte[] integralCacheArray = integralCache.array();
|
||||||
|
|
||||||
|
private SeekableByteChannel channel;
|
||||||
|
private Closeable closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
|
||||||
|
*
|
||||||
|
* @param file a {@code File} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final File file) throws IOException {
|
||||||
|
this(notNull(file, "file").toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
|
||||||
|
*
|
||||||
|
* @param file a {@code Path} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final Path file) throws IOException {
|
||||||
|
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
|
||||||
|
*
|
||||||
|
* @param file a {@code RandomAccessFile} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final RandomAccessFile file) {
|
||||||
|
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
|
||||||
|
this(notNull(file, "file").getChannel(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param inputStream a {@code FileInputStream} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
|
||||||
|
this(notNull(inputStream, "inputStream").getChannel(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param channel a {@code SeekableByteChannel} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
|
||||||
|
this(notNull(channel, "channel"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will close the {@code Cache}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param cache a {@code SeekableByteChannel} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||||
|
*/
|
||||||
|
BufferedChannelImageInputStream(final Cache cache) {
|
||||||
|
this(notNull(cache, "cache"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private boolean fillBuffer() throws IOException {
|
||||||
|
byteBuffer.rewind();
|
||||||
|
int length = channel.read(byteBuffer);
|
||||||
|
bufferPos = 0;
|
||||||
|
bufferLimit = max(length, 0);
|
||||||
|
|
||||||
|
return bufferLimit > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean bufferEmpty() {
|
||||||
|
return bufferPos >= bufferLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByteOrder(ByteOrder byteOrder) {
|
||||||
|
super.setByteOrder(byteOrder);
|
||||||
|
integralCache.order(byteOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (bufferEmpty() && !fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
streamPos++;
|
||||||
|
|
||||||
|
return buffer[bufferPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (bufferEmpty()) {
|
||||||
|
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||||
|
if (length >= buffer.length) {
|
||||||
|
return readDirect(bytes, offset, length);
|
||||||
|
}
|
||||||
|
else if (!fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fromBuffer = readBuffered(bytes, offset, length);
|
||||||
|
|
||||||
|
if (length > fromBuffer) {
|
||||||
|
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||||
|
// we'll read as much as possible from the buffer, and the rest directly after
|
||||||
|
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||||
|
bufferLimit = 0;
|
||||||
|
|
||||||
|
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
|
||||||
|
int read = 0;
|
||||||
|
while (wrapped.hasRemaining()) {
|
||||||
|
int count = channel.read(wrapped);
|
||||||
|
if (count == -1) {
|
||||||
|
if (read == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
read += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||||
|
// Read as much as possible from buffer
|
||||||
|
int available = Math.min(bufferLimit - bufferPos, length);
|
||||||
|
|
||||||
|
if (available > 0) {
|
||||||
|
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||||
|
bufferPos += available;
|
||||||
|
streamPos += available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long length() {
|
||||||
|
// WTF?! This method is allowed to throw IOException in the interface...
|
||||||
|
try {
|
||||||
|
checkClosed();
|
||||||
|
return channel.size();
|
||||||
|
}
|
||||||
|
catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
buffer = null;
|
||||||
|
byteBuffer = null;
|
||||||
|
|
||||||
|
channel = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
closeable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to override the readShort(), readInt() and readLong() methods,
|
||||||
|
// because the implementations in ImageInputStreamImpl expects the
|
||||||
|
// read(byte[], int, int) to always read the expected number of bytes,
|
||||||
|
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||||
|
// random places...
|
||||||
|
// Notes:
|
||||||
|
// * readUnsignedXx() is covered by their signed counterparts
|
||||||
|
// * readChar() is covered by readShort()
|
||||||
|
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||||
|
// respectively.
|
||||||
|
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readShort() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 2);
|
||||||
|
|
||||||
|
return integralCache.getShort(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 4);
|
||||||
|
|
||||||
|
return integralCache.getInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 8);
|
||||||
|
|
||||||
|
return integralCache.getLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (position < flushedPos) {
|
||||||
|
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (streamPos == position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized to not invalidate buffer if new position is within current buffer
|
||||||
|
long newBufferPos = bufferPos + position - streamPos;
|
||||||
|
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||||
|
bufferPos = (int) newBufferPos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Will invalidate buffer
|
||||||
|
bufferLimit = 0;
|
||||||
|
channel.position(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBefore(final long pos) throws IOException {
|
||||||
|
super.flushBefore(pos);
|
||||||
|
|
||||||
|
if (channel instanceof Cache) {
|
||||||
|
// In case of memory cache, free up memory
|
||||||
|
((Cache) channel).flushBefore(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user