Compare commits

...

448 Commits

Author SHA1 Message Date
Harald Kuhr 92ba91b412 #784 TIFF: No longer return incorrect standard image type for RGB with custom ICC profile
(cherry picked from commit b2f7cada21)
2023-07-19 13:12:51 +02:00
Harald Kuhr c8cb472407 #786 TIFF: No longer create custom Inflater, to avoid resource leak
(cherry picked from commit 2a4c152c3d)
2023-07-19 13:12:51 +02:00
dependabot[bot] 2f56650444 Bump mikepenz/action-junit-report in /.github/workflows
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 3.7.8 to 3.8.0.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/baaeba622e27b396105f35ec9ec4ee89ffcbd306...150e2f992e4fad1379da2056d1d1c279f520e058)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit d0a17ff3b3)
2023-07-19 13:12:50 +02:00
dependabot[bot] ff3d403002 Bump mikepenz/action-junit-report in /.github/workflows
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 3.7.7 to 3.7.8.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/c0e4b81aaa0067314a2d0d06e19b512c9d8af4f5...baaeba622e27b396105f35ec9ec4ee89ffcbd306)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 3018d2a342)
2023-07-19 13:12:50 +02:00
dependabot[bot] ea6070a94e Bump maven-shade-plugin from 3.4.1 to 3.5.0
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.4.1...maven-shade-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 60b7151eb6)
2023-07-19 13:12:50 +02:00
dependabot[bot] 2c7a1d1f91 Bump actions/checkout from 3.5.2 to 3.5.3 in /.github/workflows
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.2 to 3.5.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/8e5e7e5ab8b370d6c329ec480221332ada57f0ab...c85c95e3d7251135ab7dc9ce3241c5835cc595a9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 8c854f1e20)
2023-07-19 13:12:50 +02:00
dependabot[bot] 45ce7aabd8 Bump commons-io from 2.12.0 to 2.13.0
Bumps commons-io from 2.12.0 to 2.13.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit e8f1e80d4e)
2023-07-19 13:12:36 +02:00
dependabot[bot] a4d48116ee Bump maven-surefire-report-plugin from 3.1.0 to 3.1.2
Bumps [maven-surefire-report-plugin](https://github.com/apache/maven-surefire) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.0...surefire-3.1.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-report-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 164b8db988)
2023-07-19 13:12:33 +02:00
dependabot[bot] 87729d1558 Bump maven-surefire-plugin from 3.1.0 to 3.1.2
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.0...surefire-3.1.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 21feace385)
2023-07-19 13:12:32 +02:00
Harald Kuhr dc322d49dc #722 Fix WebP animation transparent frame issue
(cherry picked from commit ba1f754611)
2023-07-19 13:12:32 +02:00
Harald Kuhr e5a82216d2 #772 Fix WebP animation transparent frame issue
(cherry picked from commit 822b5da631)
2023-07-19 13:12:32 +02:00
dependabot[bot] 5f1e746411 Bump maven-release-plugin from 3.0.0 to 3.0.1
Bumps [maven-release-plugin](https://github.com/apache/maven-release) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/apache/maven-release/releases)
- [Commits](https://github.com/apache/maven-release/compare/maven-release-3.0.0...maven-release-3.0.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 9d50acd2fe)
2023-07-19 13:12:26 +02:00
Davide Tantillo 243edc581c PSD: Adding parsing for 'lsdk' (undocumented) additional layer information key that represents a 'nested section diverder setting'
(cherry picked from commit 20cd259abd)
2023-07-19 13:12:26 +02:00
Harald Kuhr f982484767 Manual mockito bump
(cherry picked from commit 2d8125e69c)
2023-07-19 13:12:17 +02:00
dependabot[bot] e2b18795ca Bump nexus-staging-maven-plugin from 1.6.8 to 1.6.13
Bumps nexus-staging-maven-plugin from 1.6.8 to 1.6.13.

---
updated-dependencies:
- dependency-name: org.sonatype.plugins:nexus-staging-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 72b9f19a51)
2023-07-19 13:12:11 +02:00
dependabot[bot] bf75cae596 Bump maven-scm-provider-gitexe from 1.11.2 to 2.0.1
Bumps maven-scm-provider-gitexe from 1.11.2 to 2.0.1.

---
updated-dependencies:
- dependency-name: org.apache.maven.scm:maven-scm-provider-gitexe
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 0083b8e77e)
2023-07-19 13:12:11 +02:00
dependabot[bot] b92611151c Bump maven-jar-plugin from 2.4 to 3.3.0
Bumps [maven-jar-plugin](https://github.com/apache/maven-jar-plugin) from 2.4 to 3.3.0.
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-2.4...maven-jar-plugin-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-jar-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 79982cd493)
2023-07-19 13:12:11 +02:00
dependabot[bot] c57ce3dcaf Bump maven-javadoc-plugin from 3.2.0 to 3.5.0
Bumps [maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.2.0 to 3.5.0.
- [Release notes](https://github.com/apache/maven-javadoc-plugin/releases)
- [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.2.0...maven-javadoc-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-javadoc-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit f0db338f3b)
2023-07-19 13:12:11 +02:00
Harald Kuhr 7565a8cbf1 More Dependabot PRs, please
(cherry picked from commit 9db4e0b3ed)
2023-07-19 13:12:10 +02:00
dependabot[bot] 2d25670a7e Bump maven-deploy-plugin from 3.0.0-M1 to 3.1.1
Bumps [maven-deploy-plugin](https://github.com/apache/maven-deploy-plugin) from 3.0.0-M1 to 3.1.1.
- [Release notes](https://github.com/apache/maven-deploy-plugin/releases)
- [Commits](https://github.com/apache/maven-deploy-plugin/compare/maven-deploy-plugin-3.0.0-M1...maven-deploy-plugin-3.1.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-deploy-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 15dc4b3852)
2023-07-19 13:12:03 +02:00
dependabot[bot] 3d0855c7c6 Bump commons-io from 2.11.0 to 2.12.0
Bumps commons-io from 2.11.0 to 2.12.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit a3534ecd59)
2023-07-19 13:12:03 +02:00
dependabot[bot] 43216c2045 Bump maven-shade-plugin from 3.2.2 to 3.4.1
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.2 to 3.4.1.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.2...maven-shade-plugin-3.4.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 7bb5fee23b)
2023-07-19 13:12:03 +02:00
dependabot[bot] 6152b2b57c Bump servlet-api from 2.4 to 2.5
Bumps servlet-api from 2.4 to 2.5.

---
updated-dependencies:
- dependency-name: javax.servlet:servlet-api
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 6cb7424bd0)
2023-07-19 13:12:03 +02:00
dependabot[bot] cbf60c191d Bump maven-gpg-plugin from 1.6 to 3.1.0
Bumps [maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 1.6 to 3.1.0.
- [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-1.6...maven-gpg-plugin-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-gpg-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 9aa04d311e)
2023-07-19 13:12:03 +02:00
dependabot[bot] 32a56cadab Bump mikepenz/action-junit-report in /.github/workflows
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 3.7.6 to 3.7.7.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/959aefb7f095e717eb407fe917238d61ca323ff3...c0e4b81aaa0067314a2d0d06e19b512c9d8af4f5)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 967e71dc92)
2023-07-19 13:12:03 +02:00
Harald Kuhr c796715f0a Dependabot workflow updates
(cherry picked from commit 628523ddc8)
2023-07-19 13:12:02 +02:00
Harald Kuhr 2494359f42 More lenient test, using dynamic local port.
(cherry picked from commit 783c28ae0e)
2023-07-19 13:12:02 +02:00
dependabot[bot] d1f65a2f70 Bump maven-resources-plugin from 3.2.0 to 3.3.1
Bumps [maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.2.0 to 3.3.1.
- [Release notes](https://github.com/apache/maven-resources-plugin/releases)
- [Commits](https://github.com/apache/maven-resources-plugin/compare/maven-resources-plugin-3.2.0...maven-resources-plugin-3.3.1)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-resources-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 3ce35e059c)
2023-07-19 13:11:55 +02:00
dependabot[bot] b7207f302e Bump maven-pmd-plugin from 3.14.0 to 3.21.0
Bumps [maven-pmd-plugin](https://github.com/apache/maven-pmd-plugin) from 3.14.0 to 3.21.0.
- [Release notes](https://github.com/apache/maven-pmd-plugin/releases)
- [Commits](https://github.com/apache/maven-pmd-plugin/compare/maven-pmd-plugin-3.14.0...maven-pmd-plugin-3.21.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-pmd-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 1d5359dd35)
2023-07-19 13:11:54 +02:00
dependabot[bot] 9c6d983129 Bump junit from 4.13.1 to 4.13.2
Bumps [junit](https://github.com/junit-team/junit4) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.13.1...r4.13.2)

---
updated-dependencies:
- dependency-name: junit:junit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 2699b75b79)
2023-07-19 13:11:54 +02:00
dependabot[bot] e989b4a204 Bump maven-checkstyle-plugin from 3.1.2 to 3.3.0
Bumps [maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) from 3.1.2 to 3.3.0.
- [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.1.2...maven-checkstyle-plugin-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 4e614dfc7e)
2023-07-19 13:11:54 +02:00
dependabot[bot] aa1568e1a4 Bump maven-help-plugin from 3.2.0 to 3.4.0
Bumps [maven-help-plugin](https://github.com/apache/maven-help-plugin) from 3.2.0 to 3.4.0.
- [Commits](https://github.com/apache/maven-help-plugin/compare/maven-help-plugin-3.2.0...maven-help-plugin-3.4.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-help-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 3a2efd9491)
2023-07-19 13:11:54 +02:00
Harald Kuhr 84a1800cd1 Update README.md
(cherry picked from commit c5dc2e4e53)
2023-07-19 13:11:45 +02:00
Harald Kuhr a78faf2b31 JDK 20 compliance
(cherry picked from commit 41460bd32a)
2023-07-19 13:11:45 +02:00
Harald Kuhr c5cb54e3e5 Use Maven in batch mode!
(cherry picked from commit 13b37b3839)
2023-07-19 13:11:45 +02:00
Harald Kuhr f492807e71 Remove transfer progress from Maven deploy output
(cherry picked from commit 6dd74070f4)
2023-07-19 13:11:45 +02:00
Harald Kuhr fe382ac89f Attempt to fix problem with upgraded maven source plugin, take 2
(cherry picked from commit 81b358b377)
2023-07-19 13:11:45 +02:00
Harald Kuhr 87927ce9f2 Attempt to fix problem with upgraded maven source plugin.
(cherry picked from commit 9715f4e74c)
2023-07-19 13:11:45 +02:00
Harald Kuhr 7a8cbc40f8 Stop dependabot causing double workflow runs.
(cherry picked from commit f74e8c8ba1)
2023-07-19 13:11:36 +02:00
dependabot[bot] 1aeabff1d9 Bump maven-surefire-report-plugin from 3.0.0-M5 to 3.1.0
Bumps [maven-surefire-report-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M5 to 3.1.0.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M5...surefire-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-report-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 0ae2c2f01d)
2023-07-19 13:11:36 +02:00
dependabot[bot] 6adb649816 Bump maven-release-plugin from 3.0.0-M4 to 3.0.0
Bumps [maven-release-plugin](https://github.com/apache/maven-release) from 3.0.0-M4 to 3.0.0.
- [Release notes](https://github.com/apache/maven-release/releases)
- [Commits](https://github.com/apache/maven-release/compare/maven-release-3.0.0-M4...maven-release-3.0.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-release-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 77c81a06bc)
2023-07-19 13:11:36 +02:00
dependabot[bot] afdbbecf70 Bump maven-compiler-plugin from 3.8.1 to 3.11.0
Bumps [maven-compiler-plugin](https://github.com/apache/maven-compiler-plugin) from 3.8.1 to 3.11.0.
- [Release notes](https://github.com/apache/maven-compiler-plugin/releases)
- [Commits](https://github.com/apache/maven-compiler-plugin/compare/maven-compiler-plugin-3.8.1...maven-compiler-plugin-3.11.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-compiler-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 2bbcd88798)
2023-07-19 13:11:36 +02:00
dependabot[bot] 412987120e Bump maven-source-plugin from 3.2.1 to 3.3.0
Bumps [maven-source-plugin](https://github.com/apache/maven-source-plugin) from 3.2.1 to 3.3.0.
- [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-3.2.1...maven-source-plugin-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 829fbe7547)
2023-07-19 13:11:36 +02:00
dependabot[bot] 628f2dd510 Bump maven-surefire-plugin from 3.0.0-M5 to 3.1.0
Bumps [maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.0.0-M5 to 3.1.0.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.0.0-M5...surefire-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 078425eed9)
2023-07-19 13:11:35 +02:00
Harald Kuhr 4c2399d246 Create dependabot.yml
(cherry picked from commit 8ddcbbd2b2)
2023-07-19 13:11:35 +02:00
Joyce 9c57fcb327 Hash pin ci.yml
(cherry picked from commit a4caac0c82)
2023-07-19 13:11:24 +02:00
Harald Kuhr 2ab998db03 Added logo to README.md
(cherry picked from commit 54c07b849c)
2023-07-19 13:11:23 +02:00
Harald Kuhr 3e27d2970b Reverted test, glossed over flakyness in library instead.
(cherry picked from commit c531d4f5d3)
2023-07-19 13:11:23 +02:00
Harald Kuhr 34ec884578 Fix flaky old test.
(cherry picked from commit aa2e8e5d7e)
2023-07-19 13:11:23 +02:00
Harald Kuhr beb88e2453 #744 TIFF: Re-complicated TIFF writing for the sake of performance...
(cherry picked from commit 76a35331b0)
2023-07-19 13:11:23 +02:00
Harald Kuhr 0b23644b8d #740 TIFF: Floating point predictor support.
(cherry picked from commit 6b3f1c6ee3)
2023-07-19 13:11:23 +02:00
Joyce e6c0f72cef Update SECURITY.md
(cherry picked from commit 4a8c3530f7)
2023-07-19 13:11:04 +02:00
Joyce 9b47c236a5 Update SECURITY.md to best effort
(cherry picked from commit e8996daa12)
2023-07-19 13:11:04 +02:00
Joyce 091319a599 Create SECURITY.md
(cherry picked from commit 9196e60c74)
2023-07-19 13:11:04 +02:00
Harald Kuhr 33eac436b9 WebP cleanup.
(cherry picked from commit eabb8fd02b)
2023-07-19 13:11:04 +02:00
Harald Kuhr f27b5700e8 WebP minor bugfix and optimization.
(cherry picked from commit 1794e336de)
2023-07-19 13:11:03 +02:00
Harald Kuhr 67b9e7df11 WebP cleanup
(cherry picked from commit ac7612b3df)
2023-07-19 13:11:03 +02:00
Harald Kuhr 4a51850e9b #738 PSD: No longer decompress PackBits across boundaries
(cherry picked from commit 606fd53823)
2023-07-19 13:11:03 +02:00
tc-wleite bf55209bf6 Avoid creating another temporary raster, filtering on the tempRaster.
(cherry picked from commit 34e8d88007)
2023-07-19 13:10:52 +02:00
tc-wleite 3cee4d0112 Remove the TODO comment.
(cherry picked from commit 9b727df901)
2023-07-19 13:10:51 +02:00
tc-wleite 50dabcd838 Use a static import.
(cherry picked from commit f1f98bb4a4)
2023-07-19 13:10:51 +02:00
tc-wleite c7c8e3372b Let copyIntoRasterWithParams() handle null param.
(cherry picked from commit b34b26e08c)
2023-07-19 13:10:51 +02:00
Wladimir Leite a085a454e0 Accept and handle null param in copyIntoRasterWithParams().
Co-authored-by: Harald Kuhr <harald.kuhr@gmail.com>
(cherry picked from commit 993e07ee34)
2023-07-19 13:10:50 +02:00
tc-wleite 5140846fb6 Replace the WebP to test alpha subsampling by a slightly smaller image.
(cherry picked from commit a377712bdb)
2023-07-19 13:10:50 +02:00
tc-wleite c5b0f4a05f Add the new image to the basic reading test.
(cherry picked from commit e5dc6aa878)
2023-07-19 13:10:50 +02:00
tc-wleite 98104222cf Test if a WebP with alpha was read correctly using subsampling.
(cherry picked from commit 46b1c1cf96)
2023-07-19 13:10:49 +02:00
tc-wleite cb1dc3f4b5 Add to resources a test WebP with alpha and filters.
(cherry picked from commit d9300b1c90)
2023-07-19 13:10:49 +02:00
tc-wleite 7e584cdab4 Param can be null in readAlpha(). Copy alphaRaster to dst in this case.
(cherry picked from commit 0a2efb9eac)
2023-07-19 13:10:49 +02:00
tc-wleite 6312c65622 Fix: use raster instead of decodedRaster to keep previous behavior.
(cherry picked from commit 3eabc591d8)
2023-07-19 13:10:49 +02:00
tc-wleite 924b7d809c Minor fix in code formatting.
(cherry picked from commit 5cefce2dbf)
2023-07-19 13:10:49 +02:00
tc-wleite 458be0380e Fix TODO comment message.
(cherry picked from commit 4c645c0220)
2023-07-19 13:10:49 +02:00
tc-wleite 7deaf47e65 Decode alpha using source dimensions and copy to destination later.
(cherry picked from commit 703848ca45)
2023-07-19 13:10:48 +02:00
tc-wleite 214f0acf29 Move to a static method the code that copies into a raster with params.
(cherry picked from commit 0ebd18fcb6)
2023-07-19 13:10:48 +02:00
Harald Kuhr 1ea443cdcf CI: Suppress download progress messages
(cherry picked from commit 29f7547a99)
2023-07-19 13:10:06 +02:00
Harald Kuhr 6186928374 Servlet: Now logs a message on context startup to aid debugging.
+ bonus generic refactorings

(cherry picked from commit 25cd351eee)
2023-07-19 13:10:06 +02:00
Harald Kuhr b4db69859d #733: Stricter permissions
(cherry picked from commit 77c98c917e)
2023-07-19 13:10:05 +02:00
Davide Tantillo 7b3f0254fd PSD: Add missing guide info in metadata
(cherry picked from commit 78832ed923)
2023-07-19 13:09:23 +02:00
Harald Kuhr 8627c3fc32 New versions, FAQ additions ++
(cherry picked from commit 164cc11592)
2023-07-19 13:09:23 +02:00
Harald Kuhr ab5d40828e [maven-release-plugin] prepare for next development iteration 2022-11-21 18:51:31 +01:00
Harald Kuhr dbb7c07695 [maven-release-plugin] prepare release twelvemonkeys-3.9.4 2022-11-21 18:51:26 +01:00
Harald Kuhr 6840f31fa3 #712 Core: Fix possible OOM situation in new stream implementation
(cherry picked from commit 8f5c1b409f)
2022-11-21 18:49:06 +01:00
Harald Kuhr debf7d0207 #714 PNM: Add support for writing TYPE_INT_* images + implementation of WriterSpi.canEncode
(cherry picked from commit 26981513d8)
2022-11-21 18:49:06 +01:00
Harald Kuhr 0538db7103 #713 PSD: Broken uncompressed reading from stream w/unknown length
(cherry picked from commit da800be8c8)
2022-11-21 18:44:44 +01:00
snyk-bot 1e981242ad fix: imageio/imageio-batik/pom.xml to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JAVA-ORGAPACHEXMLGRAPHICS-3063442
- https://snyk.io/vuln/SNYK-JAVA-ORGAPACHEXMLGRAPHICS-3063691

(cherry picked from commit 304d050bc3)
2022-11-21 18:43:40 +01:00
Harald Kuhr 135a631bcc #708 PSD: No longer emit warning for '8B64' (64 bit/long) resources.
(cherry picked from commit 0443172666)
2022-11-21 18:43:40 +01:00
Harald Kuhr f2624d5193 [maven-release-plugin] prepare for next development iteration 2022-10-20 16:22:15 +02:00
Harald Kuhr ada3a84bec [maven-release-plugin] prepare release twelvemonkeys-3.9.3 2022-10-20 16:22:10 +02:00
Harald Kuhr 7e7aaa293e #707 WebP: Fix Alpha support the correct way...
(cherry picked from commit cee2663f06)
2022-10-20 16:10:05 +02:00
Harald Kuhr 5d623cce9f #707 WebP: Fix Alpha support
(cherry picked from commit 8f44cfc43c)
2022-10-20 16:00:45 +02:00
Harald Kuhr 055838aaaf [maven-release-plugin] prepare for next development iteration 2022-10-20 14:08:11 +02:00
Harald Kuhr a8327c3c67 [maven-release-plugin] prepare release twelvemonkeys-3.9.2 2022-10-20 14:08:06 +02:00
Harald Kuhr 36c91f67e4 #704 Fix LSBBitReader to avoid back/forth seeking that invalidates buffer
(cherry picked from commit 8a240aac68)
2022-10-20 14:06:19 +02:00
Harald Kuhr 4cc53d822f [maven-release-plugin] prepare for next development iteration 2022-10-19 20:56:12 +02:00
Harald Kuhr 9875de0383 [maven-release-plugin] prepare release twelvemonkeys-3.9.1 2022-10-19 20:56:08 +02:00
Harald Kuhr 6ed858a4ca #704 Tiny performance improvement + code clean-up
(cherry picked from commit 61424f33b6)
2022-10-19 20:46:45 +02:00
Harald Kuhr 38192ae835 Code clean-up.
(cherry picked from commit c7b9b1fadd)
2022-10-19 20:46:45 +02:00
Harald Kuhr b5e8853e6b Set versions for 3.9 bugfix branch. 2022-10-18 20:49:43 +02:00
Harald Kuhr a98224e652 #705 No longer closes streams we didn't open
(cherry picked from commit ab08ec1e0d)
2022-10-18 20:34:27 +02:00
Harald Kuhr 73a58266be ...and removed System.out.. Ouch...
(cherry picked from commit cbe78dc67f)
2022-10-18 20:34:26 +02:00
Harald Kuhr edd523534c Fixed typo...
(cherry picked from commit c9e11f171f)
2022-10-18 20:34:26 +02:00
Harald Kuhr 6581e2e2a1 [maven-release-plugin] prepare release twelvemonkeys-3.9.0 2022-10-15 12:12:49 +02:00
Harald Kuhr c2873b1f27 Minor test optimization... 2022-10-15 12:05:33 +02:00
Harald Kuhr 35f2f0be9f Revert "Replace Java 18 with 19 in build matrix."
This reverts commit a77b62b6ba.
2022-10-15 11:55:34 +02:00
Harald Kuhr b5856fd110 Revert "Update test to pass on JDK 19."
This reverts commit 081f2efea2.
2022-10-15 11:55:34 +02:00
Harald Kuhr 1919d77a45 Revert "Update test to pass on JDK 19."
This reverts commit 37afe24aac.
2022-10-15 11:55:34 +02:00
Harald Kuhr 37afe24aac Update test to pass on JDK 19. 2022-10-14 18:51:40 +02:00
Harald Kuhr 081f2efea2 Update test to pass on JDK 19. 2022-10-14 18:44:54 +02:00
Harald Kuhr 627bb1bf1f Update action versions. 2022-10-14 18:27:29 +02:00
Harald Kuhr a77b62b6ba Replace Java 18 with 19 in build matrix. 2022-10-14 18:22:50 +02:00
Harald Kuhr 2500d8cc15 #687 #691 Stream performance regressions... and JDK 17+ support :-P 2022-10-14 18:20:47 +02:00
Harald Kuhr c01336fb8a #687 #691 Stream performance regressions, now with JDK 11 support... 2022-10-14 18:16:18 +02:00
Harald Kuhr 6f9b9bee01 #687 #691 Stream performance regressions 2022-10-14 18:00:43 +02:00
Harald Kuhr b9b1a35408 Replaced Map.Entry with StandardImageMetadataSupport.TextEntry 2022-10-10 14:15:57 +02:00
Harald Kuhr 7ed5663633 More tests of StandardImageMetadataSupport + minor API changes 2022-10-08 14:28:10 +02:00
Harald Kuhr 6458fcdcbd Major ImageMetadata refactor for more consistent standard metadata support.
Fixes a few related bugs as a bonus.
2022-10-08 13:43:26 +02:00
Harald Kuhr 9375bfda9a #703: Workaround for 32 bit issue in ImageTypeSpecifier 2022-10-06 16:00:43 +02:00
Harald Kuhr 0160fb70f8 #702 Fix NPE while reading an WebP animation without alpha
+ bonus cleanup
2022-10-06 15:24:40 +02:00
Harald Kuhr 29dca0f124 Removed explicit version number for Apache Batik 2022-09-29 14:36:57 +02:00
Harald Kuhr c21f14efe1 Merge pull request #698 from KoenDG/batik_upgrade115
Batik has released version 1.15
2022-09-23 15:24:45 +02:00
Koen De Groote 81ba43e1e8 Batik has released version 1.15
Tickets fixed in this release: https://issues.apache.org/jira/browse/BATIK-1334?jql=project%20%3D%20BATIK%20AND%20status%20in%20(Resolved%2C%20Closed)%20AND%20fixVersion%20%3D%201.15%20ORDER%20BY%20affectedVersion%20DESC%2C%20priority%20DESC
2022-09-22 14:51:57 +02:00
Harald Kuhr a1fcfc3958 Fix WebP visibility issues. 2022-09-09 14:09:58 +02:00
Harald Kuhr c60116a611 Inserted license header + author tags for contributed WebP files. 2022-09-09 14:06:41 +02:00
Harald Kuhr 15e6ddc1fd Fix typo. :-) 2022-09-09 14:05:51 +02:00
Harald Kuhr 49f4e5401e Add JDK 18 to the build matrix. 2022-09-09 12:35:22 +02:00
Harald Kuhr e333c7d1b2 Merge pull request #696 from Simon04090/webp-lossless
WebP lossless
2022-09-09 08:43:48 +02:00
Simon Kammermeier cda34b704b Indicate support for lossless to ImageIO 2022-09-09 01:18:53 +02:00
Simon Kammermeier 7c4487be04 Add tests for lossless decoder 2022-09-09 01:18:53 +02:00
Simon Kammermeier 5a4525aaa1 Remove debug prints 2022-09-09 01:18:53 +02:00
Simon Kammermeier b766420e3e Parse ANIM metadata
Still need to expose them in image metadata
2022-09-09 01:18:53 +02:00
Simon Kammermeier c858454c5a Support ImageReadParam Settings limiting Raster size
On animation frames dimension has to be passed as it is not guaranteed
the same as in the file header.
2022-09-09 01:18:53 +02:00
Simon Kammermeier 67b48ce1e3 Implement decoding of compressed alpha chunks, alpha filtering 2022-09-09 01:18:53 +02:00
Simon Kammermeier 6608f61353 Fix starting to read at wrong offset, now skips header 2022-09-09 01:18:53 +02:00
Simon Kammermeier 326b98d5e5 Implement applying of the inverse transforms 2022-09-09 01:18:53 +02:00
Simon Kammermeier fafa58b718 Implement actual decoding including resolving backward refs and cache 2022-09-09 01:18:45 +02:00
Simon Kammermeier 0ed0246762 Implement Huffman Table parsing and decoding
Uses a 2 level table approach inspired by libwebp
2022-09-09 01:15:04 +02:00
Simon Kammermeier b3004a1227 Implement buffering in LSBBitReader
This optimizes away the constant re-reading of bytes.
Also allows peeking at coming bits without consuming them.
2022-09-09 01:15:04 +02:00
Simon Kammermeier 7ab627a754 Setup Huffman Table framework, decode meta groups 2022-09-09 01:14:36 +02:00
Simon Kammermeier 008e57a7ce Move helper methods to transforms needing them 2022-09-09 01:09:28 +02:00
Simon Kammermeier 28270b4d5b Objectify Transforms
Deduplicate code for parsing predictor and color transforms.
Add missing subtraction code removal on indexing transform.
2022-09-09 00:46:48 +02:00
Simon Kammermeier 7382151db8 Convert transforms list and colorCache to local variables
This is needed because on recursion new (empty) ones are necessary.
2022-09-09 00:10:33 +02:00
Simon Kammermeier b856ce07af Fix not using LSBBitReader 2022-09-09 00:04:41 +02:00
Harald Kuhr 190fe87ee9 DiscreteAlphaIndexColorModel num components fix 2022-08-19 16:38:45 +02:00
Harald Kuhr d1872ce94f #694: Fixed import order 2022-08-19 14:08:26 +02:00
Harald Kuhr a5c52a99b4 #694 BMP: Fixed subsampling for 24 bit/pixel case 2022-08-16 13:56:51 +02:00
Harald Kuhr 4170b393fa #684 Add some tolerance for JDK 8... 2022-06-10 17:48:20 +02:00
Harald Kuhr 53f9ba91e0 #684 Remove TODO as it's now fixed 2022-06-10 17:44:53 +02:00
Harald Kuhr be2d7d5f10 #684 Fix some render size issues in SVGImageReader
Bonus: Minor code clean-up.
2022-06-10 17:24:47 +02:00
Harald Kuhr 00aec2c90e Minor code clean-up for WMFImageReader 2022-06-10 17:01:55 +02:00
Harald Kuhr 2b04f7205c Minor optimizations. 2022-06-10 16:58:23 +02:00
Harald Kuhr 7d401d0194 #675 PSD 16/32 bit layer support pt2: Cross-platform test 2022-06-10 15:19:14 +02:00
Harald Kuhr 48691139a3 #675 PSD 16/32 bit layer support 2022-06-10 10:14:41 +02:00
Harald Kuhr bcb87c09d2 #681: Fix for little-endian "packed" USHORT types + rewritten stream handling 2022-06-03 19:23:50 +02:00
Harald Kuhr 84a8ceeb93 #683: Fix TIFF stripByteCounts computation for uncompressed data 2022-06-03 16:04:43 +02:00
Harald Kuhr 0cb99feedf A new ImageInputStream adapter for InputStream. 2022-06-01 22:00:37 +02:00
Harald Kuhr 91493c5145 #682 TIFF Lab w/alpha support 2022-06-01 19:30:01 +02:00
Harald Kuhr 6ddb799a95 Fix bad format in validator message. 2022-05-29 14:55:17 +02:00
Harald Kuhr 8a187f6657 TGAImageReader no longer reads single byte 0-terminator as Image Identification 2022-05-18 22:47:45 +02:00
Harald Kuhr b7d865f2cf #680 TGAImageReader now reads attribute bits with no extension area as alpha 2022-05-18 22:18:20 +02:00
Harald Kuhr d50fb1a51e Fix bug in 0-terminated ASCII string parsing + test. 2022-05-18 21:01:17 +02:00
Harald Kuhr 8992406f50 Documentation & details. 2022-05-18 20:31:28 +02:00
Harald Kuhr 44eebff62f #678, #679: TIFF read support for YCbCr Planar with or without subsampling 2022-05-12 23:01:12 +02:00
Harald Kuhr 8c85c4ca96 Simplified TIFF writing. 2022-05-06 19:49:06 +02:00
Harald Kuhr fa5c77bff0 Added test cases for EncoderStream/DecoderStream and fixed a bug
+ code clean-up to make IntelliJ happy :-)
2022-05-06 19:31:16 +02:00
Harald Kuhr d87b80deea PCX: Minor clean up 2022-05-06 19:27:44 +02:00
Harald Kuhr ae138c3b4e Write LONG8 offsets for BigTIFF 2022-05-05 15:53:20 +02:00
Harald Kuhr ab13fdd09d #677 Fixed integer overflow + added tests 2022-05-04 18:23:25 +02:00
Harald Kuhr aab5b062bd Fixed test typo. 2022-05-04 18:22:14 +02:00
Harald Kuhr 00d6acd1bf [skip ci] Fixed some typos in comments. :-) 2022-04-26 19:25:52 +02:00
Harald Kuhr 0f8a7ea482 Update feature_request.md 2022-04-25 16:58:39 +02:00
Harald Kuhr 9fe87fe10d #672: WebPImageReader now supports unknown stream lengths 2022-04-22 14:41:57 +02:00
Harald Kuhr a33dbaf897 Merge pull request #669 from haraldk/snyk-upgrade-a0c5c60996f8b405ed1deadc1666ddc0
[Snyk] Upgrade jmagick:jmagick from 6.2.4 to 6.6.9
2022-04-11 17:01:44 +02:00
Harald Kuhr 9e2f369459 #666 Clean-up: No alpha for RGB 3/components 2022-03-11 19:58:38 +01:00
Harald Kuhr d34b0b7fcf #666 Support for TIFF RGB 2/4 bit per sample. 2022-03-11 19:54:33 +01:00
snyk-bot 5effcb1344 fix: upgrade jmagick:jmagick from 6.2.4 to 6.6.9
Snyk has created this PR to upgrade jmagick:jmagick from 6.2.4 to 6.6.9.

See this package in Maven Repository:
https://mvnrepository.com/artifact/jmagick/jmagick/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/eca06326-94ac-456d-a029-f411089e7f16?utm_source=github&utm_medium=referral&page=upgrade-pr
2022-03-04 21:50:39 +00:00
Oliver Schmidtmer b67d687761 TIFFImageMetadata: ImageOrientation in mergeTree (#667)
TIFFImageMetadata: ImageOrientation in mergeTree
2022-02-28 15:53:49 +01:00
Harald Kuhr d0881c8b5c Attempt at adding JavaDocs + use "release" profile for release-plugin [skip ci] 2022-02-22 14:46:14 +01:00
Harald Kuhr 976928f48c Adding description to top-level POM [skip ci] 2022-02-22 13:19:49 +01:00
Harald Kuhr e1c2f2ee73 Thank you, Travis [skip ci] 2022-02-21 09:34:34 +01:00
Harald Kuhr 92632fa2a3 #661: JUnit test results take 4 2022-02-17 19:09:26 +01:00
Harald Kuhr 5a563e315f #661: JUnit test results take 3 2022-02-17 18:42:54 +01:00
Harald Kuhr c06d47d123 #661: JUnit test results take 2 2022-02-17 18:33:20 +01:00
Harald Kuhr fea6beb364 #661: JUnit test results 2022-02-17 18:32:18 +01:00
Harald Kuhr 4b951c06cc PNM: New attempt at making the new header parser work on Windows. 2022-02-14 22:00:04 +01:00
Harald Kuhr a3e6e52c95 PNM Windows issue. Temporary roll-back to working version. 2022-02-14 19:33:28 +01:00
Harald Kuhr 5347015cbd PNM clean-up: Avoid leading/trailing whitespace in comments. 2022-02-14 19:27:55 +01:00
Harald Kuhr 4d190892df PNM clean-up. 2022-02-09 20:13:49 +01:00
Harald Kuhr 60eab81709 #660: Attempt at making the comment parsing more Windows-friendly... 2022-02-08 11:19:38 +01:00
Harald Kuhr b400b6b157 #660: Make sure region is within bounds of new test image... 2022-02-08 10:38:53 +01:00
Harald Kuhr 499b3ef120 #660: Farewell, Lena 2022-02-08 10:16:42 +01:00
Harald Kuhr 92bc9c73f6 IFF: Simplified aspect. 2022-02-08 08:43:21 +01:00
Harald Kuhr 2a77558cac IFF: XS24 clean-up (again...) 2022-02-04 12:33:28 +01:00
Harald Kuhr 816cad60a8 IFF: XS24 clean-up 2022-02-04 12:23:44 +01:00
Harald Kuhr 7167f81c69 IFF: Thumbnail support for XS24 chunk (now without stderr output) 2022-02-04 12:13:53 +01:00
Harald Kuhr f5cfa0e619 IFF: Thumbnail support for XS24 chunk. 2022-02-04 11:46:32 +01:00
Harald Kuhr 73ad024833 IFF: Read support for TVPaint DEEP and TVPP
+ Bonus: Massive code clean-up/refactor.
2022-02-03 17:26:41 +01:00
Harald Kuhr 379449b621 IFF: More clean-up 2022-01-29 14:41:49 +01:00
Harald Kuhr e17faad6fb IFF: Read support for Impulse (Imagine, Turbo Silver) RGB8 format. 2022-01-28 16:36:34 +01:00
Harald Kuhr 1271a3d55e IFF clean-up. 2022-01-28 16:07:15 +01:00
Harald Kuhr 1cd594d113 RIP: Sandbox 2022-01-24 09:01:53 +01:00
Harald Kuhr b76f74e79a A little safer way to skip 6 bytes... 2022-01-19 09:00:13 +01:00
Harald Kuhr 78817a489b #658: TGAImageReader now allows extension area of size 0 2022-01-19 09:00:13 +01:00
Harald Kuhr b8f2a80ca6 #658: TGAImageReaderSpi now recognizes "true color" images with valid palette depth != 0 2022-01-19 09:00:13 +01:00
Oliver Schmidtmer ac8a36db1c findCompressionType always uses RLE if leading EOL is missing (#657)
Update of the last read byte is missing since the last update. So if only the first EOL is missing, further EOLs after the lines are not detected.
2022-01-15 00:21:47 +01:00
Harald Kuhr 7e0d8922da #655 Experimental force raster conversion switch. 2022-01-12 19:51:56 +01:00
Harald Kuhr 9a6b8c9bfe Fix for IIOInvalidTreeException: Invalid DHT node #559 2022-01-12 19:33:21 +01:00
Harald Kuhr eced5b8efd #656 Code clean-up + minor refactorings. 2022-01-12 19:11:52 +01:00
Oliver Schmidtmer 74611e4e52 Support writing ASCII array in TIFF metadata (#656)
* Support writing ASCII array in TIFF metadata

* corrected formatting and extracted string writing to method
2022-01-12 18:56:22 +01:00
Harald Kuhr b8614eca4d New CI badge + new maven badges, replaces #653 2022-01-12 18:45:14 +01:00
Harald Kuhr efd24456ac #636: Correct name for shaded artifact. 2022-01-05 15:47:16 +01:00
Gauthier 191b2371c8 add support for Github Actions, publish snapshots to OSSRH automatically (#633)
* remove oss-parent

* add github workflow

* use java 16 for now

* disable fail fast

* add java 15

* use only java 8 and 11 for now

* snapshot deploy

* snapshot deploy

* oracle jdk

* oracle jdk

* oracle jdk

* kcms matrix

* kcms job name

* only deploy for snapshots

* try not operator

* prepare PR

* restore groupId

* Fixed Travis link + bonus project summary updates

* Readme improvements

* #629: Preliminary WebP animation (ANIM/ANMF) support

* #629: Fixed build

* Make tests pass on JDK 16 and 17 (#635)

* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

* test on java 17

* try to fix warning about maven-source-plugin

Co-authored-by: Harald Kuhr <harald.kuhr@gmail.com>
2022-01-05 12:34:52 +01:00
Harald Kuhr 33419ef291 #652: Avoid OOME for large values outside TIFF, even if length is unknown 2022-01-03 12:51:52 +01:00
Harald Kuhr 123f0bb7fc #648: (Re-)Added support for nested layer groups 2021-12-29 16:20:02 +01:00
Harald Kuhr 99b5f28a49 #648: Removed unnecessary parentheses. 2021-12-29 12:38:04 +01:00
Harald Kuhr b30fb4f8c3 #648: Simplified logic, code style fixes and clean up. 2021-12-28 16:49:41 +01:00
Jack Yun dc0bdcbd5b Support Group Layer in psd (#648) 2021-12-27 13:39:39 +01:00
Harald Kuhr 0cf29c167d Updated with latest versions. 2021-12-27 12:59:24 +01:00
Harald Kuhr 98e4b76206 #651: Fix ExtraSamplesColorModel equals + hashcode to behave nicely with ImageTypeSpecifier comparison. 2021-12-24 12:57:24 +01:00
Harald Kuhr aa4b5db054 Minor clean-up. 2021-12-24 12:27:10 +01:00
Harald Kuhr 433311c10d #651: Fix ExtraSamplesColorModel to create correct length elements array. 2021-12-24 12:25:31 +01:00
Harald Kuhr f50178bc78 Alternative fix for #650: Allow usage in OSGi environment. 2021-12-23 11:02:27 +01:00
Snyk bot e016e970e5 fix: upgrade commons-io:commons-io from 2.9.0 to 2.11.0 (#647)
Snyk has created this PR to upgrade commons-io:commons-io from 2.9.0 to 2.11.0.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-io/commons-io/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/9a1f6304-68e0-49c5-af4f-db1f87bd4f90?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-16 08:46:44 +01:00
Harald Kuhr 4223d13898 Update jakarta servlet dependency classifier. 2021-12-15 18:34:03 +01:00
Harald Kuhr 444aeabf21 Overriding transitive dependency. 2021-12-15 16:58:27 +01:00
Harald Kuhr 05507a59d6 Getting rid of the dependencies too. 2021-12-15 16:29:38 +01:00
Harald Kuhr c4c89a0a25 Delete deprecated servlet classes 2021-12-15 16:23:08 +01:00
Harald Kuhr b0ad6b2a4b Delete deprecated Servlet classes 2021-12-14 19:35:13 +01:00
Harald Kuhr 25c703f4b2 #646: Spi now recognizes VP8 encoded images in VP8X ("extended format"). 2021-12-14 19:30:08 +01:00
Oleh Astappiev 529c59f93f Create jakartified package on build (#636)
* feat(servlet): create jakartified package on build

* feat(servlet): update README to include Jakarta classifier
2021-12-14 19:25:02 +01:00
Harald Kuhr 584b1d9b21 Updated with the latest versions. 2021-12-14 09:18:36 +01:00
Harald Kuhr 312ce364cc [maven-release-plugin] prepare for next development iteration 2021-12-12 13:17:20 +01:00
Harald Kuhr 7de8231471 [maven-release-plugin] prepare release twelvemonkeys-3.8.0 2021-12-12 13:17:16 +01:00
Harald Kuhr 0de9f79029 [maven-release-plugin] rollback the release of twelvemonkeys-3.8.0 2021-12-12 13:13:54 +01:00
Harald Kuhr eeb56acdde [maven-release-plugin] prepare for next development iteration 2021-12-11 23:26:21 +01:00
Harald Kuhr a6862cfec8 Fixed JavaDoc. 2021-12-11 23:23:05 +01:00
Harald Kuhr f8284700b4 #631 Attempt to fix tests for JDK 15 & 16 2021-12-11 22:38:42 +01:00
Harald Kuhr 38caeb22e0 #631 Introduced ColorProfiles. Profile activation through SPI to force early activation. 2021-12-11 18:58:25 +01:00
Harald Kuhr b2c5915db8 #631 New way of forcing profile activation + guarding all invocations of ICC_Profile.getInstance() 2021-12-11 18:13:07 +01:00
Harald Kuhr 3911191b04 #645 AAIOBE in CCITTFaxDecoderStream now wrapped in IOException 2021-12-11 17:48:57 +01:00
Snyk bot bc328419ac fix: upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4 (#642)
Snyk has created this PR to upgrade commons-fileupload:commons-fileupload from 1.3.3 to 1.4.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/3eb50c00-e586-4f4c-a39c-90c80f89cc60?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-10 16:13:29 +01:00
Harald Kuhr da4efe98bf Avoid fetching external resources in XMPReader. 2021-12-10 13:41:05 +01:00
Harald Kuhr 6653f4a85d Minor clean-up. 2021-12-10 13:33:28 +01:00
Harald Kuhr 511a29beb9 Minor AffineTransformOp clean-up + removed test output. 2021-10-29 11:05:24 +02:00
Gauthier 5617b4323c Make tests pass on JDK 16 and 17 (#635)
* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis
2021-10-26 18:38:33 +02:00
Harald Kuhr 16d0af357d #629: Fixed build 2021-10-15 16:04:53 +02:00
Harald Kuhr 74927d5396 #629: Preliminary WebP animation (ANIM/ANMF) support 2021-10-15 15:08:39 +02:00
Harald Kuhr 7e809dd834 Readme improvements 2021-10-15 14:48:39 +02:00
Harald Kuhr 7aa95a08bc Fixed Travis link + bonus project summary updates 2021-10-15 09:41:47 +02:00
Harald Kuhr c28963ae49 Minor servlet clean-up. 2021-09-21 14:23:59 +02:00
Harald Kuhr 0327f5fc1a Servlet deprecation 2021-09-21 14:22:16 +02:00
Harald Kuhr 1c59057c30 #628: Stabilized build + better dependency scopes and module names in interop modules 2021-09-17 20:32:25 +02:00
Harald Kuhr 3e1f85c4dc #626 Clean up + fill order support for all compression types 2021-09-17 19:37:28 +02:00
Harald Kuhr 11227a68a0 #628 TIFF metadata fix, now always outputs denominator for rationals.
+ Bonus: Added JAI TIFF interop module with test and other minor fixes.
2021-09-17 16:34:38 +02:00
Oliver Schmidtmer 62ba73a30e #626: Handle fillOrder in TIFFImageReader, not in CCITTFaxDecoderStream (#627) 2021-09-17 16:16:30 +02:00
Harald Kuhr 1f33afb5a1 Fixed NullPointerException due to missing PhotometricInterpretation, now uses fallback as we do when reading. 2021-09-16 22:56:13 +02:00
Harald Kuhr 9d3f271867 #626 TIFF CCITT detection only once per IFD 2021-09-16 22:25:06 +02:00
Harald Kuhr 812e12acb0 #623: TGAImageReader, PCXImageReader and SGIImageReader now return more standard image types as default, for better AffineTransformOp compatibility. Added tests.
Bonus: Subsampling fix for TGAImageReader and BMPImageReader.
2021-09-07 09:29:13 +02:00
Harald Kuhr 060b6cf852 #624: Added metadata support for 16 bit USHORT gray. 2021-09-07 09:24:27 +02:00
Koen De Groote e68ce7ffd1 Certain pixeldepth-16 TGA files fail to process, classcast exception (#624)
* Added fixed for monochrome tga16 bit. Uncertain if that description is complete.
Test files added. Without the changed code, the tests fail.

* Fix suggested by HaraldK

Co-authored-by: Koen De Groote <koen.degroote@limecraft.com>
2021-09-03 16:19:05 +02:00
Harald Kuhr 778cdef69c Fix typo in TIFFImageMetadataFormat mk II. 2021-08-31 22:34:47 +02:00
Harald Kuhr d46a76fca8 Fix typo in TIFFImageMetadataFormat. 2021-08-31 22:26:32 +02:00
Harald Kuhr 105a1ee466 #621 Don't add ICC profile for default gray images 2021-08-31 22:16:08 +02:00
Harald Kuhr aa030f526c #617 BigTIFF write clean-up. 2021-08-31 20:24:42 +02:00
Harald Kuhr 976e5d6210 #619: Fix WebP Y'CbCr->RGB conversion (now uses rec 601) 2021-08-26 16:47:51 +02:00
Harald Kuhr 6daca00fcd Minor clean-up. 2021-08-09 21:24:32 +02:00
Harald Kuhr ef05872934 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/tiff/TIFFWriter.java
2021-08-09 21:22:46 +02:00
Harald Kuhr 1ddab866fd #617 BigTIFF write support. 2021-08-09 21:11:40 +02:00
Harald Kuhr ce997a6951 Some more minor clean-up. 2021-08-06 10:10:34 +02:00
Harald Kuhr 23bf5cb7b2 Minor clean-up. 2021-08-06 09:59:07 +02:00
Harald Kuhr 564778f415 #616: Remove dependency on old xmlgraphics-commons (no longer needed) 2021-08-04 11:25:58 +02:00
Harald Kuhr e28bf8fb44 Fix WebP ICC handling for images with alpha. 2021-07-12 10:01:16 +02:00
Harald Kuhr cf8d630d01 Add WebP to BOM. 2021-07-12 09:38:20 +02:00
Harald Kuhr 0ff7224912 Switch build from travis.ci.org to com 2021-06-29 16:56:58 +02:00
Koen De Groote 196081a317 Documentation cleanup (#612)
* Added the `@Deprecated` tag to instances where is was mentioned in documentation, but not for the actual code itself.

Changed one documentation link pointing at a non-existing item.

* As per PR suggestion.
2021-06-29 13:06:07 +02:00
Harald Kuhr ff50180d86 #609 Fixed ICC Profile handling in WebP. 2021-06-03 21:31:54 +02:00
Harald Kuhr 8f2c482167 Minor code clean-up. 2021-06-03 18:21:00 +02:00
Harald Kuhr eab24890ca Merge pull request #608 from Schmidor/ccitt_eol_search
#579 Deeper EOL search in the CCITT stream
2021-05-31 10:40:20 +02:00
Oliver Schmidtmer cd42d81817 Invert EOF check 2021-05-28 14:38:44 +02:00
Oliver Schmidtmer ba5c667b6c #579 Deeper EOL search in the CCITT stream 2021-05-27 22:11:13 +02:00
Harald Kuhr 4e9fa9442c PR template update. 2021-05-13 15:15:09 +02:00
Harald Kuhr 4d2326c18d Update issue templates 2021-05-09 18:12:34 +02:00
Harald Kuhr 94eac2d6e5 XXX: Remove another old servlet class. 2021-05-09 18:08:56 +02:00
Harald Kuhr f63a33d541 HTTPS links in README.md 2021-05-09 18:08:43 +02:00
Harald Kuhr 00f8d87f36 PR template + rename issue template. 2021-05-09 18:08:16 +02:00
Harald Kuhr 4c2ab6da7b Update issue templates 2021-05-08 20:41:13 +02:00
Harald Kuhr b5088312e2 Update issue templates 2021-05-07 19:39:41 +02:00
Harald Kuhr f04f968f12 Update issue templates 2021-05-07 19:35:02 +02:00
Harald Kuhr 8896092e31 Update issue templates 2021-05-07 19:29:52 +02:00
Harald Kuhr 2f9768a1d4 XXX: Remove old servlet class. 2021-05-06 00:14:21 +02:00
Harald Kuhr 06bcf22242 #483 Minor optimization 2021-05-06 00:12:19 +02:00
Harald Kuhr 20c7f8e60e #483 Add license headers. 2021-05-06 00:08:27 +02:00
Harald Kuhr 15a9ad0a9b #483 Initial PSD Write support 2021-05-06 00:01:54 +02:00
Harald Kuhr 7ae2d636dc Merge pull request #607 from haraldk/snyk-upgrade-b72dc0a35c77c27ccf914333993bfcb1
[Snyk] Upgrade com.github.jai-imageio:jai-imageio-core from 1.3.0 to 1.4.0
2021-04-30 08:45:03 +02:00
snyk-bot 12e756b23c fix: upgrade com.github.jai-imageio:jai-imageio-core from 1.3.0 to 1.4.0
Snyk has created this PR to upgrade com.github.jai-imageio:jai-imageio-core from 1.3.0 to 1.4.0.

See this package in Maven Repository:
https://mvnrepository.com/artifact/com.github.jai-imageio/jai-imageio-core/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/33153897-db54-44bf-a11a-efd834cecdae?utm_source=github&utm_medium=upgrade-pr
2021-04-30 00:42:19 +00:00
Harald Kuhr 4e2bf131d2 #606: Fix bug introduced by more aggressive readDirect. 2021-04-29 20:06:36 +02:00
Harald Kuhr d0c4a07556 #606: Workaround for broken JDK WBMPImageReader 2021-04-29 16:55:24 +02:00
Harald Kuhr 21059c8d5a Cleaner tests for Java 6 or later... A little late. :-) 2021-04-29 16:46:16 +02:00
Harald Kuhr fa7b530809 Adding GitHub sponsors link. 2021-04-28 14:21:37 +02:00
Harald Kuhr 790cf3b32e Test clean-up. 2021-04-26 22:42:44 +02:00
Harald Kuhr b1baaad23b Rename file, add missing file extension. 2021-04-25 19:55:33 +02:00
Harald Kuhr 7fa704ace5 Bump plugins and stop deploying useless (internal) artifacts. 2021-04-24 19:26:32 +02:00
Harald Kuhr 8d07f4fe90 Updated versions to 3.7.0. 2021-04-24 13:20:40 +02:00
Harald Kuhr 32bba6857b [maven-release-plugin] prepare for next development iteration 2021-04-24 12:31:07 +02:00
Harald Kuhr ab7b08dfa9 [maven-release-plugin] prepare release twelvemonkeys-3.7.0 2021-04-24 12:31:00 +02:00
Harald Kuhr e0d6fa0d84 Better JPMS automatic module names. 2021-04-24 12:26:36 +02:00
Harald Kuhr 51bdd370da Fix some JavaDoc issues that broke the release build... 2021-04-24 12:05:49 +02:00
Harald Kuhr ee2be3f88f [maven-release-plugin] rollback the release of twelvemonkeys-3.7.0 2021-04-24 11:24:15 +02:00
Harald Kuhr c5511833cc Fix SemVer issue. 2021-04-24 11:12:09 +02:00
Harald Kuhr 6ac8a5d8b4 Minor optimization for standard case with only one image. 2021-04-20 22:41:41 +02:00
Harald Kuhr 3f7cb24407 #395 Removed WebP lossless and extended format (until it's implemented). 2021-04-15 19:24:52 +02:00
Harald Kuhr 8bf9f7a8f0 Minor clean-up. 2021-04-10 18:25:27 +02:00
Harald Kuhr 03ab9558a0 #573: And error message. 2021-04-10 18:17:34 +02:00
Harald Kuhr 715bde8358 Comment too. 2021-04-10 18:17:01 +02:00
Harald Kuhr 0151efb5f6 #573: License headers too. 2021-04-10 18:15:27 +02:00
Harald Kuhr bd796429c5 #573: Better naming: LuminanceToGray 2021-04-10 18:14:09 +02:00
Harald Kuhr b4ef5823f3 #417: Half precision support (clean-up) 2021-04-10 18:13:04 +02:00
Harald Kuhr 9adf0f4da3 #417: Half precision support (clean-up) 2021-04-10 14:17:38 +02:00
Harald Kuhr 01a4e55185 Merge pull request #602 from haraldk/webp
We have Webp
2021-04-10 12:10:45 +02:00
Harald Kuhr 2e2ab11091 Merge branch 'master' into webp 2021-04-10 11:46:28 +02:00
Harald Kuhr 419ffc9373 #573: Always return RAWImageType for JPEG.
+ Bonus: Fix luma to gray conversion
2021-04-10 11:44:09 +02:00
Harald Kuhr b67975eef7 Better naming. 2021-04-09 17:19:15 +02:00
Harald Kuhr b32a861b2d #443 Automatic-Module-Name in each JARs MANIFEST.MF 2021-04-09 16:56:26 +02:00
Harald Kuhr 6930168c93 #362: JPEG metadata names in ProviderInfo. 2021-04-08 19:37:03 +02:00
Harald Kuhr fac9f1a927 #532 Write TGA with RLE compression. 2021-04-08 19:31:25 +02:00
Harald Kuhr 913a03608c #600 TimeoutMap now longer gets Long.MAX_VALUE as next expiry time if map is empty when removeExpiredEntries() is invoked. 2021-04-07 22:31:13 +02:00
Harald Kuhr 46ce99e10f Added links to metadata formats. 2021-04-07 13:21:31 +02:00
Harald Kuhr 3e4460ac41 Removed TODO (that has been done for some time). 2021-03-30 17:13:00 +02:00
Harald Kuhr 5b7fc25520 #584 Fix "No SOF segment in stream" for JPEG in TIFF
+ interoperability testing for JEP-262, JAI and JDK readers.
2021-03-30 16:26:48 +02:00
Harald Kuhr 42196e8513 No longer unregister the old Apple provided spi. 2021-03-30 12:31:56 +02:00
Harald Kuhr bc07524e7a Simplified/optimized TIFF file recognition code. 2021-03-30 11:31:34 +02:00
Harald Kuhr 0011b9a480 #417: TIFF 16 bit FP 2021-03-29 10:26:47 +02:00
Harald Kuhr 7b09ec8919 Merge branch 'master' into webp 2021-03-27 15:29:23 +01:00
Harald Kuhr 9c8977062d #554, #416: Relaxed custom metadata restrictions. 2021-03-27 15:20:59 +01:00
Harald Kuhr b01b820ec8 Remove unintended debug output. 2021-03-27 15:19:15 +01:00
Harald Kuhr b61f2c179c Merge branch 'master' into webp 2021-03-27 14:44:23 +01:00
Harald Kuhr 967f8e6984 PICT metadata + PNTG support 2021-03-27 14:39:59 +01:00
Harald Kuhr bb650e5280 Easier subsampling with xSub == 1 as no-op 2021-03-27 14:37:33 +01:00
Harald Kuhr 3b34d6e7ce General clean-up 2021-03-27 14:37:11 +01:00
Harald Kuhr db782cfe9e Merge pull request #599 from Schmidor/svg_data_url
Allow embedded resource URLs if external resources are disabled
2021-03-26 17:26:19 +01:00
Oliver Schmidtmer 96223f9f9f Allow embedded resource URLs if external resources are disabled 2021-03-26 16:05:21 +01:00
Harald Kuhr da45c5783d Forgot we actually had PICT write support.. ;-) 2021-03-23 13:15:06 +01:00
Harald Kuhr 5b6c819ac4 JPEG Lossless in non-bold :-) 2021-03-08 21:42:38 +01:00
Harald Kuhr 6d41f2db86 Updated version numbers for latest release. 2021-03-08 14:54:26 +01:00
Harald Kuhr ba0bb7b903 #595 Avoid infinite loop on corrupted JPEG stream 2021-03-06 14:15:44 +01:00
Harald Kuhr d03dc28764 Readme updates, mentioning JPEG lossless and built-in support (closes #471). 2021-03-01 18:42:59 +01:00
Harald Kuhr 20a785ea5e Updated version numbers. 2021-02-26 19:36:49 +01:00
Harald Kuhr 0286fa4268 JPEG Exif/thumbnail refactoring pt II. 2021-02-26 18:27:58 +01:00
Harald Kuhr 85fb9e6af3 JPEG Exif/thumbnail refactoring 2021-02-26 17:13:16 +01:00
Harald Kuhr 97a8806bfb Better name for source y... 2021-02-26 17:13:16 +01:00
Harald Kuhr 970f4f3a7e #588 Clipping path from JPEG with multiple APP13 segments 2021-02-26 17:13:16 +01:00
Harald Kuhr 6d192968d1 Fix SGI source subsampling + test optimizations. 2021-02-26 17:13:16 +01:00
Harald Kuhr f5959af2e1 New stream SPIs now behave more like the built-in SPIs. 2021-02-26 17:13:16 +01:00
Harald Kuhr ea74ac2714 JPEG Exif/thumbnail fixes pt II. 2021-02-26 17:13:16 +01:00
Harald Kuhr 80c595cea8 No longer reads thumbnails, as part of the readWithOrientation method. 2021-02-26 17:13:16 +01:00
Harald Kuhr fbc738f2d4 JPEG Exif/thumbnail fixes. 2021-02-26 17:13:16 +01:00
Harald Kuhr 3e3acf3332 More standard key mapping, more correct fit size. Nicer color! 2021-02-26 17:13:16 +01:00
Harald Kuhr 0a77520d67 Merge pull request #591 from KoenDG/batik_upgrade
Upgraded the Apache Batik library from 1.12 to 1.14 due to fixed CVEs.
2021-02-25 14:22:56 +01:00
Koen De Groote 72cd3aade3 Upgraded the Apache Batik library from 1.12 to 1.14 due to fixed CVEs. 2021-02-24 14:54:46 +01:00
Harald Kuhr 88bd9cd2ba Update README.md
Removed JDK 7 from recommended build.
2021-02-04 19:47:45 +01:00
Harald Kuhr 5ee8678a29 Removed XWD plugin that will be in 3.7.
Fixed some incorrect code escaping.
2021-01-23 18:13:18 +01:00
Harald Kuhr fb1937ae63 Updated README with latest version numbers. 2021-01-23 17:53:28 +01:00
Harald Kuhr de02e3d7e0 #582: Fix for missing Exif thumbnail, now only issues warning. 2021-01-11 22:10:25 +01:00
Harald Kuhr ebaa69713f Deprecate for BufferedImageInputStream, now using buffered streams directly in all readers. 2021-01-11 22:07:31 +01:00
Harald Kuhr 8a1a90dafd Fix some corner cases in BufferedImageInputStream. 2021-01-11 21:44:14 +01:00
Harald Kuhr 6f6e65be12 Added zoom to fit option. 2021-01-11 21:18:11 +01:00
Harald Kuhr 253f04066b #579 More reliable CCITT compression type detection 2020-12-23 11:46:58 +01:00
Harald Kuhr 74902b3fb4 StandardCharsets.US_ASCII instead of Charset.forName("ascii") 2020-12-21 17:30:34 +01:00
Harald Kuhr af1a6492d4 #577 Fix TGA subsampling + bonus metadata fix and palette conversion. 2020-12-15 22:19:04 +01:00
Harald Kuhr 0da007ec8c Minor clean-up. 2020-12-11 18:32:02 +01:00
Harald Kuhr 9053fb3816 Minor clean-up. 2020-12-11 18:28:48 +01:00
Harald Kuhr c1d4e474f0 Fix source region reading in VP8Frame. 2020-12-09 09:24:19 +01:00
Harald Kuhr 6bac13eb84 Merge branch 'master' into webp 2020-12-07 17:05:17 +01:00
Harald Kuhr 0e48ddd306 #292 Add legacy CMM option only for JDK 8. 2020-12-03 10:15:26 +01:00
Harald Kuhr 8682decbbc #292 Remove legacy CMM option. 2020-12-03 09:33:08 +01:00
Harald Kuhr bb615b90bf Merge branch 'master' into webp 2020-12-03 08:47:53 +01:00
Harald Kuhr cb0c320b45 #292 Build on Java 8, 11 and 15. 2020-12-03 07:55:30 +01:00
Harald Kuhr 73044bea58 #292 Now builds on Java 8, 11 and 15. 2020-12-02 22:08:40 +01:00
Harald Kuhr 3bb312e9e1 WebP source subsampling. 2020-11-30 22:10:54 +01:00
Harald Kuhr c7d2f422b8 BufferedImageInputStream performance optimizations. 2020-11-30 17:54:21 +01:00
Harald Kuhr 4dedf76ebc WebP performance optimizations & clean up. 2020-11-30 17:10:35 +01:00
Harald Kuhr 2376d16ffd WebP initial commit 2020-11-23 09:34:34 +01:00
Harald Kuhr 1fe0bdd41f Updated code samples to use more modern try-with-resource syntax. 2020-11-23 08:58:43 +01:00
Harald Kuhr 1b4d25342f Minor readme tweaks. 2020-11-22 13:19:43 +01:00
Harald Kuhr bc391550fb Minor readme tweaks. 2020-11-22 13:18:22 +01:00
Harald Kuhr b563f573de Better input validation. 2020-11-21 20:49:21 +01:00
Harald Kuhr 25150b421c Updated links to latest version. 2020-11-21 15:28:51 +01:00
Harald Kuhr 94031a2913 Add XWD to BOM. 2020-11-21 15:26:39 +01:00
Harald Kuhr 64fb421b38 IFF format clean-up + standard metadata support 2020-11-21 15:23:18 +01:00
Harald Kuhr 78af95d747 Merge branch 'master' of github.com:haraldk/TwelveMonkeys 2020-11-19 21:24:23 +01:00
Harald Kuhr 1d4f681b8f #574 Better test data. 2020-11-19 20:59:12 +01:00
Harald Kuhr eda2cd76db #574 Fix for possible OOME in Exif metadata. 2020-11-19 20:42:10 +01:00
Harald Kuhr 4adc60a6c6 Some minor code clean-up. 2020-11-19 20:35:37 +01:00
Harald Kuhr 0d5577a9a4 #330 ImageReaderBase.getDestination now throws IIOException for too large dimension/size. 2020-11-17 23:07:09 +01:00
Harald Kuhr 918f92aba7 #330 Now correctly calculates scanline for 1 & 4 bits 2020-11-17 22:57:25 +01:00
Harald Kuhr 7a24d55be7 #330 Now correctly uses USHORT instead of SHORT for 16 bit DIB. 2020-11-17 22:40:23 +01:00
Harald Kuhr a84cc1c060 #330 Now guards against buffer overruns in RLE decoder. 2020-11-17 22:33:50 +01:00
Harald Kuhr 31cb79d2b9 #330 Minor improvements to avoid RuntimeExceptions. 2020-11-17 22:11:31 +01:00
Harald Kuhr d995e7baa0 Fixed Maven Central link URL to more relevant URL. 2020-11-17 09:15:19 +01:00
Harald Kuhr e7fe6d5c22 Fixed Maven Central link URL 2020-11-16 19:44:41 +01:00
Harald Kuhr 918b698e50 Release notes already on the Github page. 2020-11-16 19:42:47 +01:00
Harald Kuhr 2427b2323f ...and again. 2020-11-16 19:36:40 +01:00
Harald Kuhr 0a8222fea3 ...and again. 2020-11-16 19:36:06 +01:00
Harald Kuhr 60a00b89ae Fixed metadata support (not all formats have it yet). 2020-11-16 19:31:58 +01:00
Harald Kuhr 4c88efa19d Removed empty lines. Added missing BMP info. 2020-11-16 19:18:58 +01:00
Harald Kuhr 17d65a1f6f New & improved README with tables and link to Wiki! 2020-11-16 19:13:36 +01:00
Harald Kuhr fcd03eb903 Added PayPal donation link. Go use it! :-) 2020-11-13 18:29:15 +01:00
Harald Kuhr 4e69efce28 Now correctly uses Image*Input*Stream instead of ImageOutputStream... 2020-11-13 15:52:17 +01:00
Harald Kuhr 16caec4a22 ...and fix the broken test. 2020-10-28 18:48:16 +01:00
Harald Kuhr 08282ea09d Minor improvements and better test cases. 2020-10-28 17:07:59 +01:00
Harald Kuhr c04fed1aff Fixed URL now works, ideally should point to correct branch... 2020-10-28 17:07:59 +01:00
Harald Kuhr 97e788883a More standard way for getting vendor name and version info. 2020-10-28 17:07:59 +01:00
Harald Kuhr a16fce0749 Update README.md 2020-10-27 14:42:18 +01:00
Harald Kuhr 26e2fa0168 Fixed URL now works, ideally should point to correct branch... 2020-10-26 15:49:46 +01:00
Harald Kuhr 120deb3ad4 More standard way for getting vendor name and version info. 2020-10-26 15:40:38 +01:00
Harald Kuhr 0a9e2df5de NetBPM clean-up, fixes and better tests. 2020-10-23 19:25:54 +02:00
Harald Kuhr 6ffcb88872 Verify that RGB data is correct. 2020-10-16 18:21:40 +02:00
Harald Kuhr 960e764c7b Added test to verify how to write CMYK JPEG without ICC profile. 2020-10-16 18:19:08 +02:00
Harald Kuhr d88f27b251 Code clean-up. 2020-10-16 18:17:51 +02:00
Harald Kuhr e5b3e9755e Added missing tests. 2020-10-14 19:13:28 +02:00
Harald Kuhr 6c34fb211f ImageWriterAbstractTest refactorings. 2020-10-14 18:54:46 +02:00
Harald Kuhr 9fdbc3b1fc ImageReaderAbstractTest refactorings. 2020-10-14 17:06:35 +02:00
Harald Kuhr 622c6f40d4 Getting rid of more JUnit deprecation. 2020-10-13 19:29:58 +02:00
Harald Kuhr 107da17ca9 Dependabot broke my build... 2020-10-13 19:16:58 +02:00
Harald Kuhr f9871b73a3 Merge pull request #568 from haraldk/dependabot/maven/sandbox/junit-junit-4.13.1
Bump junit from 4.7 to 4.13.1 in /sandbox
2020-10-13 10:36:50 +02:00
Harald Kuhr 7605b646fe Merge pull request #567 from haraldk/dependabot/maven/common/junit-junit-4.13.1
Bump junit from 4.7 to 4.13.1 in /common
2020-10-13 10:36:37 +02:00
dependabot[bot] 19c62ac7da Bump junit from 4.7 to 4.13.1 in /sandbox
Bumps [junit](https://github.com/junit-team/junit4) from 4.7 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.7...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 08:36:22 +00:00
Harald Kuhr a5e4412d1a Merge pull request #566 from haraldk/dependabot/maven/imageio/junit-junit-4.13.1
Bump junit from 4.7 to 4.13.1 in /imageio
2020-10-13 10:36:20 +02:00
Harald Kuhr 651246566a Merge pull request #564 from haraldk/dependabot/maven/servlet/junit-junit-4.13.1
Bump junit from 4.7 to 4.13.1 in /servlet
2020-10-13 10:36:00 +02:00
Harald Kuhr fe8f854b17 Merge pull request #565 from haraldk/dependabot/maven/contrib/junit-junit-4.13.1
Bump junit from 4.7 to 4.13.1 in /contrib
2020-10-13 10:35:47 +02:00
dependabot[bot] a4d20a4af4 Bump junit from 4.7 to 4.13.1 in /common
Bumps [junit](https://github.com/junit-team/junit4) from 4.7 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.7...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 07:58:59 +00:00
dependabot[bot] 0643d5910a Bump junit from 4.7 to 4.13.1 in /imageio
Bumps [junit](https://github.com/junit-team/junit4) from 4.7 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.7...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 07:58:58 +00:00
dependabot[bot] c78a456985 Bump junit from 4.7 to 4.13.1 in /contrib
Bumps [junit](https://github.com/junit-team/junit4) from 4.7 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.7...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 07:49:27 +00:00
dependabot[bot] 27017576d3 Bump junit from 4.7 to 4.13.1 in /servlet
Bumps [junit](https://github.com/junit-team/junit4) from 4.7 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/compare/r4.7...r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 07:39:02 +00:00
Harald Kuhr f1810be10a X Window dump support. 2020-10-07 19:50:00 +02:00
Harald Kuhr 021aba1a98 Better output when debugging readers. 2020-10-07 19:47:09 +02:00
Harald Kuhr a0b68adff3 Removed work in progress... 2020-10-02 09:58:34 +02:00
Harald Kuhr fa4586663c Fixed a minor dependency issue. All test-jar dependencies now has correct test scope. 2020-10-02 09:41:39 +02:00
Harald Kuhr 623d13a517 Better PFM support. 2020-09-25 19:35:55 +02:00
Harald Kuhr a7ebc1b52f Code clean-up. 2020-09-24 17:12:12 +02:00
Harald Kuhr f54f4370c0 Added PNMImageWriterTest 2020-09-24 17:11:19 +02:00
Harald Kuhr 5040e9fe8a Add missing tests. 2020-09-24 15:07:40 +02:00
Harald Kuhr fc72cd34a1 Minor language fix. 2020-09-24 09:54:14 +02:00
Harald Kuhr 6d71a3d306 Added section about re-packaging and Shade plugin. 2020-09-24 09:51:30 +02:00
Harald Kuhr 86f8cf52a5 Comment fix 2020-08-07 16:36:08 +02:00
Harald Kuhr bda6544a5f #556 PICTImageReaderSpi no longer claim to decode known formats 2020-08-07 16:31:29 +02:00
Harald Kuhr 49c7cd1979 #466 TGA extension size fix for 3ds max files 2020-08-07 11:24:55 +02:00
Harald Kuhr 9dae58d5a6 Code clean-up. 2020-08-07 08:49:37 +02:00
Harald Kuhr ed14b97199 Update readme to 3.6. 2020-07-10 23:51:15 +02:00
Harald Kuhr b94135a91c [maven-release-plugin] prepare for next development iteration 2020-07-10 22:49:24 +02:00
712 changed files with 32972 additions and 48927 deletions
+1
View File
@@ -0,0 +1 @@
github: haraldk
+53
View File
@@ -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.
+20
View File
@@ -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
+13
View File
@@ -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"
+102
View File
@@ -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, 20 ]
runs-on: ${{ matrix.os }}
permissions:
checks: write
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.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@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- 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@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.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@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
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@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up Maven Central
uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.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)
-14
View File
@@ -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
+292 -449
View File
@@ -1,264 +1,80 @@
## Latest
[![CI](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml/badge.svg)](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.twelvemonkeys.imageio/imageio?color=slateblue)](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
[![Maven Snapshot](https://img.shields.io/nexus/s/com.twelvemonkeys.imageio/imageio?label=development&server=https%3A%2F%2Foss.sonatype.org&color=slateblue)](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys)
[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/haraldk76/100)
Master branch build status: [![Build Status](https://travis-ci.org/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.org/haraldk/TwelveMonkeys)
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).
![Logo](logo.png)
## 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 purpose of this project is to provide support for formats not covered by the JRE itself.
Support for formats is important, both to be able to read data found
The main goal 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
"in the wild", as well as to maintain access to data in legacy formats.
Because there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
As there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
----
## 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
* Indexed color, 1, 4 and 8 bit, including 4 and 8 bit RLE
* 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+.*
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
and make sure you use an updated and secure version.*
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
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.
The basic and simplest form of writing is:
if (!ImageIO.write(image, format, file)) {
// Handle image not written case
}
```java
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.
@@ -269,50 +85,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:
```java
// Create input stream
ImageInputStream input = ImageIO.createImageInputStream(file);
// Create input stream (in try-with-resource block to avoid leaks)
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 {
// Get the reader
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
reader.setInput(input);
if (!readers.hasNext()) {
throw new IllegalArgumentException("No reader for: " + file);
}
// Optionally, listen for read warnings, progress, etc.
reader.addIIOReadWarningListener(...);
reader.addIIOReadProgressListener(...);
ImageReader reader = readers.next();
ImageReadParam param = reader.getDefaultReadParam();
try {
reader.setInput(input);
// Optionally, control read settings like sub sampling, source region or destination etc.
param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Optionally, listen for read warnings, progress, etc.
reader.addIIOReadWarningListener(...);
reader.addIIOReadProgressListener(...);
// Finally read the image, using settings from param
BufferedImage image = reader.read(0, param);
ImageReadParam param = reader.getDefaultReadParam();
// Optionally, control read settings like sub sampling, source region or destination etc.
param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Finally read the image, using settings from param
BufferedImage image = reader.read(0, param);
// Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// ...
}
finally {
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
// Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// ...
}
finally {
// Close stream in finally block to avoid resource leaks
input.close();
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
}
```
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
@@ -324,86 +134,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:
```java
// Get the writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
// Get the writer
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
if (!writers.hasNext()) {
throw new IllegalArgumentException("No writer for: " + format);
}
ImageWriter writer = writers.next();
try {
// Create output stream
ImageOutputStream output = ImageIO.createImageOutputStream(file);
try {
writer.setOutput(output);
// Optionally, listen to progress, warnings, etc.
ImageWriteParam param = writer.getDefaultWriteParam();
// Optionally, control format specific settings of param (requires casting), or
// control generic write settings like sub sampling, source region, output type etc.
// Optionally, provide thumbnails and image/stream metadata
writer.write(..., new IIOImage(..., image, ...), param);
}
finally {
// Close stream in finally block to avoid resource leaks
output.close();
}
}
finally {
// Dispose writer in finally block to avoid memory leaks
writer.dispose();
if (!writers.hasNext()) {
throw new IllegalArgumentException("No writer for: " + format);
}
ImageWriter writer = writers.next();
try {
// Create output stream (in try-with-resource block to avoid leaks)
try (ImageOutputStream output = ImageIO.createImageOutputStream(file)) {
writer.setOutput(output);
// Optionally, listen to progress, warnings, etc.
ImageWriteParam param = writer.getDefaultWriteParam();
// Optionally, control format specific settings of param (requires casting), or
// control generic write settings like sub sampling, source region, output type etc.
// Optionally, provide thumbnails and image/stream metadata
writer.write(..., new IIOImage(..., image, ...), param);
}
}
finally {
// Dispose writer in finally block to avoid memory leaks
writer.dispose();
}
```
For more advanced usage, and information on how to use the ImageIO API, I suggest you read the
[Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
[Java Image I/O API Guide](https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
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).
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.
try (ImageInputStream stream = ImageIO.createImageInputStream(new File("image_with_path.jpg")) {
BufferedImage image = Paths.readClipped(stream);
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>
// Do something with the clipped image...
}
```
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
@@ -411,15 +191,15 @@ The library comes with a resampling (image resizing) operation, that contains ma
to provide excellent results at reasonable speed.
```java
import com.twelvemonkeys.image.ResampleOp;
import com.twelvemonkeys.image.ResampleOp;
...
...
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImage input = ...; // Image to resample
int width, height = ...; // new width/height
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
BufferedImage output = resampler.filter(input, null);
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS); // A good default filter, see class documentation for more info
BufferedImage output = resampler.filter(input, null);
```
#### Using the DiffusionDither
@@ -428,30 +208,30 @@ The library comes with a dithering operation, that can be used to convert `Buffe
Floyd-Steinberg error-diffusion dither.
```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();
BufferedImage output = ditherer.filter(input, null);
BufferedImageOp ditherer = new DiffusionDither();
BufferedImage output = ditherer.filter(input, null);
```
## 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
This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys`
folder, and issue the command below to build.
Build the project (using [Maven](http://maven.apache.org/download.cgi)):
Build the project (using [Maven](https://maven.apache.org/download.cgi)):
$ 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.
@@ -473,10 +253,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:
```java
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
while (readers.hasNext()) {
System.out.println("reader: " + readers.next());
}
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
while (readers.hasNext()) {
System.out.println("reader: " + readers.next());
}
```
The first line should print:
@@ -488,119 +268,162 @@ The first line should print:
To depend on the JPEG and TIFF plugin using Maven, add the following to your POM:
```xml
...
<dependencies>
...
<dependencies>
...
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.9.4</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.9.4</version>
</dependency>
<!--
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.
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
<!--
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.
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.9.4</version>
</dependency>
<!--
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
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-io-3.5.jar
twelvemonkeys-common-image-3.5.jar
twelvemonkeys-imageio-core-3.5.jar
twelvemonkeys-imageio-metadata-3.5.jar
twelvemonkeys-imageio-jpeg-3.5.jar
twelvemonkeys-imageio-tiff-3.5.jar
twelvemonkeys-common-lang-3.9.4.jar
twelvemonkeys-common-io-3.9.4.jar
twelvemonkeys-common-image-3.9.4.jar
twelvemonkeys-imageio-core-3.9.4.jar
twelvemonkeys-imageio-metadata-3.9.4.jar
twelvemonkeys-imageio-jpeg-3.9.4.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
##### 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-lang-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.5/common-lang-3.5.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-image-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.5/common-image-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.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.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-core-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.5/imageio-core-3.5.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-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.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-bmp-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.5/imageio-bmp-3.5.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-tiff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.5/imageio-tiff-3.5.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-psd-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.5/imageio-psd-3.5.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-iff-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.5/imageio-iff-3.5.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-pict-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.5/imageio-pict-3.5.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-tga-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.5/imageio-tga-3.5.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.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.5/imageio-thumbsdb-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-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-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-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-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-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-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-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-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.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.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.9.4/imageio-tga-3.9.4.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-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-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
* [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-3.5.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.5/servlet-3.5.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)
* [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar)
## 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.
Redistribution and use in source and binary forms, with or without
@@ -632,8 +455,9 @@ The project is distributed under the OSI approved [BSD license](http://opensourc
q: How do I use it?
a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need.
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
a: The easiest way is to build your own project using Maven, Gradle or other build tool with dependency management,
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?
@@ -651,22 +475,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.
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
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
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.
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:
- 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.
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.
+5
View File
@@ -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
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
@@ -123,6 +123,16 @@
<artifactId>imageio-tiff</artifactId>
<version>${project.version}</version>
</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 -->
<dependency>
+6 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
@@ -13,6 +13,10 @@
The TwelveMonkeys Common Image support
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.common.image</project.jpms.module.name>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
@@ -27,7 +31,7 @@
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId>
<version>6.2.4</version>
<version>6.6.9</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
@@ -34,7 +34,13 @@ import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
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}.
@@ -70,6 +76,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
}
@SuppressWarnings("ConstantConditions")
@Override
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
try {
@@ -80,10 +87,9 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
dst = createCompatibleDestImage(src, src.getColorModel());
}
Graphics2D g2d = null;
Graphics2D g2d = dst.createGraphics();
try {
g2d = dst.createGraphics();
int interpolationType = delegate.getInterpolationType();
if (interpolationType > 0) {
@@ -109,9 +115,7 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
return dst;
}
finally {
if (g2d != null) {
g2d.dispose();
}
g2d.dispose();
}
}
}
@@ -79,7 +79,7 @@ public final class BufferedImageFactory {
private int scanSize;
private ColorModel sourceColorModel;
private Hashtable sourceProperties; // ImageConsumer API dictates Hashtable
private Hashtable<?, ?> sourceProperties; // ImageConsumer API dictates Hashtable
private Object sourcePixels;
@@ -91,21 +91,21 @@ public final class BufferedImageFactory {
/**
* Creates a {@code BufferedImageFactory}.
* @param pSource the source image
* @throws IllegalArgumentException if {@code pSource == null}
* @param source the source image
* @throws IllegalArgumentException if {@code source == null}
*/
public BufferedImageFactory(final Image pSource) {
this(pSource != null ? pSource.getSource() : null);
public BufferedImageFactory(final Image source) {
this(source != null ? source.getSource() : null);
}
/**
* Creates a {@code BufferedImageFactory}.
* @param pSource the source image producer
* @throws IllegalArgumentException if {@code pSource == null}
* @param source the source image producer
* @throws IllegalArgumentException if {@code source == null}
*/
public BufferedImageFactory(final ImageProducer pSource) {
Validate.notNull(pSource, "source");
producer = pSource;
public BufferedImageFactory(final ImageProducer source) {
Validate.notNull(source, "source");
producer = source;
}
/**
@@ -155,44 +155,44 @@ public final class BufferedImageFactory {
/**
* 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
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();
}
x = pRegion.x;
y = pRegion.y;
width = pRegion.width;
height = pRegion.height;
x = region.x;
y = region.y;
width = region.width;
height = region.height;
}
/**
* Sets the source subsampling for the new image.
*
* @param pXSub horizontal subsampling factor
* @param pYSub vertical subsampling factor
* @param xSubsampling horizontal 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
if (xSub != pXSub || ySub != pYSub) {
if (xSub != xSubsampling || ySub != ySubsampling) {
dispose();
}
if (pXSub > 1) {
xSub = pXSub;
if (xSubsampling > 1) {
xSub = xSubsampling;
}
if (pYSub > 1) {
ySub = pYSub;
if (ySubsampling > 1) {
ySub = ySubsampling;
}
}
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException {
if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
// 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 (width > 0 && height > 0) {
width = (width + xSub - 1) / xSub;
@@ -207,38 +207,41 @@ public final class BufferedImageFactory {
// Start fetching
fetching = true;
readColorModelOnly = pColorModelOnly;
readColorModelOnly = colorModelOnly;
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
// Wait until the producer wakes us up, by calling imageComplete
while (fetching) {
try {
wait(200l);
wait(200L);
}
catch (InterruptedException e) {
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
}
}
if (consumerException != null) {
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
}
try {
if (consumerException != null) {
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
}
if (pColorModelOnly) {
createColorModel();
if (colorModelOnly) {
createColorModel();
}
else {
createBuffered();
}
}
else {
createBuffered();
finally {
// Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
}
}
}
private void createColorModel() {
colorModel = sourceColorModel;
// Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
}
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
freeResources();
if (buffered == null) {
throw new ImageConversionException("Could not create BufferedImage");
}
}
private void freeResources() {
@@ -280,27 +284,27 @@ public final class BufferedImageFactory {
/**
* Adds a progress listener to this factory.
*
* @param pListener the progress listener
* @param listener the progress listener
*/
public void addProgressListener(ProgressListener pListener) {
if (pListener == null) {
public void addProgressListener(ProgressListener listener) {
if (listener == null) {
return;
}
if (listeners == null) {
listeners = new CopyOnWriteArrayList<ProgressListener>();
listeners = new CopyOnWriteArrayList<>();
}
listeners.add(pListener);
listeners.add(listener);
}
/**
* Removes a progress listener from this factory.
*
* @param pListener the progress listener
* @param listener the progress listener
*/
public void removeProgressListener(ProgressListener pListener) {
if (pListener == null) {
public void removeProgressListener(ProgressListener listener) {
if (listener == null) {
return;
}
@@ -308,7 +312,7 @@ public final class BufferedImageFactory {
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}
* pixels. The conversion is done, by masking out the
* <em>higher 16 bits</em> of the {@code int}.
*
* <p>
* For any given {@code int}, the {@code short} value is computed as
* follows:
* <blockquote>{@code
* short value = (short) (intValue & 0x0000ffff);
* }</blockquote>
* </p>
*
* @param pPixels the pixel data to convert
* @return an array of {@code short}s, same lenght as {@code pPixels}
* @param inputPixels the pixel data to convert
* @return an array of {@code short}s, same length as {@code inputPixels}
*/
private static short[] toShortPixels(int[] pPixels) {
short[] pixels = new short[pPixels.length];
private static short[] toShortPixels(int[] inputPixels) {
short[] pixels = new short[inputPixels.length];
for (int i = 0; i < pixels.length; i++) {
pixels[i] = (short) (pPixels[i] & 0xffff);
pixels[i] = (short) (inputPixels[i] & 0xffff);
}
return pixels;
@@ -351,17 +356,17 @@ public final class BufferedImageFactory {
* @see BufferedImageFactory#addProgressListener
* @see BufferedImageFactory#removeProgressListener
*/
public static interface ProgressListener extends EventListener {
public interface ProgressListener extends EventListener {
/**
* Reports progress to this listener.
* Invoked by the {@code BufferedImageFactory} to report progress in
* the image decoding.
*
* @param pFactory the factory reporting the progress
* @param pPercentage the percentage of progress
* @param factory the factory reporting the progress
* @param percentage the percentage of progress
*/
void progress(BufferedImageFactory pFactory, float pPercentage);
void progress(BufferedImageFactory factory, float percentage);
}
private class Consumer implements ImageConsumer {
@@ -446,18 +451,18 @@ public final class BufferedImageFactory {
processProgress(pY + pHeight);
}
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, short[] pixels, int offset, int scanSize) {
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
// first passes the original color model through in setColorModel, then
// 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).
if (sourceColorModel != pModel) {
if (/*sourceColorModel == null ||*/ sourcePixels == null) {
sourceColorModel = pModel;
if (sourceColorModel != colorModel) {
if (sourcePixels == null) {
sourceColorModel = colorModel;
}
else {
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;
if (producer != null) {
producer.removeConsumer(this);
}
switch (pStatus) {
case ImageConsumer.IMAGEERROR:
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
break;
if (status == ImageConsumer.IMAGEERROR) {
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
}
synchronized (BufferedImageFactory.this) {
@@ -488,16 +492,18 @@ public final class BufferedImageFactory {
}
}
public void setColorModel(ColorModel pModel) {
setColorModelOnce(pModel);
@Override
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) {
width = pWidth - x;
width = w - x;
}
if (height < 0) {
height = pHeight - y;
height = h - y;
}
// 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
}
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
@Override
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) {
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
@Override
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
// 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 {
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
}
}
public void setProperties(Hashtable pProperties) {
sourceProperties = pProperties;
@Override
public void setProperties(Hashtable properties) {
sourceProperties = properties;
}
}
@@ -45,8 +45,8 @@ import java.awt.image.BufferedImage;
*/
public class BufferedImageIcon implements Icon {
private final BufferedImage image;
private int width;
private int height;
private final int width;
private final int height;
private final boolean fast;
public BufferedImageIcon(BufferedImage pImage) {
@@ -81,11 +81,10 @@ public class BufferedImageIcon implements Icon {
else {
//System.out.println("Scaling using interpolation");
Graphics2D g2 = (Graphics2D) g;
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, xform, null);
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
transform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(image, transform, null);
}
}
}
@@ -587,6 +587,7 @@ class IndexImage {
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
* This version will be removed in a later version of the API.
*/
@Deprecated
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
}
@@ -30,17 +30,26 @@
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.geom.AffineTransform;
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.List;
import static org.junit.Assert.*;
import javax.imageio.ImageTypeSpecifier;
import org.junit.Test;
/**
* AffineTransformOpTest.
@@ -101,6 +110,7 @@ public class AffineTransformOpTest {
private final int width = 30;
private final int height = 20;
private final double anchor = min(width, height) / 2.0;
@Test
public void testGetPoint2D() {
@@ -128,8 +138,8 @@ public class AffineTransformOpTest {
@Test
public void testFilterRotateBIStandard() {
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
for (Integer type : TYPES) {
BufferedImage image = new BufferedImage(width, height, type);
@@ -147,8 +157,8 @@ public class AffineTransformOpTest {
@Test
public void testFilterRotateBICustom() {
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
for (ImageTypeSpecifier spec : SPECS) {
BufferedImage image = spec.createBufferedImage(width, height);
@@ -197,8 +207,8 @@ public class AffineTransformOpTest {
@Test
public void testFilterRotateRasterStandard() {
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
for (Integer type : TYPES) {
Raster raster = new BufferedImage(width, height, type).getRaster();
@@ -221,8 +231,6 @@ public class AffineTransformOpTest {
fail("No result!");
}
else {
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
System.err.println("type: " + type);
continue;
}
}
@@ -240,8 +248,8 @@ public class AffineTransformOpTest {
@Test
public void testFilterRotateRasterCustom() {
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
for (ImageTypeSpecifier spec : SPECS) {
Raster raster = spec.createBufferedImage(width, height).getRaster();
@@ -264,8 +272,6 @@ public class AffineTransformOpTest {
fail("No result!");
}
else {
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
System.err.println("spec: " + spec);
continue;
}
}
@@ -34,14 +34,13 @@ import org.junit.Ignore;
import org.junit.Test;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.awt.color.*;
import java.awt.image.*;
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
@@ -260,9 +259,9 @@ public class BufferedImageFactoryTest {
// Listener should abort ASAP
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
if (pPercentage > 5) {
pFactory.abort();
public void progress(BufferedImageFactory factory, float percentage) {
if (percentage > 5) {
factory.abort();
}
}
});
@@ -343,7 +342,7 @@ public class BufferedImageFactoryTest {
VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener);
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) {
public void progress(BufferedImageFactory factory, float percentage) {
}
});
factory.getBufferedImage();
@@ -380,11 +379,11 @@ public class BufferedImageFactoryTest {
this.factory = factory;
}
public void progress(BufferedImageFactory pFactory, float pPercentage) {
assertEquals(factory, pFactory);
assertTrue(pPercentage >= progress && pPercentage <= 100f);
public void progress(BufferedImageFactory factory, float percentage) {
assertEquals(this.factory, factory);
assertTrue(percentage >= progress && percentage <= 100f);
progress = pPercentage;
progress = percentage;
}
+5 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -13,6 +13,10 @@
The TwelveMonkeys Common IO support
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.common.io</project.jpms.module.name>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
@@ -34,6 +34,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
/**
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
@@ -42,11 +43,8 @@ import java.io.OutputStream;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: FastByteArrayOutputStream.java#2 $
*/
// 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 {
/** 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
* size.
@@ -72,7 +70,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
}
@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) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException();
@@ -97,10 +95,8 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
private void growIfNeeded(int pNewCount) {
if (pNewCount > buf.length) {
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
byte newBuf[] = new byte[newSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
int newSize = Math.max(buf.length << 1, pNewCount);
buf = Arrays.copyOf(buf, newSize);
}
}
@@ -113,10 +109,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
// Non-synchronized version of toByteArray
@Override
public byte[] toByteArray() {
byte newBuf[] = new byte[count];
System.arraycopy(buf, 0, newBuf, 0, count);
return newBuf;
return Arrays.copyOf(buf, count);
}
/**
@@ -346,7 +346,7 @@ public final class FileUtil {
/**
* 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 '.'}.
* If the filename contains no period, {@code null} is returned.
*
@@ -65,6 +65,7 @@ import java.io.FilenameFilter;
* @see WildcardStringParser
* @deprecated
*/
@Deprecated
public class FilenameMaskFilter implements FilenameFilter {
// 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.DataInputStream#readLine()
*/
@Deprecated
public String readLine() throws IOException {
DataInputStream ds = new DataInputStream(in);
return ds.readLine();
@@ -41,21 +41,20 @@ import java.io.InputStream;
* underlying stream.
*
* @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 {
private long bytesLeft;
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 pLength maximum number of bytes to read drom this stream
* @param stream the underlying input stream
* @param length maximum number of bytes to read from this stream
*/
public SubStream(final InputStream pStream, final long pLength) {
super(Validate.notNull(pStream, "stream"));
bytesLeft = pLength;
public SubStream(final InputStream stream, final long length) {
super(Validate.notNull(stream, "stream"));
bytesLeft = length;
}
/**
@@ -73,13 +72,13 @@ public final class SubStream extends FilterInputStream {
@Override
public int available() throws IOException {
return (int) Math.min(super.available(), bytesLeft);
return (int) findMaxLen(super.available());
}
@Override
public void mark(int pReadLimit) {
super.mark(pReadLimit);// This either succeeds or does nothing...
markLimit = pReadLimit;
public void mark(int readLimit) {
super.mark(readLimit);// This either succeeds or does nothing...
markLimit = readLimit;
}
@Override
@@ -93,44 +92,42 @@ public final class SubStream extends FilterInputStream {
if (bytesLeft-- <= 0) {
return -1;
}
return super.read();
}
@Override
public final int read(byte[] pBytes) throws IOException {
return read(pBytes, 0, pBytes.length);
public int read(byte[] bytes) throws IOException {
return read(bytes, 0, bytes.length);
}
@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) {
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;
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.
*
* @param pLength the requested length
* @param length the requested length
* @return the maximum number of bytes to read
*/
private long findMaxLen(long pLength) {
if (bytesLeft < pLength) {
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;
private long findMaxLen(long length) {
return bytesLeft < length ? Math.max(bytesLeft, 0) : length;
}
}
@@ -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 $
*/
public final class DecoderStream extends FilterInputStream {
protected final ByteBuffer buffer;
protected final Decoder decoder;
private final ByteBuffer buffer;
private final Decoder decoder;
/**
* 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.
*
* @param pStream the underlying input stream.
* @param pDecoder the decoder that will be used to decode the underlying stream
* @param stream the underlying input stream.
* @param decoder the decoder that will be used to decode the underlying stream
*
* @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
this(pStream, pDecoder, 1024);
this(stream, decoder, 1024);
}
/**
* 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 pDecoder the decoder that will be used to decode the underlying stream
* @param pBufferSize the size of the decode buffer
* @param stream the underlying input stream.
* @param decoder the decoder that will be used to decode the underlying stream
* @param bufferSize the size of the decode buffer
*
* @see java.io.FilterInputStream#in
*/
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
super(pStream);
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
super(stream);
decoder = pDecoder;
buffer = ByteBuffer.allocate(pBufferSize);
this.decoder = decoder;
buffer = ByteBuffer.allocate(bufferSize);
buffer.flip();
}
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
return buffer.get() & 0xff;
}
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
if (pBytes == null) {
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
if (bytes == null) {
throw new NullPointerException();
}
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
else if ((offset < 0) || (offset > bytes.length) || (length < 0) ||
((offset + length) > bytes.length) || ((offset + length) < 0)) {
throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length);
}
else if (pLength == 0) {
else if (length == 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 off = pOffset;
int off = offset;
while (pLength > count) {
while (length > count) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
break;
@@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream {
}
// Copy as many bytes as possible
int dstLen = Math.min(pLength - count, buffer.remaining());
buffer.get(pBytes, off, dstLen);
int dstLen = Math.min(length - count, buffer.remaining());
buffer.get(bytes, off, dstLen);
// Update offset (rest)
off += dstLen;
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
return count;
}
public long skip(final long pLength) throws IOException {
public long skip(final long length) throws IOException {
// End of file?
if (!buffer.hasRemaining()) {
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;
while (total < pLength) {
while (total < length) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
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
int skipped = (int) Math.min(pLength - total, buffer.remaining());
int skipped = (int) Math.min(length - total, buffer.remaining());
buffer.position(buffer.position() + skipped);
total += skipped;
}
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
*
* @throws IOException if an I/O error occurs
*/
protected int fill() throws IOException {
private int fill() throws IOException {
buffer.clear();
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 $
*/
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;
protected final ByteBuffer buffer;
private final ByteBuffer buffer;
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param pStream the underlying output stream
* @param pEncoder the encoder to use
* @param stream the underlying output stream
* @param encoder the encoder to use
*/
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
this(pStream, pEncoder, false);
public EncoderStream(final OutputStream stream, final Encoder encoder) {
this(stream, encoder, false);
}
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param pStream the underlying output stream
* @param pEncoder the encoder to use
* @param pFlushOnWrite if {@code true}, calls to the byte-array
* @param stream the underlying output stream
* @param encoder the encoder to use
* @param flushOnWrite if {@code true}, calls to the byte-array
* {@code write} methods will automatically flush the buffer.
*/
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
super(pStream);
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
super(stream);
encoder = pEncoder;
flushOnWrite = pFlushOnWrite;
this.encoder = encoder;
this.flushOnWrite = flushOnWrite;
buffer = ByteBuffer.allocate(1024);
buffer.flip();
}
public void close() throws IOException {
@@ -104,33 +102,33 @@ public final class EncoderStream extends FilterOutputStream {
}
}
public final void write(final byte[] pBytes) throws IOException {
write(pBytes, 0, pBytes.length);
public void write(final byte[] bytes) throws IOException {
write(bytes, 0, bytes.length);
}
// 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
// that the encoder can't buffer. In that case, the encoder should probably
// tell the EncoderStream how large buffer it prefers...
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (!flushOnWrite && pLength < buffer.remaining()) {
// tell the EncoderStream how large buffer it prefers...
public void write(final byte[] values, final int offset, final int length) throws IOException {
if (!flushOnWrite && length < buffer.remaining()) {
// Buffer data
buffer.put(pBytes, pOffset, pLength);
buffer.put(values, offset, length);
}
else {
// Encode data already in the buffer
encodeBuffer();
// 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()) {
encodeBuffer(); // Resets bufferPos to 0
}
buffer.put((byte) pByte);
buffer.put((byte) value);
}
}
@@ -29,6 +29,9 @@
package com.twelvemonkeys.xml;
import java.io.OutputStream;
import java.io.Writer;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMImplementationList;
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.LSSerializer;
import java.io.OutputStream;
import java.io.Writer;
/**
* {@code DOMImplementationLS} backed implementation.
*
@@ -88,17 +88,6 @@ public final class DOMSerializer {
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
* readability.
@@ -169,13 +158,7 @@ public final class DOMSerializer {
try {
return DOMImplementationRegistry.newInstance();
}
catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
catch (InstantiationException e) {
throw new IllegalStateException(e);
}
catch (IllegalAccessException e) {
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
@@ -30,16 +30,23 @@
package com.twelvemonkeys.xml;
import com.twelvemonkeys.lang.StringUtil;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Date;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import com.twelvemonkeys.lang.StringUtil;
/**
* 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) {
pBuilder.append(pString.substring(pStart, pEnd));
pBuilder.append(pString, pStart, pEnd);
pBuilder.append(pEntity);
return pEnd + 1;
}
@@ -527,8 +534,7 @@ public class XMLSerializer {
builder = factory.newDocumentBuilder();
}
catch (ParserConfigurationException e) {
//noinspection ThrowableInstanceNeverThrown BOGUS
throw (IOException) new IOException(e.getMessage()).initCause(e);
throw new IOException(e);
}
DOMImplementation dom = builder.getDOMImplementation();
@@ -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.lang.ObjectAbstractTest;
import org.junit.Test;
import java.io.*;
import java.util.Arrays;
import java.util.Random;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertArrayEquals;
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];
RANDOM.nextBytes(bytes);
return bytes;
@@ -82,9 +82,8 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
private void runStreamTest(final int pLength) throws Exception {
byte[] data = createData(pLength);
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
int off = (data.length + 1) / 2;
out.write(data, 0, off);
@@ -92,9 +91,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
out.write(data, off, data.length - off);
}
}
finally {
out.close();
}
byte[] encoded = outBytes.toByteArray();
@@ -102,7 +98,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
// System.err.println("encoded: " + Arrays.toString(encoded));
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());
outBytes = new ByteArrayOutputStream();
@@ -116,7 +112,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
}
decoded = outBytes.toByteArray();
assertTrue(Arrays.equals(data, decoded));
assertArrayEquals(data, decoded);
}
@Test
@@ -129,10 +125,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
e.printStackTrace();
fail(e.getMessage() + ": " + i);
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage() + ": " + i);
}
}
for (int i = 100; i < 2000; i += 250) {
@@ -143,10 +135,6 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
e.printStackTrace();
fail(e.getMessage() + ": " + i);
}
catch (Exception e) {
e.printStackTrace();
fail(e.getMessage() + ": " + i);
}
}
for (int i = 2000; i < 80000; i += 1000) {
@@ -157,14 +145,8 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
e.printStackTrace();
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());
}
}
}
+5 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
@@ -13,4 +13,8 @@
The TwelveMonkeys Common Language support
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.common.lang</project.jpms.module.name>
</properties>
</project>
@@ -770,6 +770,7 @@ public final class StringUtil {
*/
/*public*/
@Deprecated
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
StringBuilder result = new StringBuilder();
@@ -903,7 +904,7 @@ public final class StringUtil {
}
catch (ParseException pe) {
// 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*/
@Deprecated
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
StringBuilder filteredString = new StringBuilder();
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 {
K mKey;
@@ -157,6 +157,7 @@ public class Time {
* @see #parseTime(String)
* @deprecated
*/
@Deprecated
public String toString(String pFormatStr) {
TimeFormat tf = new TimeFormat(pFormatStr);
@@ -174,6 +175,7 @@ public class Time {
* @see #toString(String)
* @deprecated
*/
@Deprecated
public static Time parseTime(String pStr) {
TimeFormat tf = TimeFormat.getInstance();
@@ -34,7 +34,7 @@ import java.io.Serializable;
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},
* or can be instantiated with any given {@code Map} as backing.
* <p>
@@ -67,7 +67,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
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.
*/
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)
*/
public V get(Object pKey) {
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey);
TimedEntry entry = (TimedEntry) entries.get(pKey);
if (entry == null) {
return null;
@@ -236,7 +236,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* {@code null} values.
*/
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;
if (entry == null) {
@@ -272,7 +272,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
* {@code null} values.
*/
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;
}
@@ -284,13 +284,12 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
init();
}
/*protected*/ TimedEntry<K, V> createEntry(K pKey, V pValue) {
return new TimedEntry<K, V>(pKey, pValue);
/*protected*/ TimedEntry createEntry(K pKey, V pValue) {
return new TimedEntry(pKey, pValue);
}
/**
* Removes any expired mappings.
*
*/
protected void removeExpiredEntries() {
// Remove any expired elements
@@ -312,7 +311,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
long next = Long.MAX_VALUE;
nextExpiryTime = next; // Avoid multiple runs...
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();
if (expires < next) {
@@ -376,7 +375,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
while (mNext == null && mIterator.hasNext()) {
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)) {
// 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
*/
private class TimedEntry<K, V> extends BasicEntry<K, V> {
private class TimedEntry extends BasicEntry<K, V> {
private long mTimestamp;
TimedEntry(K pKey, V pValue) {
super(pKey, pValue);
mTimestamp = System.currentTimeMillis();
updateTimestamp();
}
public V setValue(V pValue) {
mTimestamp = System.currentTimeMillis();
updateTimestamp();
return super.setValue(pValue);
}
private void updateTimestamp() {
mTimestamp = System.currentTimeMillis();
long expires = expires();
if (expires < nextExpiryTime) {
nextExpiryTime = expires;
}
}
final boolean isExpired() {
return isExpiredBy(System.currentTimeMillis());
}
@@ -111,6 +111,7 @@ import java.io.PrintStream;
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
* @deprecated Will probably be removed in the near future
*/
@Deprecated
public class WildcardStringParser {
// TODO: Get rid of this class
@@ -30,7 +30,7 @@
package com.twelvemonkeys.lang;
import org.junit.Test;
import static org.junit.Assert.*;
import java.awt.*;
import java.sql.Timestamp;
@@ -41,7 +41,7 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* StringUtilTestCase
@@ -76,24 +76,24 @@ public class StringUtilTest {
assertNull(StringUtil.valueOf(null));
}
@SuppressWarnings("ConstantConditions")
@Test
public void testToUpperCase() {
String str = StringUtil.toUpperCase(TEST_STRING);
assertNotNull(str);
assertEquals(TEST_STRING.toUpperCase(), str);
str = StringUtil.toUpperCase(null);
assertNull(str);
assertNull(StringUtil.toUpperCase(null));
}
@SuppressWarnings("ConstantConditions")
@Test
public void testToLowerCase() {
String str = StringUtil.toLowerCase(TEST_STRING);
assertNotNull(str);
assertEquals(TEST_STRING.toLowerCase(), str);
str = StringUtil.toLowerCase(null);
assertNull(str);
assertNull(StringUtil.toLowerCase(null));
}
@Test
@@ -113,6 +113,7 @@ public class StringUtilTest {
assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING}));
}
@SuppressWarnings("ConstantConditions")
@Test
public void testContains() {
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING));
@@ -145,6 +146,7 @@ public class StringUtilTest {
assertFalse(StringUtil.containsIgnoreCase(null, null));
}
@SuppressWarnings("ConstantConditions")
@Test
public void testContainsChar() {
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(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
}
@@ -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 + 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
}
@@ -516,7 +518,7 @@ public class StringUtilTest {
public void testCaptialize() {
assertNull(StringUtil.capitalize(null));
assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase()));
assertTrue(StringUtil.capitalize("abc").charAt(0) == 'A');
assertEquals('A', StringUtil.capitalize("abc").charAt(0));
}
@Test
@@ -552,13 +554,13 @@ public class StringUtilTest {
public void testToDateWithFormatString() {
Calendar cal = new GregorianCalendar();
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");
assertNotNull(date);
assertEquals(cal.getTime(), date);
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)");
assertNotNull(date);
assertEquals(cal.getTime(), date);
@@ -576,23 +578,23 @@ public class StringUtilTest {
public void testToDateWithFormat() {
Calendar cal = new GregorianCalendar();
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"));
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(2004, 4, 13, 23, 51);
date = StringUtil.toDate("13.5.04 23:51",
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO")));
cal.set(2004, Calendar.MAY, 13, 23, 51);
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO"));
date = StringUtil.toDate(format.format(cal.getTime()), format);
assertNotNull(date);
assertEquals(cal.getTime(), date);
cal.clear();
cal.set(Calendar.HOUR, 1);
cal.set(Calendar.MINUTE, 2);
date = StringUtil.toDate("1:02 am",
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
format = new SimpleDateFormat("HH:mm");
date = StringUtil.toDate("1:02", format);
assertNotNull(date);
assertEquals(cal.getTime(), date);
}
@@ -601,10 +603,9 @@ public class StringUtilTest {
public void testToTimestamp() {
Calendar cal = new GregorianCalendar();
cal.clear();
cal.set(1976, 2, 16, 21, 28, 4); // Month is 0-based
Date date = StringUtil.toTimestamp("1976-03-16 21:28:04");
cal.set(1976, Calendar.MARCH, 16, 21, 28, 4); // Month is 0-based
Timestamp date = StringUtil.toTimestamp("1976-03-16 21:28:04");
assertNotNull(date);
assertTrue(date instanceof Timestamp);
assertEquals(cal.getTime(), date);
}
@@ -821,7 +822,7 @@ public class StringUtilTest {
assertTrue(StringUtil.isNumber("12345"));
assertTrue(StringUtil.isNumber(TEST_INTEGER.toString()));
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(TEST_STRING));
}
@@ -831,7 +832,7 @@ public class StringUtilTest {
assertTrue(StringUtil.isNumber("-12345"));
assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString()));
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('-' + TEST_STRING));
}
@@ -557,7 +557,7 @@ public class TimeoutMapTest extends MapAbstractTest {
// NOTE: Only wait fist time, to avoid slooow tests
synchronized (this) {
try {
wait(60l);
wait(60L);
}
catch (InterruptedException e) {
}
@@ -591,7 +591,7 @@ public class TimeoutMapTest extends MapAbstractTest {
try {
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(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
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
@@ -47,7 +47,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
@@ -65,7 +65,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -2,6 +2,7 @@ package com.twelvemonkeys.contrib.exif;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import org.w3c.dom.NodeList;
import javax.imageio.IIOImage;
@@ -29,48 +30,68 @@ import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
public class EXIFUtilities {
/**
* 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}
* @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.
*/
public static IIOImage readWithOrientation(final URL input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream);
}
}
/**
* 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}
* @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.
*/
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream);
}
}
/**
* 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}
* @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.
*/
public static IIOImage readWithOrientation(final File input) throws IOException {
try (ImageInputStream stream = ImageIO.createImageOutputStream(input)) {
try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
return readWithOrientation(stream);
}
}
/**
* 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}
* @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.
*/
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
@@ -82,12 +103,11 @@ public class EXIFUtilities {
ImageReader reader = readers.next();
try {
reader.setInput(input, true, false);
IIOImage image = reader.readAll(0, reader.getDefaultReadParam());
BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage());
image.setRenderedImage(applyOrientation(bufferedImage, findImageOrientation(image.getMetadata()).value()));
IIOMetadata metadata = reader.getImageMetadata(0);
BufferedImage bufferedImage = applyOrientation(reader.read(0), findImageOrientation(metadata).value());
return image;
return new IIOImage(bufferedImage, null, metadata);
}
finally {
reader.dispose();
@@ -123,9 +143,15 @@ public class EXIFUtilities {
for (String arg : args) {
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);
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
Orientation orientation = findImageOrientation(image.getMetadata());
@@ -31,8 +31,9 @@
package com.twelvemonkeys.contrib.tiff;
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 org.junit.Assert;
import org.junit.Test;
import org.w3c.dom.Node;
@@ -154,7 +155,7 @@ public class TIFFUtilitiesTest {
reader.setInput(checkTest1);
for (int i = 0; i < 3; 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();
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
}
@@ -171,7 +172,7 @@ public class TIFFUtilitiesTest {
reader.setInput(checkTest2);
for (int i = 0; i < 3; 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();
Assert.assertEquals(orientation, i == 1
? TIFFExtension.ORIENTATION_BOTRIGHT
+18 -15
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -14,6 +14,12 @@
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
for more information.]]>
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
<batik.version>1.16</batik.version>
</properties>
<build>
<plugins>
<plugin>
@@ -21,9 +27,9 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
true
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
</systemPropertyVariables>
</configuration>
</plugin>
@@ -39,6 +45,14 @@
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.13.0</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -61,13 +75,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>xmlgraphics-commons</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-anim</artifactId>
@@ -91,7 +98,7 @@
<!--
There seems to be some weirdness in the
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>
<exclusion>
@@ -101,8 +108,4 @@
</exclusions>
</dependency>
</dependencies>
<properties>
<batik.version>1.12</batik.version>
</properties>
</project>
@@ -34,10 +34,10 @@ import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument;
import org.apache.batik.bridge.*;
import org.apache.batik.css.parser.CSSLexicalUnit;
import org.apache.batik.dom.util.DOMUtilities;
import org.apache.batik.ext.awt.image.GraphicsUtil;
import org.apache.batik.gvt.CanvasGraphicsNode;
@@ -45,11 +45,14 @@ import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
import org.apache.batik.gvt.renderer.ImageRenderer;
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.util.ParsedURL;
import org.apache.batik.util.SVGConstants;
import org.apache.batik.xml.LexicalUnits;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement;
@@ -59,10 +62,8 @@ import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -76,12 +77,13 @@ import java.util.Map;
* @author Harald Kuhr
* @author Inpspired by code from the Batik Team
* @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 {
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 boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
@@ -132,6 +134,7 @@ public class SVGImageReader extends ImageReaderBase {
// Set ImageReadParams as hints
// Note: The cast to Map invokes a different method that preserves
// unset defaults, DO NOT REMOVE!
//noinspection rawtypes
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
}
@@ -146,29 +149,23 @@ public class SVGImageReader extends ImageReaderBase {
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
// Read in the image, using the Batik Transcoder
processImageStarted(pIndex);
BufferedImage image = rasterizer.getImage();
Graphics2D g = destination.createGraphics();
try {
processImageStarted(pIndex);
BufferedImage image = rasterizer.getImage();
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;
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
}
catch (TranscoderException e) {
Throwable cause = unwrapException(e);
throw new IIOException(cause.getMessage(), cause);
finally {
g.dispose();
}
processImageComplete();
return destination;
}
private static Throwable unwrapException(TranscoderException ex) {
@@ -183,11 +180,11 @@ public class SVGImageReader extends ImageReaderBase {
// Set dimensions
Dimension size = pParam.getSourceRenderSize();
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
Rectangle viewBox = rasterizer.getViewBox();
if (size == null) {
// SVG is not a pixel based format, but we'll scale it, according to
// the subsampling for compatibility
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
size = getSourceRenderSizeFromSubsamping(pParam, viewBox.getSize());
}
if (size != null) {
@@ -207,8 +204,8 @@ public class SVGImageReader extends ImageReaderBase {
}
else {
// Need to resize here...
double xScale = size.getWidth() / origSize.getWidth();
double yScale = size.getHeight() / origSize.getHeight();
double xScale = size.getWidth() / viewBox.getWidth();
double yScale = size.getHeight() / viewBox.getHeight();
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
@@ -216,7 +213,7 @@ public class SVGImageReader extends ImageReaderBase {
}
else if (size != null) {
// Allow non-uniform scaling
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
hints.put(ImageTranscoder.KEY_AOI, viewBox);
}
// Background color
@@ -231,7 +228,7 @@ public class SVGImageReader extends ImageReaderBase {
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
}
return null;
}
@@ -242,25 +239,16 @@ public class SVGImageReader extends ImageReaderBase {
public int getWidth(int pIndex) throws IOException {
checkBounds(pIndex);
try {
return rasterizer.getDefaultWidth();
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
return rasterizer.getDefaultWidth();
}
public int getHeight(int pIndex) throws IOException {
checkBounds(pIndex);
try {
return rasterizer.getDefaultHeight();
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
return rasterizer.getDefaultHeight();
}
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
}
@@ -271,12 +259,11 @@ public class SVGImageReader extends ImageReaderBase {
* and needs major refactoring!
* </p>
*/
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
private class Rasterizer extends SVGAbstractTranscoder {
private BufferedImage image;
private TranscoderInput transcoderInput;
private float defaultWidth;
private float defaultHeight;
private final Rectangle2D viewBox = new Rectangle2D.Float();
private final Dimension defaultSize = new Dimension();
private boolean initialized = false;
private SVGOMDocument document;
private String uri;
@@ -289,7 +276,7 @@ public class SVGImageReader extends ImageReaderBase {
}
// 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
// ----
if (document != null) {
@@ -337,54 +324,66 @@ public class SVGImageReader extends ImageReaderBase {
// ----
SVGSVGElement rootElement = svgDoc.getRootElement();
// get the 'width' and 'height' attributes of the SVG document
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, rootElement);
// Get the viewBox
String viewBoxStr = rootElement.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
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 heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
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)){
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
if (!StringUtil.isEmpty(heightStr)) {
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
}
boolean hasWidth = defaultWidth > 0.0;
boolean hasHeight = defaultHeight > 0.0;
boolean hasWidth = width > 0.0;
boolean hasHeight = height > 0.0;
if (!hasWidth || !hasHeight) {
String viewBoxStr = rootElement.getAttributeNS
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
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 (!viewBox.isEmpty()) {
// If one dimension is given, calculate other by aspect ratio in viewBox
if (hasWidth) {
defaultHeight = defaultWidth * rect[3] / rect[2];
height = width * viewBox.getHeight() / viewBox.getWidth();
}
else if (hasHeight) {
defaultWidth = defaultHeight * rect[2] / rect[3];
width = height * viewBox.getWidth() / viewBox.getHeight();
}
else {
defaultWidth = rect[2];
defaultHeight = rect[3];
// ...or use viewBox if no dimension is given
width = viewBox.getWidth();
height = viewBox.getHeight();
}
}
else {
// No viewBox, just assume square size
if (hasHeight) {
defaultWidth = defaultHeight;
width = height;
}
else if (hasWidth) {
defaultHeight = defaultWidth;
height = width;
}
else {
// fallback to batik default sizes
defaultWidth = 400;
defaultHeight = 400;
// ...or finally fall back to Batik default sizes
width = 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
if (root != null) {
gvtRoot = root;
@@ -397,7 +396,7 @@ public class SVGImageReader extends ImageReaderBase {
ctx = null;
}
private BufferedImage readImage() throws TranscoderException {
private BufferedImage readImage() throws IOException {
init();
if (abortRequested()) {
@@ -422,7 +421,8 @@ public class SVGImageReader extends ImageReaderBase {
}
if (gvtRoot == null) {
throw exception;
Throwable cause = unwrapException(exception);
throw new IIOException(cause.getMessage(), cause);
}
}
ctx = context;
@@ -440,7 +440,7 @@ public class SVGImageReader extends ImageReaderBase {
// ----
setImageSize(defaultWidth, defaultHeight);
setImageSize(defaultSize.width, defaultSize.height);
if (abortRequested()) {
processReadAborted();
@@ -454,18 +454,17 @@ public class SVGImageReader extends ImageReaderBase {
try {
Px = ViewBox.getViewTransform(ref, root, width, height, null);
}
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.
// we want to keep the document size ratio
float xscale, yscale;
xscale = width / defaultWidth;
yscale = height / defaultHeight;
xscale = width / defaultSize.width;
yscale = height / defaultSize.height;
float scale = Math.min(xscale, yscale);
Px = AffineTransform.getScaleInstance(scale, scale);
}
@@ -515,7 +514,7 @@ public class SVGImageReader extends ImageReaderBase {
}
}
catch (BridgeException ex) {
throw new TranscoderException(ex);
throw new IIOException(ex.getMessage(), ex);
}
this.root = gvtRoot;
@@ -584,9 +583,7 @@ public class SVGImageReader extends ImageReaderBase {
return dest;
}
catch (Exception ex) {
TranscoderException exception = new TranscoderException(ex.getMessage());
exception.initCause(ex);
throw exception;
throw new IIOException(ex.getMessage(), ex);
}
finally {
if (context != null) {
@@ -595,7 +592,7 @@ public class SVGImageReader extends ImageReaderBase {
}
}
private synchronized void init() throws TranscoderException {
private synchronized void init() throws IIOException {
if (!initialized) {
if (transcoderInput == null) {
throw new IllegalStateException("input == null");
@@ -603,11 +600,17 @@ public class SVGImageReader extends ImageReaderBase {
initialized = true;
super.transcode(transcoderInput, null);
try {
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) {
image = readImage();
}
@@ -615,14 +618,19 @@ public class SVGImageReader extends ImageReaderBase {
return image;
}
int getDefaultWidth() throws TranscoderException {
int getDefaultWidth() throws IOException {
init();
return (int) Math.ceil(defaultWidth);
return defaultSize.width;
}
int getDefaultHeight() throws TranscoderException {
int getDefaultHeight() throws IOException {
init();
return (int) Math.ceil(defaultHeight);
return defaultSize.height;
}
Rectangle getViewBox() throws IOException {
init();
return viewBox.getBounds();
}
void setInput(final TranscoderInput pInput) {
@@ -655,7 +663,7 @@ public class SVGImageReader extends ImageReaderBase {
if (allowExternalResources) {
return super.getExternalResourceSecurity(resourceURL, docURL);
}
return new NoLoadExternalResourceSecurity();
return new EmbededExternalResourceSecurity(resourceURL);
}
}
}
@@ -1,141 +1,143 @@
/*
* Copyright (c) 2008, 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.plugins.wmf;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
/**
* WMFImageReader class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @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: Consider using temp file instead of in-memory stream
public final class WMFImageReader extends ImageReaderBase {
private SVGImageReader reader = null;
public WMFImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
protected void resetMembers() {
if (reader != null) {
reader.dispose();
}
reader = null;
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
init();
processImageStarted(pIndex);
BufferedImage image = reader.read(pIndex, pParam);
if (abortRequested()) {
processReadAborted();
return image;
}
processImageProgress(100f);
processImageComplete();
return image;
}
private synchronized void init() throws IOException {
// Need the extra test, to avoid throwing an IOException from the Transcoder
if (imageInput == null) {
throw new IllegalStateException("input == null");
}
if (reader == null) {
WMFTranscoder transcoder = new WMFTranscoder();
ByteArrayOutputStream output = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(output, "UTF8");
try {
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
TranscoderOutput out = new TranscoderOutput(writer);
// TODO: Transcodinghints?
transcoder.transcode(in, out);
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
reader = new SVGImageReader(getOriginatingProvider());
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
}
}
@Override
public ImageReadParam getDefaultReadParam() {
return new SVGReadParam();
}
public int getWidth(int pIndex) throws IOException {
init();
return reader.getWidth(pIndex);
}
public int getHeight(int pIndex) throws IOException {
init();
return reader.getHeight(pIndex);
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
init();
return reader.getImageTypes(pImageIndex);
}
}
/*
* Copyright (c) 2008, 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.plugins.wmf;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.image.*;
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;
/**
* WMFImageReader class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @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...
public final class WMFImageReader extends ImageReaderBase {
private SVGImageReader reader = null;
public WMFImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
protected void resetMembers() {
if (reader != null) {
reader.dispose();
}
reader = null;
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
init();
processImageStarted(pIndex);
BufferedImage image = reader.read(pIndex, pParam);
if (abortRequested()) {
processReadAborted();
return image;
}
processImageProgress(100f);
processImageComplete();
return image;
}
private void init() throws IOException {
// Need the extra test, to avoid throwing an IOException from the Transcoder
if (imageInput == null) {
throw new IllegalStateException("input == null");
}
if (reader == null) {
WMFTranscoder transcoder = new WMFTranscoder();
ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
TranscoderOutput out = new TranscoderOutput(writer);
// TODO: Transcodinghints?
transcoder.transcode(in, out);
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
reader = new SVGImageReader(getOriginatingProvider());
reader.setInput(new ByteArrayImageInputStream(output.toByteArray()));
}
}
@Override
public ImageReadParam getDefaultReadParam() {
return new SVGReadParam();
}
public int getWidth(int pIndex) throws IOException {
init();
return reader.getWidth(pIndex);
}
public int getHeight(int pIndex) throws IOException {
init();
return reader.getHeight(pIndex);
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
init();
return reader.getImageTypes(pImageIndex);
}
}
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
@@ -42,9 +43,7 @@ import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImagingOpException;
import java.io.File;
import java.awt.image.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
@@ -53,9 +52,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
/**
@@ -67,7 +67,10 @@ import static org.mockito.Mockito.*;
*/
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
@Override
protected ImageReaderSpi createProvider() {
return new SVGImageReaderSpi();
}
protected List<TestData> getTestData() {
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() {
return Collections.singletonList("svg");
}
@@ -111,8 +101,6 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
public void testScaleViewBox() throws IOException {
URL svgUrl = getClassLoaderResource("/svg/quadrants.svg");
File tmpDir = new File(System.getProperty("java.io.tmpdir"));
SVGImageReader reader = createReader();
SVGReadParam param = new SVGReadParam();
@@ -160,11 +148,11 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
@Test
@Override
public void testReadWithSizeParam() {
public void testReadWithSizeParam() throws IOException {
try {
super.testReadWithSizeParam();
}
catch (AssertionError failure) {
catch (AssertionError | IOException failure) {
Throwable cause = failure;
while (cause.getCause() != null) {
@@ -206,12 +194,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
reader.setInput(redSquare.getInputStream());
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);
reader.setInput(blueSquare.getInputStream());
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
@@ -237,7 +225,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
assertEquals(500, image.getHeight());
// CSS and embedded resources all go!
verifyZeroInteractions(listener);
verifyNoInteractions(listener);
}
finally {
reader.dispose();
@@ -278,7 +266,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
assertEquals(500, image.getHeight());
// No more warnings now that the base URI is set
verifyZeroInteractions(listener);
verifyNoInteractions(listener);
}
finally {
reader.dispose();
@@ -299,7 +287,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
params.setAllowExternalResources(true);
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) {
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)
public void testDisallowedExternalResources() throws URISyntaxException, IOException {
// system-property set to true in surefire-plugin-settings in the pom
@@ -332,4 +339,69 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
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();
}
}
}
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.plugins.wmf;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
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$
*/
public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader> {
private WMFImageReaderSpi provider = new WMFImageReaderSpi();
@Override
protected ImageReaderSpi createProvider() {
return new WMFImageReaderSpi();
}
protected List<TestData> getTestData() {
return Collections.singletonList(
@@ -57,27 +61,17 @@ public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader>
);
}
protected ImageReaderSpi createProvider() {
return provider;
}
@Override
protected WMFImageReader createReader() {
return new WMFImageReader(createProvider());
}
protected Class<WMFImageReader> getReaderClass() {
return WMFImageReader.class;
}
protected List<String> getFormatNames() {
return Collections.singletonList("wmf");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("wmf", "emf");
}
@Override
protected List<String> getMIMETypes() {
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

+6 -1
View File
@@ -4,12 +4,16 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
<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>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
@@ -19,6 +23,7 @@
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -39,7 +39,11 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
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.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
@@ -47,13 +51,13 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.*;
import java.awt.image.*;
import java.io.DataInput;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
/**
@@ -77,7 +81,7 @@ public final class BMPImageReader extends ImageReaderBase {
super(new BMPImageReaderSpi());
}
protected BMPImageReader(final ImageReaderSpi pProvider) {
BMPImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
@@ -125,6 +129,10 @@ public final class BMPImageReader extends ImageReaderBase {
// Read DIB header
header = DIBHeader.read(imageInput);
if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) {
throw new IIOException("Invalid pixel offset: " + pixelOffset);
}
}
}
@@ -196,7 +204,7 @@ public final class BMPImageReader extends ImageReaderBase {
checkBounds(pImageIndex);
// 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(pImageIndex)).iterator();
}
@Override
@@ -207,50 +215,55 @@ public final class BMPImageReader extends ImageReaderBase {
throw new IIOException("Multiple planes not supported");
}
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap());
try {
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
return ImageTypeSpecifiers.createFromIndexColorModel(readColorMap());
case 16:
if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_USHORT, false
);
}
case 16:
if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_USHORT, false
);
}
// Default if no mask is 555
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
// Default if no mask is 555
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
case 24:
if (header.getCompression() != DIB.COMPRESSION_RGB) {
throw new IIOException("Unsupported compression for RGB: " + header.getCompression());
}
case 24:
if (header.getCompression() != DIB.COMPRESSION_RGB) {
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:
if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_INT, false
);
}
case 32:
if (header.hasMasks()) {
return ImageTypeSpecifiers.createPacked(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
header.masks[0], header.masks[1], header.masks[2], header.masks[3],
DataBuffer.TYPE_INT, false
);
}
// Default if no mask
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
// Default if no mask
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
case 0:
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
return initReaderDelegate(header.getCompression()).getRawImageType(0);
}
default:
throw new IIOException("Unsupported bit count: " + header.getBitCount());
case 0:
if (header.getCompression() == DIB.COMPRESSION_JPEG || header.getCompression() == DIB.COMPRESSION_PNG) {
return initReaderDelegate(header.getCompression()).getRawImageType(0);
}
default:
throw new IIOException("Unsupported bit count: " + header.getBitCount());
}
}
catch (IllegalArgumentException e) {
throw new IIOException(e.getMessage(), e);
}
}
@@ -349,14 +362,18 @@ public final class BMPImageReader extends ImageReaderBase {
processImageStarted(imageIndex);
for (int y = 0; y < height; y++) {
switch (header.getBitCount()) {
int bitCount = header.getBitCount();
switch (bitCount) {
case 1:
case 2:
case 4:
case 8:
case 24:
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;
case 16:
@@ -370,7 +387,7 @@ public final class BMPImageReader extends ImageReaderBase {
break;
default:
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
throw new AssertionError("Unsupported pixel depth: " + bitCount);
}
processImageProgress(100f * y / height);
@@ -396,6 +413,13 @@ public final class BMPImageReader extends ImageReaderBase {
private ImageReader initReaderDelegate(int compression) throws IOException {
ImageReader reader = getImageReaderDelegate(compression);
reader.reset();
// Install listener
ListenerDelegator listenerDelegator = new ListenerDelegator();
reader.addIIOReadWarningListener(listenerDelegator);
reader.addIIOReadProgressListener(listenerDelegator);
reader.addIIOReadUpdateListener(listenerDelegator);
imageInput.seek(pixelOffset);
reader.setInput(new SubImageInputStream(imageInput, header.getImageSize()));
@@ -436,12 +460,6 @@ public final class BMPImageReader extends ImageReaderBase {
ImageReader reader = readers.next();
// Install listener
ListenerDelegator listenerDelegator = new ListenerDelegator();
reader.addIIOReadWarningListener(listenerDelegator);
reader.addIIOReadProgressListener(listenerDelegator);
reader.addIIOReadUpdateListener(listenerDelegator);
// Cache for later use
switch (compression) {
case DIB.COMPRESSION_JPEG:
@@ -466,9 +484,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,
int bitsPerSample, int samplesPerPixel,
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 (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);
return;
@@ -478,24 +501,20 @@ public final class BMPImageReader extends ImageReaderBase {
// Subsample horizontal
if (xSub != 1) {
for (int x = 0; x < srcRegion.width / xSub; x++) {
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
}
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
destChannel.setDataElements(0, dstY, srcChannel);
}
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 {
// 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 (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);
return;
@@ -515,19 +534,17 @@ public final class BMPImageReader extends ImageReaderBase {
}
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
destChannel.setDataElements(0, dstY, srcChannel);
}
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 {
// 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 (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);
return;
@@ -542,13 +559,7 @@ public final class BMPImageReader extends ImageReaderBase {
}
}
if (header.topDown) {
destChannel.setDataElements(0, y, srcChannel);
} else {
// Flip into position
int dstY = (height - 1 - y - srcRegion.y) / ySub;
destChannel.setDataElements(0, dstY, srcChannel);
}
destChannel.setDataElements(0, dstY, srcChannel);
}
// TODO: Candidate util method
@@ -619,7 +630,8 @@ public final class BMPImageReader extends ImageReaderBase {
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();
BMPImageReader reader = new BMPImageReader(provider);
@@ -672,7 +684,7 @@ 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 {
throw (T) pThrowable;
}
@@ -116,7 +116,7 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
}
}
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
public ImageReader createReaderInstance(final Object pExtension) {
return new BMPImageReader(this);
}
@@ -29,9 +29,10 @@
*/
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.lang.Validate;
import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* Describes a bitmap structure.
@@ -47,14 +48,11 @@ abstract class BitmapDescriptor {
protected BitmapMask mask;
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
Validate.notNull(pEntry, "entry");
Validate.notNull(pHeader, "header");
entry = pEntry;
header = pHeader;
entry = notNull(pEntry, "entry");;
header = notNull(pHeader, "header");
}
abstract public BufferedImage getImage();
abstract public BufferedImage getImage() throws IOException;
public final int getWidth() {
return entry.getWidth();
@@ -163,6 +163,7 @@ class BitmapIndexed extends BitmapDescriptor {
return transparent;
}
@Override
public BufferedImage getImage() {
if (image == null) {
image = createImageIndexed();
@@ -46,6 +46,7 @@ class BitmapRGB extends BitmapDescriptor {
super(pEntry, pHeader);
}
@Override
public BufferedImage getImage() {
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
// might still have bitmask, but we don't read or use it.
@@ -31,6 +31,9 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.IIOException;
/**
* Represents bitmap structures we can't read.
@@ -42,13 +45,14 @@ import java.awt.image.BufferedImage;
class BitmapUnsupported extends BitmapDescriptor {
private String message;
public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) {
super(pEntry, null);
public BitmapUnsupported(final DirectoryEntry pEntry, DIBHeader header, final String pMessage) {
super(pEntry, header);
message = pMessage;
}
public BufferedImage getImage() {
throw new IllegalStateException(message);
@Override
public BufferedImage getImage() throws IOException {
throw new IIOException(message);
}
}
@@ -30,11 +30,12 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import javax.imageio.IIOException;
/**
* Represents the DIB (Device Independent Bitmap) Information header structure.
*
@@ -213,7 +214,7 @@ abstract class DIBHeader {
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
width = pStream.readUnsignedShort();
height = pStream.readUnsignedShort();
height = pStream.readShort();
if (height < 0) {
height = -height;
@@ -240,6 +241,7 @@ abstract class DIBHeader {
* @see <a href="http://www.fileformat.info/format/os2bmp/egff.htm">OS/2 Bitmap File Format Summary</a>
*/
static final class BitmapCoreHeaderV2 extends DIBHeader {
@SuppressWarnings("unused")
protected void read(final int pSize, final DataInput pStream) throws IOException {
if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));
@@ -362,7 +364,11 @@ abstract class DIBHeader {
public String getBMPVersion() {
// 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";
}
}
@@ -30,16 +30,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.event.WindowAdapter;
@@ -48,8 +38,26 @@ import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.Map;
import java.util.WeakHashMap;
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.stream.ImageInputStream;
import javax.swing.*;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
/**
* ImageReader for Microsoft Windows ICO (icon) format.
@@ -287,7 +295,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
if (header.getCompression() != DIB.COMPRESSION_RGB) {
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression()));
descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported compression: %d", header.getCompression()));
}
else {
int bitCount = header.getBitCount();
@@ -315,7 +323,7 @@ abstract class DIBImageReader extends ImageReaderBase {
break;
default:
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount));
descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported bit count %d", bitCount));
}
}
@@ -355,7 +363,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 3);
int width = adjustToPadding((pBitmap.getWidth() + 7) >> 3);
byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) {
@@ -389,7 +397,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding(pBitmap.getWidth() >> 1);
int width = adjustToPadding((pBitmap.getWidth() + 1) >> 1);
byte[] row = new byte[width];
for (int y = 0; y < pBitmap.getHeight(); y++) {
@@ -457,13 +465,12 @@ abstract class DIBImageReader extends ImageReaderBase {
}
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
// TODO: No idea if this actually works..
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555
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(
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
);
@@ -41,8 +41,8 @@ import java.util.Arrays;
* @version $Id: RLE4Decoder.java#1 $
*/
final class RLE4Decoder extends AbstractRLEDecoder {
final static int BIT_MASKS[] = {0xf0, 0x0f};
final static int BIT_SHIFTS[] = {4, 0};
final static int[] BIT_MASKS = {0xf0, 0x0f};
final static int[] BIT_SHIFTS = {4, 0};
public RLE4Decoder(final int width) {
super(width, 4);
@@ -94,7 +94,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 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) {
packed = checkEOF(stream.read());
}
@@ -111,7 +111,7 @@ final class RLE4Decoder extends AbstractRLEDecoder {
else {
// Encoded mode
// 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]);
srcX++;
}
@@ -94,7 +94,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// an additional padding byte is in the stream and must be skipped
boolean paddingByte = (byte2 % 2) != 0;
while (byte2-- > 0) {
while (byte2-- > 0 && srcX < row.length) {
row[srcX++] = (byte) checkEOF(stream.read());
}
@@ -107,7 +107,7 @@ final class RLE8Decoder extends AbstractRLEDecoder {
// Encoded mode
// Replicate byte2 as many times as byte1 says
byte value = (byte) byte2;
while (byte1-- > 0) {
while (byte1-- > 0 && srcX < row.length) {
row[srcX++] = value;
}
}
@@ -30,35 +30,43 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.xml.XMLSerializer;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeNoException;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.InOrder;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeNoException;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.xml.XMLSerializer;
/**
* BMPImageReaderTest
@@ -68,6 +76,12 @@ import static org.mockito.Mockito.*;
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader> {
@Override
protected ImageReaderSpi createProvider() {
return new BMPImageReaderSpi();
}
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
// BMP Suite "Good"
@@ -144,27 +158,17 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
);
}
protected ImageReaderSpi createProvider() {
return new BMPImageReaderSpi();
}
@Override
protected BMPImageReader createReader() {
return new BMPImageReader(createProvider());
}
protected Class<BMPImageReader> getReaderClass() {
return BMPImageReader.class;
}
protected List<String> getFormatNames() {
return Collections.singletonList("bmp");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("bmp", "rle");
}
@Override
protected List<String> getMIMETypes() {
return Collections.singletonList("image/bmp");
}
@@ -178,7 +182,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
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")) {
continue;
}
@@ -273,7 +277,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
}
@Test
public void testAddIIOReadProgressListenerCallbacksJPEG() {
public void testAddIIOReadProgressListenerCallbacksJPEG() throws IOException {
ImageReader reader = createReader();
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24jpeg.bmp"), new Dimension(127, 64));
reader.setInput(data.getInputStream());
@@ -291,12 +295,12 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
// At least imageStarted and imageComplete, plus any number of imageProgress
InOrder ordered = inOrder(listener);
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);
}
@Test
public void testAddIIOReadProgressListenerCallbacksPNG() {
public void testAddIIOReadProgressListenerCallbacksPNG() throws IOException {
ImageReader reader = createReader();
TestData data = new TestData(getClassLoaderResource("/bmpsuite/q/rgb24png.bmp"), new Dimension(127, 64));
reader.setInput(data.getInputStream());
@@ -314,12 +318,12 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
// At least imageStarted and imageComplete, plus any number of imageProgress
InOrder ordered = inOrder(listener);
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);
}
@Test
public void testMetadataEqualsJRE() throws IOException, URISyntaxException {
public void testMetadataEqualsJRE() throws IOException {
ImageReader jreReader;
try {
@SuppressWarnings("unchecked")
@@ -338,6 +342,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
for (TestData data : getTestData()) {
if (data.getInput().toString().contains("pal8offs")) {
// Skip: Contains extra bogus PaletteEntry nodes
continue;
}
@@ -354,9 +359,10 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage());
continue;
}
IIOMetadata jreMetadata = jreReader.getImageMetadata(0);
assertEquals(true, metadata.isStandardMetadataFormatSupported());
assertTrue(metadata.isStandardMetadataFormatSupported());
assertEquals(jreMetadata.getNativeMetadataFormatName(), metadata.getNativeMetadataFormatName());
assertArrayEquals(jreMetadata.getExtraMetadataFormatNames(), metadata.getExtraMetadataFormatNames());
@@ -366,6 +372,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
String absolutePath = data.toString();
String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12);
// TODO: blauesglas_16_bitmask444 fails BMP Version for 11+
Node expectedTree = jreMetadata.getAsTree(format);
Node actualTree = metadata.getAsTree(format);
@@ -379,7 +386,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
new XMLSerializer(expected, "UTF-8").serialize(expectedTree, 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;
}
@@ -424,6 +431,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
}
}
@SuppressWarnings("RedundantIfStatement")
private boolean excludeEqualValueTest(final Node expected) {
if (expected.getLocalName().equals("ImageSize")) {
// JRE metadata returns 0, even if known in reader...
@@ -2,10 +2,10 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
@@ -15,18 +15,15 @@ import java.util.List;
* @author last modified by : harald.kuhr$
* @version : BMPImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
*/
public class BMPImageWriterTest extends ImageWriterAbstractTest {
private final BMPImageWriterSpi provider = new BMPImageWriterSpi();
public class BMPImageWriterTest extends ImageWriterAbstractTest<BMPImageWriter> {
@Override
protected ImageWriter createImageWriter() {
return provider.createWriterInstance(null);
protected ImageWriterSpi createProvider() {
return new BMPImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
return Collections.singletonList(
new BufferedImage(10, 10, BufferedImage.TYPE_4BYTE_ABGR)
);
}
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
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$
*/
public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader> {
@Override
protected ImageReaderSpi createProvider() {
return new CURImageReaderSpi();
}
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
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
protected CURImageReader createReader() {
return new CURImageReader();
}
protected Class<CURImageReader> getReaderClass() {
return CURImageReader.class;
}
protected List<String> getFormatNames() {
return Collections.singletonList("cur");
}
@Override
protected List<String> getSuffixes() {
return Collections.singletonList("cur");
}
@Override
protected List<String> getMIMETypes() {
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);
// 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);
assertEquals(pExpected, hotspot);
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
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$
*/
public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader> {
@Override
protected ImageReaderSpi createProvider() {
return new ICOImageReaderSpi();
}
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(
@@ -75,27 +82,17 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
);
}
protected ImageReaderSpi createProvider() {
return new ICOImageReaderSpi();
}
@Override
protected ICOImageReader createReader() {
return new ICOImageReader();
}
protected Class<ICOImageReader> getReaderClass() {
return ICOImageReader.class;
}
protected List<String> getFormatNames() {
return Collections.singletonList("ico");
}
@Override
protected List<String> getSuffixes() {
return Collections.singletonList("ico");
}
@Override
protected List<String> getMIMETypes() {
return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon");
}
@@ -2,7 +2,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.Arrays;
@@ -15,12 +15,10 @@ import java.util.List;
* @author last modified by : harald.kuhr$
* @version : ICOImageWriterTest.java,v 1.0 25/06/2020 harald.kuhr Exp$
*/
public class ICOImageWriterTest extends ImageWriterAbstractTest {
private final ICOImageWriterSpi provider = new ICOImageWriterSpi();
public class ICOImageWriterTest extends ImageWriterAbstractTest<ICOImageWriter> {
@Override
protected ImageWriter createImageWriter() {
return provider.createWriterInstance(null);
protected ImageWriterSpi createProvider() {
return new ICOImageWriterSpi();
}
@Override
Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

+6 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
@@ -12,6 +12,10 @@
Photoshop Clipping Path Support.
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.clippath</project.jpms.module.name>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
@@ -21,6 +25,7 @@
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
@@ -39,6 +39,7 @@ import java.io.IOException;
*
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
*/
@Deprecated
public final class AdobePathBuilder {
private final AdobePathReader delegate;
@@ -56,6 +56,8 @@ import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
@@ -131,7 +133,13 @@ public final class Paths {
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
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
@@ -350,10 +358,10 @@ public final class Paths {
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF));
byte[] identfier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
byte[] data = new byte[identfier.length + 1 + pathResource.length];
System.arraycopy(identfier, 0, data, 0, identfier.length);
System.arraycopy(pathResource, 0, data, identfier.length + 1, pathResource.length);
byte[] identifier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
byte[] data = new byte[identifier.length + 1 + pathResource.length];
System.arraycopy(identifier, 0, data, 0, identifier.length);
System.arraycopy(pathResource, 0, data, identifier.length + 1, pathResource.length);
unknown.setUserObject(data);
+5 -1
View File
@@ -4,11 +4,15 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.6</version>
<version>3.9.5-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.core</project.jpms.module.name>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
@@ -265,8 +265,9 @@ public abstract class ImageReaderBase extends ImageReader {
// - transferType is ok
// - bands are ok
// TODO: Test if color model is ok?
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() &&
specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType()
&& Arrays.equals(specifier.getSampleModel().getSampleSize(), dest.getSampleModel().getSampleSize())
&& specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
found = true;
break;
}
@@ -314,12 +315,12 @@ public abstract class ImageReaderBase extends ImageReader {
long dimension = (long) destWidth * destHeight;
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();
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
@@ -440,6 +441,7 @@ public abstract class ImageReaderBase extends ImageReader {
static final String ZOOM_IN = "zoom-in";
static final String ZOOM_OUT = "zoom-out";
static final String ZOOM_ACTUAL = "zoom-actual";
static final String ZOOM_FIT = "zoom-fit";
private BufferedImage image;
@@ -515,9 +517,20 @@ public abstract class ImageReaderBase extends ImageReader {
private void setupActions() {
// 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 out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN,
KeyStroke.getKeyStroke('+'),
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.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() {
JPopupMenu popup = new JPopupMenu();
popup.add(getActionMap().get(ZOOM_FIT));
popup.add(getActionMap().get(ZOOM_ACTUAL));
popup.add(getActionMap().get(ZOOM_IN));
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("Black", Color.BLACK), background, group);
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);
addCheckBoxItem(chooseBackgroundAction, background, group);
@@ -668,14 +682,41 @@ public abstract class ImageReaderBase extends ImageReader {
}
else {
Icon current = getIcon();
int w = (int) Math.max(Math.min(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 w = Math.max(Math.min((int) (current.getIconWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 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()));
}
}
}
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 final BufferedImage image;
@@ -694,7 +735,7 @@ public abstract class ImageReaderBase extends ImageReader {
}
@Override
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException {
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException {
if (isDataFlavorSupported(flavor)) {
return image;
}
@@ -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;
}
}
@@ -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);
}
}
}
@@ -30,23 +30,17 @@
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.util.LRUHashMap;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
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.
@@ -83,9 +77,6 @@ public final class ColorSpaces {
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
/** 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")
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
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
// 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 {
try {
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
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();
}
// In case we didn't activate through SPI already
ProfileDeferralActivator.activateProfiles();
}
private ColorSpaces() {}
@@ -134,7 +118,7 @@ public final class ColorSpaces {
Validate.notNull(profile, "profile");
// Fix profile before lookup/create
profileCleaner.fixProfile(profile);
fixProfile(profile);
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
@@ -146,53 +130,20 @@ public final class ColorSpaces {
return getCachedOrCreateCS(profile, profileHeader);
}
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
// Get *entire profile data*... :-/
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)) {
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
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);
}
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);
}
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);
}
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);
}
@@ -203,26 +154,36 @@ public final class ColorSpaces {
Key key = new Key(profileHeader);
synchronized (cache) {
ICC_ColorSpace cs = cache.get(key);
ICC_ColorSpace cs = getCachedCS(key);
if (cs == null) {
cs = new ICC_ColorSpace(profile);
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);
// 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;
}
}
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
// Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
@@ -233,73 +194,27 @@ public final class ColorSpaces {
}
/**
* 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#isCS_sRGB()
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
*/
@Deprecated
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);
return ColorProfiles.isCS_sRGB(profile);
}
/**
* 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}
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
*/
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;
@Deprecated
public static boolean isCS_GRAY(final ICC_Profile profile) {
return ColorProfiles.isCS_GRAY(profile);
}
/**
* 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.
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
*/
@Deprecated
public static ICC_Profile validateProfile(final ICC_Profile profile) {
// Fix profile before validation
profileCleaner.fixProfile(profile);
validateColorSpace(new ICC_ColorSpace(profile));
return profile;
return ColorProfiles.validateProfile(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 final byte[] data;
@@ -448,78 +319,4 @@ public final class ColorSpaces {
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);
}
}
}
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
// Our IndexColorModel delegate
private final IndexColorModel icm;
private final int extraSamples;
private final int samples;
private final boolean hasAlpha;
/**
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
);
this.icm = icm;
this.extraSamples = extraSamples;
this.samples = 1 + extraSamples;
this.hasAlpha = hasAlpha;
}
@Override
public int getNumComponents() {
return samples;
return getNumColorComponents() + extraSamples;
}
@Override
public final int getRed(final int pixel) {
public int getRed(final int pixel) {
return icm.getRed(pixel);
}
@Override
public final int getGreen(final int pixel) {
public int getGreen(final int pixel) {
return icm.getGreen(pixel);
}
@Override
public final int getBlue(final int pixel) {
public int getBlue(final int pixel) {
return icm.getBlue(pixel);
}
@Override
public final int getAlpha(final int pixel) {
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
public int getAlpha(final int pixel) {
return hasAlpha() ? (int) ((((float) pixel) / ((1 << getComponentSize(3)) - 1)) * 255.0f + 0.5f) : 0xff;
}
private int getSample(final Object inData, final int index) {
@@ -120,15 +120,15 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
switch (transferType) {
case DataBuffer.TYPE_BYTE:
byte bdata[] = (byte[]) inData;
byte[] bdata = (byte[]) inData;
pixel = bdata[index] & 0xff;
break;
case DataBuffer.TYPE_USHORT:
short sdata[] = (short[]) inData;
short[] sdata = (short[]) inData;
pixel = sdata[index] & 0xffff;
break;
case DataBuffer.TYPE_INT:
int idata[] = (int[]) inData;
int[] idata = (int[]) inData;
pixel = idata[index];
break;
default:
@@ -139,27 +139,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
}
@Override
public final int getRed(final Object inData) {
public int getRed(final Object inData) {
return getRed(getSample(inData, 0));
}
@Override
public final int getGreen(final Object inData) {
public int getGreen(final Object inData) {
return getGreen(getSample(inData, 0));
}
@Override
public final int getBlue(final Object inData) {
public int getBlue(final Object inData) {
return getBlue(getSample(inData, 0));
}
@Override
public final int getAlpha(final Object inData) {
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
public int getAlpha(final Object inData) {
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
}
@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));
}
@@ -174,17 +174,17 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
}
@Override
public final boolean isCompatibleSampleModel(final SampleModel sm) {
public boolean isCompatibleSampleModel(final SampleModel sm) {
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
}
@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));
}
@Override
public final boolean isCompatibleRaster(final Raster raster) {
public boolean isCompatibleRaster(final Raster raster) {
int size = raster.getSampleModel().getSampleSize(0);
return ((raster.getTransferType() == transferType) &&
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
@@ -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();
}
}
}
@@ -61,7 +61,7 @@ public final class UInt32ColorModel extends ComponentColorModel {
// This class only supports DataBuffer.TYPE_INT, cast is safe
int[] ipixel = (int[]) pixel;
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();
@@ -45,36 +45,82 @@ public final class YCbCrConverter {
private final static int CENTERJSAMPLE = 128;
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
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 class JPEG {
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];
/**
* Initializes tables for YCC->RGB color space conversion.
*/
private static void buildYCCtoRGBtable() {
if (ColorSpaces.DEBUG) {
System.err.println("Building YCC conversion table");
/**
* Initializes tables for YCC->RGB color space conversion.
*/
private static void buildYCCtoRGBtable() {
if (ColorSpaces.DEBUG) {
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++) {
// 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;
static {
buildYCCtoRGBtable();
}
}
static {
buildYCCtoRGBtable();
private final static class ITU_R_601 {
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) {
@@ -108,17 +154,27 @@ public final class YCbCrConverter {
rgb[offset + 1] = clamp(green);
}
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
int y = yCbCr[offset] & 0xff;
int cr = yCbCr[offset + 2] & 0xff;
public static void convertJPEGYCbCr2RGB(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(y + Cr_R_LUT[cr]);
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
rgb[offset ] = clamp(y + JPEG.Cr_R_LUT[cr]);
rgb[offset + 1] = clamp(y + (JPEG.Cb_G_LUT[cb] + JPEG.Cr_G_LUT[cr] >> SCALEBITS));
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));
}
}
@@ -49,10 +49,10 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
private final String[] mimeTypes;
private final String readerClassName;
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[] writerSpiClassNames;
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
private final Class<?>[] outputTypes = new Class<?>[] {ImageOutputStream.class};
private final boolean supportsStandardStreamMetadata;
private final String nativeStreamMetadataFormatName;
private final String nativeStreamMetadataFormatClassName;
@@ -80,7 +80,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
final String writerClassName,
final String[] writerSpiClassNames,
final boolean supportsStandardStreamMetadata,
final String nativeStreameMetadataFormatName,
final String nativeStreamMetadataFormatName,
final String nativeStreamMetadataFormatClassName,
final String[] extraStreamMetadataFormatNames,
final String[] extraStreamMetadataFormatClassNames,
@@ -99,7 +99,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
this.writerClassName = writerClassName;
this.writerSpiClassNames = writerSpiClassNames;
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
this.nativeStreamMetadataFormatName = nativeStreamMetadataFormatName;
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
@@ -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);
}
}
}
@@ -0,0 +1,261 @@
/*
* 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.stream;
import javax.imageio.stream.ImageInputStreamImpl;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
/**
* A buffered replacement for {@link javax.imageio.stream.FileImageInputStream}
* that provides greatly improved performance for shorter reads, like single
* byte or bit reads.
* As with {@code javax.imageio.stream.FileImageInputStream}, either
* {@link File} or {@link RandomAccessFile} can be used as input.
*
* @see javax.imageio.stream.FileImageInputStream
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
*/
// TODO: Create a memory-mapped version?
// Or not... From java.nio.channels.FileChannel.map:
// For most operating systems, mapping a file into memory is more
// expensive than reading or writing a few tens of kilobytes of data via
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
@Deprecated
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
static final int DEFAULT_BUFFER_SIZE = 8192;
private byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
private int bufferPos;
private int bufferLimit;
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
private final byte[] integralCacheArray = integralCache.array();
private RandomAccessFile raf;
/**
* Constructs a <code>BufferedFileImageInputStream</code> that will read from a given <code>File</code>.
*
* @param file a <code>File</code> to read from.
* @throws IllegalArgumentException if <code>file</code> is <code>null</code>.
* @throws FileNotFoundException if <code>file</code> is a directory or cannot be opened for reading
* for any reason.
*/
public BufferedFileImageInputStream(final File file) throws FileNotFoundException {
this(new RandomAccessFile(notNull(file, "file"), "r"));
}
/**
* Constructs a <code>BufferedFileImageInputStream</code> that will read from a given <code>RandomAccessFile</code>.
*
* @param raf a <code>RandomAccessFile</code> to read from.
* @throws IllegalArgumentException if <code>raf</code> is <code>null</code>.
*/
public BufferedFileImageInputStream(final RandomAccessFile raf) {
this.raf = notNull(raf, "raf");
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean fillBuffer() throws IOException {
int length = raf.read(buffer, 0, buffer.length);
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;
int read = raf.read(bytes, offset, length);
if (read > 0) {
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 raf.length();
}
catch (IOException ignore) {
}
return -1;
}
public void close() throws IOException {
super.close();
buffer = null;
raf.close();
raf = 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;
raf.seek(position);
}
streamPos = position;
}
}
@@ -0,0 +1,107 @@
/*
* 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.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedFileImageInputStreamSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedFileImageInputStreamSpi() {
this(new StreamProviderInfo());
}
private BufferedFileImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), File.class);
}
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new FileInputFilter(), true);
while (providers.hasNext()) {
ImageInputStreamSpi provider = providers.next();
if (provider != this) {
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
}
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof File) {
try {
return new BufferedChannelImageInputStream((File) input);
}
catch (FileNotFoundException | NoSuchFileException e) {
// For consistency with the JRE bundled SPIs, we'll return null here,
// even though the spec does not say that's allowed.
// The problem is that the SPIs can only declare that they support an input type like a File,
// instead they should be allowed to inspect the instance, to see that the file does exist...
return null;
}
}
throw new IllegalArgumentException("Expected input of type File: " + input);
}
@Override
public boolean canUseCacheFile() {
return false;
}
@Override
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from a File";
}
private static class FileInputFilter implements ServiceRegistry.Filter {
@Override
public boolean filter(final Object provider) {
return ((ImageInputStreamSpi) provider).getInputClass() == File.class;
}
}
}
@@ -43,22 +43,28 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* A buffered {@code ImageInputStream}.
* Experimental - seems to be effective for {@link javax.imageio.stream.FileImageInputStream}
* and {@link javax.imageio.stream.FileCacheImageInputStream} when doing a lot of single-byte reads
* (or short byte-array reads) on OS X at least.
* (or short byte-array reads).
* Code that uses the {@code readFully} methods are not affected by the issue.
* <p>
* NOTE: Invoking {@code close()} will <em>NOT</em> close the wrapped stream.
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStream.java,v 1.0 May 15, 2008 4:36:49 PM haraldk Exp$
*
* @deprecated Use {@link BufferedFileImageInputStream} instead.
*/
// TODO: Create a provider for this (wrapping the FileIIS and FileCacheIIS classes), and disable the Sun built-in spis?
// TODO: Test on other platforms, might be just an OS X issue
@Deprecated
public final class BufferedImageInputStream extends ImageInputStreamImpl implements ImageInputStream {
static final int DEFAULT_BUFFER_SIZE = 8192;
private ImageInputStream stream;
private ByteBuffer buffer;
private ByteBuffer integralCache = ByteBuffer.allocate(8);
private ByteBuffer buffer;
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
private final byte[] integralCacheArray = integralCache.array();
public BufferedImageInputStream(final ImageInputStream pStream) throws IOException {
this(pStream, DEFAULT_BUFFER_SIZE);
@@ -97,10 +103,10 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
if (!buffer.hasRemaining()) {
fillBuffer();
}
if (!buffer.hasRemaining()) {
return -1;
if (!buffer.hasRemaining()) {
return -1;
}
}
bitOffset = 0;
@@ -172,21 +178,21 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
@Override
public short readShort() throws IOException {
readFully(integralCache.array(), 0, 2);
readFully(integralCacheArray, 0, 2);
return integralCache.getShort(0);
}
@Override
public int readInt() throws IOException {
readFully(integralCache.array(), 0, 4);
readFully(integralCacheArray, 0, 4);
return integralCache.getInt(0);
}
@Override
public long readLong() throws IOException {
readFully(integralCache.array(), 0, 8);
readFully(integralCacheArray, 0, 8);
return integralCache.getLong(0);
}
@@ -253,6 +259,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
}
int val = buffer.get() & 0xff;
streamPos++;
accum <<= 8;
accum |= val;
@@ -262,9 +269,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
// Move byte position back if in the middle of a byte
if (newBitOffset != 0) {
buffer.position(buffer.position() - 1);
}
else {
streamPos++;
streamPos--;
}
this.bitOffset = newBitOffset;
@@ -279,26 +284,26 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
}
@Override
public void seek(long pPosition) throws IOException {
public void seek(long position) throws IOException {
checkClosed();
bitOffset = 0;
if (streamPos == pPosition) {
if (streamPos == position) {
return;
}
// Optimized to not invalidate buffer if new position is within current buffer
long newBufferPos = buffer.position() + pPosition - streamPos;
long newBufferPos = buffer.position() + position - streamPos;
if (newBufferPos >= 0 && newBufferPos <= buffer.limit()) {
buffer.position((int) newBufferPos);
}
else {
// Will invalidate buffer
buffer.limit(0);
stream.seek(pPosition);
stream.seek(position);
}
streamPos = pPosition;
streamPos = position;
}
@Override
@@ -330,7 +335,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
@Override
public void close() throws IOException {
if (stream != null) {
//stream.close();
// TODO: FixMe: Need to close underlying stream here!
// For call sites that relies on not closing, we should instead not close the buffered stream.
// stream.close();
stream = null;
buffer = null;
}
@@ -0,0 +1,78 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedInputStreamImageInputStreamSpi.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
*/
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedInputStreamImageInputStreamSpi() {
this(new StreamProviderInfo());
}
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
}
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
while (providers.hasNext()) {
ImageInputStreamSpi provider = providers.next();
if (provider != this) {
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
}
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof InputStream) {
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
if (channel instanceof SeekableByteChannel) {
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
}
// Otherwise, create a cache for backwards seeking
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
}
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
}
@Override
public boolean canUseCacheFile() {
return true;
}
@Override
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from an InputStream";
}
private static class InputStreamFilter implements ServiceRegistry.Filter {
@Override
public boolean filter(final Object provider) {
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
}
}
}
@@ -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.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedRAFImageInputStreamSpi
* Experimental
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedRAFImageInputStreamSpi() {
this(new StreamProviderInfo());
}
private BufferedRAFImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), RandomAccessFile.class);
}
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new RAFInputFilter(), true);
while (providers.hasNext()) {
ImageInputStreamSpi provider = providers.next();
if (provider != this) {
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
}
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
if (input instanceof RandomAccessFile) {
return new BufferedChannelImageInputStream((RandomAccessFile) input);
}
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
}
@Override
public boolean canUseCacheFile() {
return false;
}
@Override
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
}
private static class RAFInputFilter implements ServiceRegistry.Filter {
@Override
public boolean filter(final Object provider) {
return ((ImageInputStreamSpi) provider).getInputClass() == RandomAccessFile.class;
}
}
}
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
private final int dataOffset;
private final int dataLength;
public ByteArrayImageInputStream(final byte[] pData) {
this(pData, 0, pData != null ? pData.length : -1);
public ByteArrayImageInputStream(final byte[] data) {
this(data, 0, data != null ? data.length : -1);
}
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
data = notNull(pData, "data");
dataOffset = isBetween(0, pData.length, offset, "offset");
dataLength = isBetween(0, pData.length - offset, length, "length");
public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
this.data = notNull(data, "data");
dataOffset = isMax(data.length, offset, "offset");
dataLength = isMax(data.length - offset, length, "length");
}
private static int isBetween(final int low, final int high, final int value, final String name) {
return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, high, value));
private static int isMax(final int high, final int value, final String name) {
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
}
public int read() throws IOException {
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
return data[((int) streamPos++) + dataOffset] & 0xff;
}
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
public int read(byte[] buffer, int offset, int len) throws IOException {
if (streamPos >= dataLength) {
return -1;
}
int length = (int) Math.min(this.dataLength - streamPos, pLength);
int length = (int) Math.min(dataLength - streamPos, len);
bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
streamPos += length;
return length;
@@ -30,10 +30,11 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
/**
@@ -44,22 +45,27 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
*/
public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
public ByteArrayImageInputStreamSpi() {
super("TwelveMonkeys", "1.0 BETA", byte[].class);
this(new StreamProviderInfo());
}
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) throws IOException {
if (pInput instanceof byte[]) {
return new ByteArrayImageInputStream((byte[]) pInput);
}
else {
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
}
private ByteArrayImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
}
public String getDescription(Locale pLocale) {
@Override
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
if (input instanceof byte[]) {
return new ByteArrayImageInputStream((byte[]) input);
}
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
}
@Override
public String getDescription(Locale locale) {
return "Service provider that instantiates an ImageInputStream from a byte array";
}
@@ -0,0 +1,7 @@
package com.twelvemonkeys.imageio.stream;
import java.nio.channels.SeekableByteChannel;
interface Cache extends SeekableByteChannel {
void flushBefore(long pos);
}
@@ -0,0 +1,133 @@
/*
* 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.IOException;
import java.io.InputStream;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* An {@code ImageInputStream} that adapts an {@code InputSteam},
* by reading directly from the stream without and form of caching or buffering.
* <p>
* Note: This is <em>not</em> a general-purpose {@code ImageInputStream}, and is designed for reading large chunks,
* typically of pixel data, from an {@code InputStream}.
* It does <em>not</em> support backwards seeking, or reading bits.
* </p>
*/
public final class DirectImageInputStream extends ImageInputStreamImpl {
private final InputStream stream;
private final long length;
public DirectImageInputStream(final InputStream stream) {
this(stream, -1L);
}
public DirectImageInputStream(final InputStream stream, long length) {
this.stream = notNull(stream, "stream");
this.length = isTrue(length >= 0L || length == -1L, length, "negative length: %d");
}
@Override
public int read() throws IOException {
bitOffset = 0;
streamPos++;
return stream.read();
}
@Override
public int read(final byte[] bytes, int off, int len) throws IOException {
bitOffset = 0;
int read = stream.read(bytes, off, len);
if (read > 0) {
streamPos += read;
}
return read;
}
@Override
public void seek(long pos) throws IOException {
checkClosed();
if (pos < streamPos) {
// Handle as if flushedPos == streamPos at any time
throw new IndexOutOfBoundsException("pos < flushedPos");
}
bitOffset = 0;
while (streamPos < pos) {
long skipped = stream.skip(pos - streamPos);
if (skipped <= 0) {
break;
}
streamPos += skipped;
}
}
@Override
public long getFlushedPosition() {
// Handle as if flushedPos == streamPos at any time
return streamPos;
}
@Override
public long length() {
return length;
}
@SuppressWarnings("RedundantThrows")
@Override
public int readBit() throws IOException {
throw new UnsupportedOperationException("Bit reading not supported");
}
@SuppressWarnings("RedundantThrows")
@Override
public long readBits(int numBits) throws IOException {
throw new UnsupportedOperationException("Bit reading not supported");
}
@Override
public void close() throws IOException {
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
stream.close();
super.close();
}
}
@@ -0,0 +1,112 @@
package com.twelvemonkeys.imageio.stream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
// Note: We could consider creating a memory-mapped version...
// But, from java.nio.channels.FileChannel.map:
// For most operating systems, mapping a file into memory is more
// expensive than reading or writing a few tens of kilobytes of data via
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache;
private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor?
FileCache(InputStream stream, File cacheDir) throws IOException {
// Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
}
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
// Create a temp file to hold our cache,
// will be deleted when this channel is closed, as we close the cache
Path cacheFile = cacheDir == null
? Files.createTempFile("imageio", ".tmp")
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
}
@SuppressWarnings("StatementWithEmptyBody")
void fetch() throws IOException {
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
// Continue transfer...
}
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.close();
}
@Override
public int read(ByteBuffer dest) throws IOException {
fetch();
if (cache.position() >= cache.size()) {
return -1;
}
return cache.read(dest);
}
@Override
public long position() throws IOException {
return cache.position();
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
cache.position(newPosition);
return this;
}
@Override
public long size() {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override public void flushBefore(long pos) {
}
}
@@ -0,0 +1,165 @@
package com.twelvemonkeys.imageio.stream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min;
final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private static final byte[] NULL_BLOCK = new byte[0];
private final List<byte[]> cache = new ArrayList<>();
private final ReadableByteChannel channel;
private int maxBlock = Integer.MAX_VALUE;
private long length;
private long position;
private long start;
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
MemoryCache(InputStream stream) {
this(Channels.newChannel(notNull(stream, "stream")));
}
public MemoryCache(ReadableByteChannel channel) {
this.channel = notNull(channel, "channel");
}
byte[] fetchBlock() throws IOException {
long currPos = position;
long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded");
}
if (index > maxBlock) {
return NULL_BLOCK;
}
while (index >= cache.size()) {
byte[] block;
try {
block = new byte[BLOCK_SIZE];
}
catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
}
cache.add(block);
int bytesRead = readBlock(block);
length += bytesRead;
if (bytesRead < BLOCK_SIZE) {
// Last block, EOF found
maxBlock = (int) index;
return block;
}
}
return cache.get((int) index);
}
private int readBlock(final byte[] block) throws IOException {
ByteBuffer wrapped = ByteBuffer.wrap(block);
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
// Last block, EOF found
break;
}
}
return wrapped.position();
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.clear();
}
@Override
public int read(ByteBuffer dest) throws IOException {
byte[] buffer = fetchBlock();
if (position >= length) {
return -1;
}
int bufferPos = (int) (position % BLOCK_SIZE);
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
dest.put(buffer, bufferPos, len);
position += len;
return len;
}
@Override
public long position() throws IOException {
return position;
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
if (newPosition < start) {
throw new IOException("Seek before flush position");
}
this.position = newPosition;
return this;
}
@Override
public long size() throws IOException {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override
public void flushBefore(long pos) {
if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position");
}
if (pos > position) {
throw new IndexOutOfBoundsException("pos > current position");
}
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
// Clear blocks no longer needed
for (int i = 0; i < blocks; i++) {
cache.set(i, null);
}
start = pos;
}
}
@@ -0,0 +1,9 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
final class StreamProviderInfo extends ProviderInfo {
StreamProviderInfo() {
super(StreamProviderInfo.class.getPackage());
}
}
@@ -53,20 +53,20 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
/**
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
*
* @param pStream the underlying stream
* @param pLength the maximum length to read from the stream.
* Note that {@code pStream} may contain less than this maximum number of bytes.
* @param stream the underlying stream
* @param length the maximum length to read from the stream.
* Note that {@code stream} may contain less than this maximum number of bytes.
*
* @throws IOException if {@code pStream}'s position can't be determined.
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
* @throws IOException if {@code stream}'s position can't be determined.
* @throws IllegalArgumentException if {@code stream == null} or {@code length < 0}
*/
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
Validate.notNull(pStream, "stream");
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException {
Validate.notNull(stream, "stream");
Validate.isTrue(length >= 0, length, "length < 0: %d");
stream = pStream;
startPos = pStream.getStreamPosition();
length = pLength;
this.stream = stream;
this.startPos = stream.getStreamPosition();
this.length = length;
}
public int read() throws IOException {
@@ -84,14 +84,14 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
}
}
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 (streamPos >= length) { // Local EOF
return -1;
}
// Safe cast, as pLength can never cause int overflow
int length = (int) Math.min(pLength, this.length - streamPos);
int count = stream.read(pBytes, pOffset, length);
// Safe cast, as len can never cause int overflow
int length = (int) Math.min(len, this.length - streamPos);
int count = stream.read(bytes, off, length);
if (count >= 0) {
streamPos += count;
@@ -113,18 +113,18 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
}
@Override
public void seek(final long pPosition) throws IOException {
if (pPosition < getFlushedPosition()) {
public void seek(final long position) throws IOException {
if (position < getFlushedPosition()) {
throw new IndexOutOfBoundsException("pos < flushedPosition");
}
stream.seek(startPos + pPosition);
streamPos = pPosition;
stream.seek(startPos + position);
streamPos = position;
}
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
protected void finalize() throws Throwable {
protected void finalize() {
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -30,11 +30,10 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.FileCacheImageInputStream;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -51,64 +50,48 @@ import java.util.Locale;
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
// TODO: URI instead of URL?
public class URLImageInputStreamSpi extends ImageInputStreamSpi {
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
public URLImageInputStreamSpi() {
super("TwelveMonkeys", "1.0 BETA", URL.class);
this(new StreamProviderInfo());
}
private URLImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), URL.class);
}
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
if (pInput instanceof URL) {
URL url = (URL) pInput;
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof URL) {
URL url = (URL) input;
// Special case for file protocol, a lot faster than FileCacheImageInputStream
if ("file".equals(url.getProtocol())) {
try {
return new BufferedImageInputStream(new FileImageInputStream(new File(url.toURI())));
return new BufferedChannelImageInputStream(new File(url.toURI()));
}
catch (URISyntaxException ignore) {
// This should never happen, but if it does, we'll fall back to using the stream
ignore.printStackTrace();
catch (URISyntaxException shouldNeverHappen) {
// This should never happen, but if it does, we'll fall back to using the stream
shouldNeverHappen.printStackTrace();
}
}
// Otherwise revert to cached
final InputStream stream = url.openStream();
if (pUseCache) {
return new BufferedImageInputStream(new FileCacheImageInputStream(stream, pCacheDir) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
stream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
});
}
else {
return new MemoryCacheImageInputStream(stream) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
stream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
};
}
}
else {
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
InputStream urlStream = url.openStream();
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
}
throw new IllegalArgumentException("Expected input of type URL: " + input);
}
public String getDescription(final Locale pLocale) {
@Override
public boolean canUseCacheFile() {
return true;
}
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from a URL";
}
}
@@ -81,7 +81,7 @@ class IIOInputStreamAdapter extends InputStream {
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
Validate.notNull(pInput, "stream");
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %d");
input = pInput;
left = pLength;
@@ -45,6 +45,7 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -158,33 +159,39 @@ public final class IIOUtil {
}
/**
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
*
* @param registry the registry to unregister from.
* @param provider the provider to unregister.
* @param category the category to unregister from.
*/
public static <T> void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class<T> category) {
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
registry.deregisterServiceProvider(category.cast(provider), category);
}
/**
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
*
* @param registry the registry to lookup from.
* @param providerClassName name of the provider class.
* @param category provider category
*
* @return the provider instance, or {@code null}.
* @return the provider instance, or {@code null} if not found
*/
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
try {
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
}
catch (ClassNotFoundException ignore) {
return null;
// NOTE: While more verbose, this is more OSGi-friendly than using
// registry.getServiceProviderByClass(Class.forName(providerClassName))
Iterator<T> providers = registry.getServiceProviders(category, true);
while (providers.hasNext()) {
T provider = providers.next();
if (provider.getClass().getName().equals(providerClassName)) {
return provider;
}
}
return null;
}
/**
@@ -223,7 +230,12 @@ public final class IIOUtil {
public static void subsampleRow(byte[] srcRow, int srcPos, int srcWidth,
byte[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
// Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 8 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 8 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
@@ -261,7 +273,12 @@ public final class IIOUtil {
public static void subsampleRow(short[] srcRow, int srcPos, int srcWidth,
short[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
// Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 16 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 16 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
@@ -278,6 +295,28 @@ public final class IIOUtil {
public static void subsampleRow(int[] srcRow, int srcPos, int srcWidth,
int[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
// Period == 1 is a no-op...
if (samplePeriod == 1) {
return;
}
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 32 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0,
"samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 ");
int pixelStride = bitsPerSample * samplesPerPixel / 32;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
// System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
}
}
public static void subsampleRow(float[] srcRow, int srcPos, int srcWidth,
float[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 32 and a power of 2");
@@ -33,7 +33,7 @@ package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
@@ -51,28 +51,52 @@ import static com.twelvemonkeys.lang.Validate.notNull;
*/
public final class ImageTypeSpecifiers {
private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0xFF0000,
0x00FF00,
0x0000FF,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0x0000FF,
0x00FF00,
0xFF0000,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private ImageTypeSpecifiers() {}
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
switch (bufferedImageType) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
case BufferedImage.TYPE_INT_RGB:
return TYPE_INT_RGB;
case BufferedImage.TYPE_INT_BGR:
return TYPE_INT_BGR;
case BufferedImage.TYPE_USHORT_565_RGB:
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
return TYPE_USHORT_565_RGB;
case BufferedImage.TYPE_USHORT_555_RGB:
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
return TYPE_USHORT_555_RGB;
default:
}
@@ -83,23 +107,41 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask);
if (bits != 32) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
private static int calculateRequiredBits(int mask) {
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
int r = 1;
while ((mask >>>= 1) != 0) {
r++;
}
return r;
}
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
final int[] bandOffsets,
final int dataType,
@@ -169,21 +211,36 @@ public final class ImageTypeSpecifiers {
int numEntries = 1 << bits;
byte[] r = new byte[numEntries];
byte[] g = new byte[numEntries];
byte[] b = new byte[numEntries];
ColorModel colorModel;
// Scale array values according to color profile..
for (int i = 0; i < numEntries; i++) {
float[] gray = new float[]{i / (float) (numEntries - 1)};
float[] rgb = colorSpace.toRGB(gray);
if (ColorSpace.getInstance(ColorSpace.CS_GRAY).equals(colorSpace)) {
// For default gray, use linear response
byte[] gray = new byte[numEntries];
r[i] = (byte) (rgb[0] * 255);
g[i] = (byte) (rgb[1] * 255);
b[i] = (byte) (rgb[2] * 255);
for (int i = 0; i < numEntries; i++) {
gray[i] = (byte) ((i * 255) / (numEntries - 1));
}
colorModel = new IndexColorModel(bits, numEntries, gray, gray, gray);
}
else {
byte[] r = new byte[numEntries];
byte[] g = new byte[numEntries];
byte[] b = new byte[numEntries];
// Scale array values according to color profile..
for (int i = 0; i < numEntries; i++) {
float[] gray = new float[] { i / (float) (numEntries - 1) };
float[] rgb = colorSpace.toRGB(gray);
r[i] = (byte) Math.round(rgb[0] * 255);
g[i] = (byte) Math.round(rgb[1] * 255);
b[i] = (byte) Math.round(rgb[2] * 255);
}
colorModel = new IndexColorModel(bits, numEntries, r, g, b);
}
ColorModel colorModel = new IndexColorModel(bits, numEntries, r, g, b);
SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
return new ImageTypeSpecifier(colorModel, sampleModel);
@@ -201,7 +258,7 @@ public final class ImageTypeSpecifiers {
}
public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) {
return IndexedImageTypeSpecifier.createFromIndexColorModel(pColorModel);
return new IndexedImageTypeSpecifier(pColorModel);
}
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) {
@@ -213,4 +270,20 @@ public final class ImageTypeSpecifiers {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
if (image == null) {
throw new IllegalArgumentException("image == null!");
}
if (image instanceof BufferedImage) {
int bufferedImageType = ((BufferedImage) image).getType();
if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
return createFromBufferedImageType(bufferedImageType);
}
}
return new ImageTypeSpecifier(image);
}
}
@@ -30,14 +30,14 @@
package com.twelvemonkeys.imageio.util;
import javax.imageio.ImageTypeSpecifier;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.util.Hashtable;
import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import javax.imageio.ImageTypeSpecifier;
/**
* IndexedImageTypeSpecifier
*
@@ -45,27 +45,24 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* @author last modified by $Author: haraldk$
* @version $Id: IndexedImageTypeSpecifier.java,v 1.0 May 19, 2008 11:04:28 AM haraldk Exp$
*/
final class IndexedImageTypeSpecifier {
private IndexedImageTypeSpecifier() {}
final class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
IndexedImageTypeSpecifier(final ColorModel colorModel) {
// For some reason, we need a sample model, even though we won't use it
super(notNull(colorModel, "colorModel"), colorModel.createCompatibleSampleModel(1, 1));
}
static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) {
// For some reason, we need a sample model
return new ImageTypeSpecifier(notNull(pColorModel, "colorModel"), pColorModel.createCompatibleSampleModel(1, 1)) {
@Override
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
try {
// This is a fix for the super-method, that first creates a sample model, and then
// creates a raster from it, using Raster.createWritableRaster. The problem with
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable());
}
catch (NegativeArraySizeException e) {
// Exception most likely thrown from a DataBuffer constructor
throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!");
}
}
};
@Override
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
try {
// This is a fix for the super-method, that first creates a sample model, and then
// creates a raster from it, using Raster.createWritableRaster. The problem with
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
}
catch (NegativeArraySizeException e) {
// Exception most likely thrown from a DataBuffer constructor
throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!");
}
}
}
@@ -0,0 +1,152 @@
/*
* 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.util;
import java.awt.*;
import java.awt.image.*;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* A class containing various raster utility methods.
*/
public final class RasterUtils {
private RasterUtils() {
}
/**
* Returns a raster with {@code DataBuffer.TYPE_BYTE} transfer type.
* Works for any raster from a {@code BufferedImage.TYPE_INT_*} image
*
* @param raster a {@code Raster} with either transfer type {@code DataBuffer.TYPE_BYTE}
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}.
* @return a raster with {@code DataBuffer.TYPE_BYTE} transfer type.
* @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE}
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`
* @throws NullPointerException if {@code raster} is {@code null}.
*/
public static Raster asByteRaster(final Raster raster) {
return asByteRaster0(raster);
}
/**
* Returns a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type.
* Works for any raster from a {@code BufferedImage.TYPE_INT_*} image.
*
* @param raster a {@code WritableRaster} with either transfer type {@code DataBuffer.TYPE_BYTE}
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}.
* @return a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type.
* @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE}
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`
* @throws NullPointerException if {@code raster} is {@code null}.
*/
public static WritableRaster asByteRaster(final WritableRaster raster) {
return (WritableRaster) asByteRaster0(raster);
}
private static Raster asByteRaster0(final Raster raster) {
switch (raster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
return raster;
case DataBuffer.TYPE_INT:
SampleModel sampleModel = raster.getSampleModel();
if (!(sampleModel instanceof SinglePixelPackedSampleModel)) {
throw new IllegalArgumentException(String.format("Requires SinglePixelPackedSampleModel, %s not supported", sampleModel.getClass().getSimpleName()));
}
final int bands = 4;
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
int w = raster.getWidth();
int h = raster.getHeight();
int size = buffer.getSize();
return new WritableRaster(
new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, bands, w * bands, createBandOffsets((SinglePixelPackedSampleModel) sampleModel)),
new DataBuffer(DataBuffer.TYPE_BYTE, size * bands) {
final int[] MASKS = {
0xffffff00,
0xffff00ff,
0xff00ffff,
0x00ffffff,
};
@Override
public int getElem(int bank, int i) {
int index = i / bands;
int shift = (i % bands) * 8;
return (buffer.getElem(index) >>> shift) & 0xff;
}
@Override
public void setElem(int bank, int i, int val) {
int index = i / bands;
int element = i % bands;
int shift = element * 8;
int value = (buffer.getElem(index) & MASKS[element]) | ((val & 0xff) << shift);
buffer.setElem(index, value);
}
}, new Point()) {
};
default:
throw new IllegalArgumentException(String.format("Raster type %d not supported", raster.getTransferType()));
}
}
private static int[] createBandOffsets(final SinglePixelPackedSampleModel sampleModel) {
notNull(sampleModel, "sampleModel");
int[] masks = sampleModel.getBitMasks();
int[] offs = new int[masks.length];
for (int i = 0; i < masks.length; i++) {
int mask = masks[i];
int off = 0;
// TODO: FixMe! This only works for standard 8 bit masks (0xFF)
if (mask != 0) {
while ((mask & 0xFF) == 0) {
mask >>>= 8;
off++;
}
}
offs[i] = off;
}
return offs;
}
}

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