Compare commits

...

100 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
95 changed files with 1765 additions and 684 deletions
+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"
+27 -15
View File
@@ -1,6 +1,14 @@
name: CI name: CI
on: [ push, pull_request ] on:
push:
branches:
- '**'
- '!dependabot/**'
pull_request:
branches: [ 'master' ]
permissions: read-all
jobs: jobs:
test: test:
@@ -9,20 +17,22 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ 8, 11, 17, 18 ] java: [ 8, 11, 17, 20 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
checks: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: actions/setup-java@v3 - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
java-package: jdk java-package: jdk
cache: 'maven' cache: 'maven'
- name: Run Tests - name: Run Tests
run: mvn test run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report - name: Publish Test Report
uses: mikepenz/action-junit-report@v3 uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
with: with:
report_paths: "**/target/surefire-reports/TEST*.xml" report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -31,15 +41,17 @@ jobs:
test_oracle: test_oracle:
name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }} name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
checks: write
strategy: strategy:
matrix: matrix:
kcms: [ true, false ] kcms: [ true, false ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- run: | - run: |
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292" download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
- uses: actions/setup-java@v3 - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
with: with:
distribution: 'jdkfile' distribution: 'jdkfile'
jdkFile: ${{ runner.temp }}/java_package.tar.gz jdkFile: ${{ runner.temp }}/java_package.tar.gz
@@ -52,9 +64,9 @@ jobs:
- name: Display Java version - name: Display Java version
run: java -version run: java -version
- name: Run Tests - name: Run Tests
run: mvn test run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report - name: Publish Test Report
uses: mikepenz/action-junit-report@v3 uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
with: with:
report_paths: "**/target/surefire-reports/TEST*.xml" report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -66,9 +78,9 @@ jobs:
if: github.ref == 'refs/heads/master' # only perform on latest master if: github.ref == 'refs/heads/master' # only perform on latest master
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Set up Maven Central - name: Set up Maven Central
uses: actions/setup-java@v3 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
with: # running setup-java again overwrites the settings.xml with: # running setup-java again overwrites the settings.xml
distribution: 'temurin' distribution: 'temurin'
java-version: '8' java-version: '8'
@@ -80,11 +92,11 @@ jobs:
gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3) gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3)
- name: Get Project Version - name: Get Project Version
run: | run: |
echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV echo "PROJECT_VERSION=$(mvn --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
- name: Publish to Maven Central - name: Publish to Maven Central
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
run: mvn deploy -P release -DskipTests run: mvn --batch-mode --no-transfer-progress deploy -P release -DskipTests
env: env:
MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} # must be the same env variable name as (1) 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_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) MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # must be the same env variable name as (3)
+59 -73
View File
@@ -4,6 +4,8 @@
[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/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) [![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/haraldk76/100)
![Logo](logo.png)
## About ## About
TwelveMonkeys ImageIO provides extended image file format support for the Java platform, through plugins for the `javax.imageio.*` package. TwelveMonkeys ImageIO provides extended image file format support for the Java platform, through plugins for the `javax.imageio.*` package.
@@ -272,12 +274,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<version>3.8.1</version> <version>3.9.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<version>3.8.1</version> <version>3.9.4</version>
</dependency> </dependency>
<!-- <!--
@@ -287,7 +289,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.servlet</groupId> <groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<version>3.8.1</version> <version>3.9.4</version>
</dependency> </dependency>
<!-- <!--
@@ -296,7 +298,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency> <dependency>
<groupId>com.twelvemonkeys.servlet</groupId> <groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId> <artifactId>servlet</artifactId>
<version>3.8.1</version> <version>3.9.4</version>
<classifier>jakarta</classifier> <classifier>jakarta</classifier>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -306,13 +308,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path: To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.8.1.jar twelvemonkeys-common-lang-3.9.4.jar
twelvemonkeys-common-io-3.8.1.jar twelvemonkeys-common-io-3.9.4.jar
twelvemonkeys-common-image-3.8.1.jar twelvemonkeys-common-image-3.9.4.jar
twelvemonkeys-imageio-core-3.8.1.jar twelvemonkeys-imageio-core-3.9.4.jar
twelvemonkeys-imageio-metadata-3.8.1.jar twelvemonkeys-imageio-metadata-3.9.4.jar
twelvemonkeys-imageio-jpeg-3.8.1.jar twelvemonkeys-imageio-jpeg-3.9.4.jar
twelvemonkeys-imageio-tiff-3.8.1.jar twelvemonkeys-imageio-tiff-3.9.4.jar
#### Deploying the plugins in a web app #### Deploying the plugins in a web app
@@ -378,81 +380,50 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries ### Links to prebuilt binaries
##### Latest version (3.8.1) ##### Latest version (3.9.4)
Requires Java 7 or later. The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later.
Common dependencies Common dependencies
* [common-lang-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.8.1/common-lang-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.8.1/common-io-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.8.1/common-image-3.8.1.jar) * [common-image-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.9.4/common-image-3.9.4.jar)
ImageIO dependencies ImageIO dependencies
* [imageio-core-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.8.1/imageio-core-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.8.1/imageio-metadata-3.8.1.jar) * [imageio-metadata-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.9.4/imageio-metadata-3.9.4.jar)
ImageIO plugins ImageIO plugins
* [imageio-bmp-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.8.1/imageio-bmp-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.8.1/imageio-hdr-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.8.1/imageio-icns-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.8.1/imageio-iff-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.8.1/imageio-jpeg-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.8.1/imageio-pcx-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.8.1/imageio-pict-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.8.1/imageio-pnm-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.8.1/imageio-psd-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.8.1/imageio-sgi-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.8.1/imageio-tga-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.8.1/imageio-thumbsdb-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.8.1/imageio-tiff-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.8.1/imageio-webp-3.8.1.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.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.8.1/imageio-xwd-3.8.1.jar) * [imageio-xwd-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.9.4/imageio-xwd-3.9.4.jar)
ImageIO plugins requiring 3rd party libs ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.8.1/imageio-batik-3.8.1.jar) * [imageio-batik-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.9.4/imageio-batik-3.9.4.jar)
Photoshop Path support for ImageIO Photoshop Path support for ImageIO
* [imageio-clippath-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.8.1/imageio-clippath-3.8.1.jar) * [imageio-clippath-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.9.4/imageio-clippath-3.9.4.jar)
Servlet support Servlet support
* [servlet-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.1/servlet-3.8.1.jar) * [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar)
##### Old version (3.0.x)
Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8 or later*.
Common dependencies
* [common-lang-3.0.2.jar](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://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](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
## License ## License
This project is provided under the OSI approved [BSD license](https://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-2020, Harald Kuhr Copyright (c) 2008-2022, Harald Kuhr
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@@ -484,8 +455,9 @@ This project is provided under the OSI approved [BSD license](https://opensource
q: How do I use it? q: How do I use it?
a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need. a: The easiest way is to build your own project using Maven, Gradle or other build tool with dependency management,
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above. and just add dependencies to the specific plug-ins you need.
If you don't use such a build tool, make sure you have all the necessary JARs in classpath. See the Install section above.
q: What changes do I have to make to my code in order to use the plug-ins? q: What changes do I have to make to my code in order to use the plug-ins?
@@ -503,22 +475,36 @@ q: How does it work?
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses a service lookup mechanism, to discover plug-ins at runtime. a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath. All you have to do, is to make sure you have the TwelveMonkeys ImageIO JARs in your classpath.
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html). You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and
utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before
the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, and the Apple provided `TIFFImageReader` on OS X, the Sun/Oracle provided `JPEGImageReader`, `BMPImageReader` `TIFFImageReader`, and the Apple provided `TIFFImageReader` on OS X,
respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most
cases you'll end up using the TwelveMonkeys plug-ins instead. cases you'll end up using the TwelveMonkeys plug-ins instead.
q: Why is there no support for common formats like GIF or PNG? 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 good enough as-is. 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). If you are looking for better PNG write performance on Java 7 and 8, see [JDK9 PNG Writer Backport](https://github.com/gredler/jdk9-png-writer-backport).
q: When is the next release? What is the current release schedule?
a: The goal is to make monthly releases, containing bug fixes and minor new features.
And quarterly releases with more "major" features.
q: I love this project! How can I help?
a: Have a look at the open issues, and see if there are any issues you can help fix, or provide sample file or create test cases for.
It is also possible for you or your organization to become a sponsor, through GitHub Sponsors.
Providing funding will allow us to spend more time on fixing bugs and implementing new features.
q: What about JAI? Several of the formats are already supported by JAI. q: What about JAI? Several of the formats are already supported by JAI.
a: While JAI (and jai-imageio in particular) have support for some of the same 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.
+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.
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.bom</groupId> <groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-image</artifactId> <artifactId>common-image</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -79,7 +79,7 @@ public final class BufferedImageFactory {
private int scanSize; private int scanSize;
private ColorModel sourceColorModel; private ColorModel sourceColorModel;
private Hashtable sourceProperties; // ImageConsumer API dictates Hashtable private Hashtable<?, ?> sourceProperties; // ImageConsumer API dictates Hashtable
private Object sourcePixels; private Object sourcePixels;
@@ -91,21 +91,21 @@ public final class BufferedImageFactory {
/** /**
* Creates a {@code BufferedImageFactory}. * Creates a {@code BufferedImageFactory}.
* @param pSource the source image * @param source the source image
* @throws IllegalArgumentException if {@code pSource == null} * @throws IllegalArgumentException if {@code source == null}
*/ */
public BufferedImageFactory(final Image pSource) { public BufferedImageFactory(final Image source) {
this(pSource != null ? pSource.getSource() : null); this(source != null ? source.getSource() : null);
} }
/** /**
* Creates a {@code BufferedImageFactory}. * Creates a {@code BufferedImageFactory}.
* @param pSource the source image producer * @param source the source image producer
* @throws IllegalArgumentException if {@code pSource == null} * @throws IllegalArgumentException if {@code source == null}
*/ */
public BufferedImageFactory(final ImageProducer pSource) { public BufferedImageFactory(final ImageProducer source) {
Validate.notNull(pSource, "source"); Validate.notNull(source, "source");
producer = pSource; producer = source;
} }
/** /**
@@ -155,44 +155,44 @@ public final class BufferedImageFactory {
/** /**
* Sets the source region (AOI) for the new image. * Sets the source region (AOI) for the new image.
* *
* @param pRegion the source region * @param region the source region
*/ */
public void setSourceRegion(final Rectangle pRegion) { public void setSourceRegion(final Rectangle region) {
// Re-fetch everything, if region changed // Re-fetch everything, if region changed
if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) { if (x != region.x || y != region.y || width != region.width || height != region.height) {
dispose(); dispose();
} }
x = pRegion.x; x = region.x;
y = pRegion.y; y = region.y;
width = pRegion.width; width = region.width;
height = pRegion.height; height = region.height;
} }
/** /**
* Sets the source subsampling for the new image. * Sets the source subsampling for the new image.
* *
* @param pXSub horizontal subsampling factor * @param xSubsampling horizontal subsampling factor
* @param pYSub vertical subsampling factor * @param ySubsampling vertical subsampling factor
*/ */
public void setSourceSubsampling(int pXSub, int pYSub) { public void setSourceSubsampling(int xSubsampling, int ySubsampling) {
// Re-fetch everything, if subsampling changed // Re-fetch everything, if subsampling changed
if (xSub != pXSub || ySub != pYSub) { if (xSub != xSubsampling || ySub != ySubsampling) {
dispose(); dispose();
} }
if (pXSub > 1) { if (xSubsampling > 1) {
xSub = pXSub; xSub = xSubsampling;
} }
if (pYSub > 1) { if (ySubsampling > 1) {
ySub = pYSub; ySub = ySubsampling;
} }
} }
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException { private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException {
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) { if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
// NOTE: Subsampling is only applied if extracting full image // NOTE: Subsampling is only applied if extracting full image
if (!pColorModelOnly && (xSub > 1 || ySub > 1)) { if (!colorModelOnly && (xSub > 1 || ySub > 1)) {
// If only sampling a region, the region must be scaled too // If only sampling a region, the region must be scaled too
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
width = (width + xSub - 1) / xSub; width = (width + xSub - 1) / xSub;
@@ -207,38 +207,41 @@ public final class BufferedImageFactory {
// Start fetching // Start fetching
fetching = true; fetching = true;
readColorModelOnly = pColorModelOnly; readColorModelOnly = colorModelOnly;
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
// Wait until the producer wakes us up, by calling imageComplete // Wait until the producer wakes us up, by calling imageComplete
while (fetching) { while (fetching) {
try { try {
wait(200l); wait(200L);
} }
catch (InterruptedException e) { catch (InterruptedException e) {
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e); throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
} }
} }
if (consumerException != null) { try {
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException); if (consumerException != null) {
} throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
}
if (pColorModelOnly) { if (colorModelOnly) {
createColorModel(); createColorModel();
}
else {
createBuffered();
}
} }
else { finally {
createBuffered(); // Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
} }
} }
} }
private void createColorModel() { private void createColorModel() {
colorModel = sourceColorModel; colorModel = sourceColorModel;
// Clean up, in case any objects are copied/cloned, so we can free resources
freeResources();
} }
private void createBuffered() { private void createBuffered() {
@@ -253,8 +256,9 @@ public final class BufferedImageFactory {
} }
} }
// Clean up, in case any objects are copied/cloned, so we can free resources if (buffered == null) {
freeResources(); throw new ImageConversionException("Could not create BufferedImage");
}
} }
private void freeResources() { private void freeResources() {
@@ -280,27 +284,27 @@ public final class BufferedImageFactory {
/** /**
* Adds a progress listener to this factory. * Adds a progress listener to this factory.
* *
* @param pListener the progress listener * @param listener the progress listener
*/ */
public void addProgressListener(ProgressListener pListener) { public void addProgressListener(ProgressListener listener) {
if (pListener == null) { if (listener == null) {
return; return;
} }
if (listeners == null) { if (listeners == null) {
listeners = new CopyOnWriteArrayList<ProgressListener>(); listeners = new CopyOnWriteArrayList<>();
} }
listeners.add(pListener); listeners.add(listener);
} }
/** /**
* Removes a progress listener from this factory. * Removes a progress listener from this factory.
* *
* @param pListener the progress listener * @param listener the progress listener
*/ */
public void removeProgressListener(ProgressListener pListener) { public void removeProgressListener(ProgressListener listener) {
if (pListener == null) { if (listener == null) {
return; return;
} }
@@ -308,7 +312,7 @@ public final class BufferedImageFactory {
return; return;
} }
listeners.remove(pListener); listeners.remove(listener);
} }
/** /**
@@ -324,21 +328,22 @@ public final class BufferedImageFactory {
* Converts an array of {@code int} pixels to an array of {@code short} * Converts an array of {@code int} pixels to an array of {@code short}
* pixels. The conversion is done, by masking out the * pixels. The conversion is done, by masking out the
* <em>higher 16 bits</em> of the {@code int}. * <em>higher 16 bits</em> of the {@code int}.
* * <p>
* For any given {@code int}, the {@code short} value is computed as * For any given {@code int}, the {@code short} value is computed as
* follows: * follows:
* <blockquote>{@code * <blockquote>{@code
* short value = (short) (intValue & 0x0000ffff); * short value = (short) (intValue & 0x0000ffff);
* }</blockquote> * }</blockquote>
* </p>
* *
* @param pPixels the pixel data to convert * @param inputPixels the pixel data to convert
* @return an array of {@code short}s, same lenght as {@code pPixels} * @return an array of {@code short}s, same length as {@code inputPixels}
*/ */
private static short[] toShortPixels(int[] pPixels) { private static short[] toShortPixels(int[] inputPixels) {
short[] pixels = new short[pPixels.length]; short[] pixels = new short[inputPixels.length];
for (int i = 0; i < pixels.length; i++) { for (int i = 0; i < pixels.length; i++) {
pixels[i] = (short) (pPixels[i] & 0xffff); pixels[i] = (short) (inputPixels[i] & 0xffff);
} }
return pixels; return pixels;
@@ -351,17 +356,17 @@ public final class BufferedImageFactory {
* @see BufferedImageFactory#addProgressListener * @see BufferedImageFactory#addProgressListener
* @see BufferedImageFactory#removeProgressListener * @see BufferedImageFactory#removeProgressListener
*/ */
public static interface ProgressListener extends EventListener { public interface ProgressListener extends EventListener {
/** /**
* Reports progress to this listener. * Reports progress to this listener.
* Invoked by the {@code BufferedImageFactory} to report progress in * Invoked by the {@code BufferedImageFactory} to report progress in
* the image decoding. * the image decoding.
* *
* @param pFactory the factory reporting the progress * @param factory the factory reporting the progress
* @param pPercentage the percentage of progress * @param percentage the percentage of progress
*/ */
void progress(BufferedImageFactory pFactory, float pPercentage); void progress(BufferedImageFactory factory, float percentage);
} }
private class Consumer implements ImageConsumer { private class Consumer implements ImageConsumer {
@@ -446,18 +451,18 @@ public final class BufferedImageFactory {
processProgress(pY + pHeight); processProgress(pY + pHeight);
} }
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) { public void setPixels(int x, int y, int width, int height, ColorModel colorModel, short[] pixels, int offset, int scanSize) {
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
} }
private void setColorModelOnce(final ColorModel pModel) { private void setColorModelOnce(final ColorModel colorModel) {
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it // NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
// first passes the original color model through in setColorModel, then // first passes the original color model through in setColorModel, then
// later replaces it with the default RGB in the first setPixels call // later replaces it with the default RGB in the first setPixels call
// (this is probably allowed according to the spec, but it's a waste of time and space). // (this is probably allowed according to the spec, but it's a waste of time and space).
if (sourceColorModel != pModel) { if (sourceColorModel != colorModel) {
if (/*sourceColorModel == null ||*/ sourcePixels == null) { if (sourcePixels == null) {
sourceColorModel = pModel; sourceColorModel = colorModel;
} }
else { else {
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported"); throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
@@ -470,17 +475,16 @@ public final class BufferedImageFactory {
} }
} }
public void imageComplete(int pStatus) { @Override
public void imageComplete(int status) {
fetching = false; fetching = false;
if (producer != null) { if (producer != null) {
producer.removeConsumer(this); producer.removeConsumer(this);
} }
switch (pStatus) { if (status == ImageConsumer.IMAGEERROR) {
case ImageConsumer.IMAGEERROR: consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
break;
} }
synchronized (BufferedImageFactory.this) { synchronized (BufferedImageFactory.this) {
@@ -488,16 +492,18 @@ public final class BufferedImageFactory {
} }
} }
public void setColorModel(ColorModel pModel) { @Override
setColorModelOnce(pModel); public void setColorModel(ColorModel colorModel) {
setColorModelOnce(colorModel);
} }
public void setDimensions(int pWidth, int pHeight) { @Override
public void setDimensions(int w, int h) {
if (width < 0) { if (width < 0) {
width = pWidth - x; width = w - x;
} }
if (height < 0) { if (height < 0) {
height = pHeight - y; height = h - y;
} }
// Hmm.. Special case, but is it a good idea? // Hmm.. Special case, but is it a good idea?
@@ -506,27 +512,31 @@ public final class BufferedImageFactory {
} }
} }
public void setHints(int pHintflags) { @Override
public void setHints(int hintFlags) {
// ignore // ignore
} }
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) { @Override
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize); public void setPixels(int x, int y, int width, int height, ColorModel colorModel, byte[] pixels, int offset, int scanSize) {
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
} }
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) { @Override
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) { public void setPixels(int x, int y, int width, int height, ColorModel colorModel, int[] pixels, int offset, int scanSize) {
if (colorModel.getTransferType() == DataBuffer.TYPE_USHORT) {
// NOTE: Workaround for limitation in ImageConsumer API // NOTE: Workaround for limitation in ImageConsumer API
// Convert int[] to short[], to be compatible with the ColorModel // Convert int[] to short[], to be compatible with the ColorModel
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize); setPixelsImpl(x, y, width, height, colorModel, toShortPixels(pixels), offset, scanSize);
} }
else { else {
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize); setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
} }
} }
public void setProperties(Hashtable pProperties) { @Override
sourceProperties = pProperties; public void setProperties(Hashtable properties) {
sourceProperties = properties;
} }
} }
@@ -34,14 +34,13 @@ import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.*;
import java.awt.image.BufferedImage; import java.awt.image.*;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.net.URL; import java.net.URL;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* BufferedImageFactoryTestCase * BufferedImageFactoryTestCase
@@ -260,9 +259,9 @@ public class BufferedImageFactoryTest {
// Listener should abort ASAP // Listener should abort ASAP
factory.addProgressListener(new BufferedImageFactory.ProgressListener() { factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) { public void progress(BufferedImageFactory factory, float percentage) {
if (pPercentage > 5) { if (percentage > 5) {
pFactory.abort(); factory.abort();
} }
} }
}); });
@@ -343,7 +342,7 @@ public class BufferedImageFactoryTest {
VerifyingListener listener = new VerifyingListener(factory); VerifyingListener listener = new VerifyingListener(factory);
factory.addProgressListener(listener); factory.addProgressListener(listener);
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() { factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
public void progress(BufferedImageFactory pFactory, float pPercentage) { public void progress(BufferedImageFactory factory, float percentage) {
} }
}); });
factory.getBufferedImage(); factory.getBufferedImage();
@@ -380,11 +379,11 @@ public class BufferedImageFactoryTest {
this.factory = factory; this.factory = factory;
} }
public void progress(BufferedImageFactory pFactory, float pPercentage) { public void progress(BufferedImageFactory factory, float percentage) {
assertEquals(factory, pFactory); assertEquals(this.factory, factory);
assertTrue(pPercentage >= progress && pPercentage <= 100f); assertTrue(percentage >= progress && percentage <= 100f);
progress = pPercentage; progress = percentage;
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -41,21 +41,20 @@ import java.io.InputStream;
* underlying stream. * underlying stream.
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
*/ */
public final class SubStream extends FilterInputStream { public final class SubStream extends FilterInputStream {
private long bytesLeft; private long bytesLeft;
private int markLimit; private int markLimit;
/** /**
* Creates a {@code SubStream} of the given {@code pStream}. * Creates a {@code SubStream} of the given {@code stream}.
* *
* @param pStream the underlying input stream * @param stream the underlying input stream
* @param pLength maximum number of bytes to read drom this stream * @param length maximum number of bytes to read from this stream
*/ */
public SubStream(final InputStream pStream, final long pLength) { public SubStream(final InputStream stream, final long length) {
super(Validate.notNull(pStream, "stream")); super(Validate.notNull(stream, "stream"));
bytesLeft = pLength; bytesLeft = length;
} }
/** /**
@@ -73,13 +72,13 @@ public final class SubStream extends FilterInputStream {
@Override @Override
public int available() throws IOException { public int available() throws IOException {
return (int) Math.min(super.available(), bytesLeft); return (int) findMaxLen(super.available());
} }
@Override @Override
public void mark(int pReadLimit) { public void mark(int readLimit) {
super.mark(pReadLimit);// This either succeeds or does nothing... super.mark(readLimit);// This either succeeds or does nothing...
markLimit = pReadLimit; markLimit = readLimit;
} }
@Override @Override
@@ -93,44 +92,42 @@ public final class SubStream extends FilterInputStream {
if (bytesLeft-- <= 0) { if (bytesLeft-- <= 0) {
return -1; return -1;
} }
return super.read(); return super.read();
} }
@Override @Override
public final int read(byte[] pBytes) throws IOException { public int read(byte[] bytes) throws IOException {
return read(pBytes, 0, pBytes.length); return read(bytes, 0, bytes.length);
} }
@Override @Override
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { public int read(final byte[] bytes, final int off, final int len) throws IOException {
if (bytesLeft <= 0) { if (bytesLeft <= 0) {
return -1; return -1;
} }
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength)); int read = super.read(bytes, off, (int) findMaxLen(len));
bytesLeft = read < 0 ? 0 : bytesLeft - read; bytesLeft = read < 0 ? 0 : bytesLeft - read;
return read; return read;
} }
@Override
public long skip(long length) throws IOException {
long skipped = super.skip(findMaxLen(length));// Skips 0 or more, never -1
bytesLeft -= skipped;
return skipped;
}
/** /**
* Finds the maximum number of bytes we can read or skip, from this stream. * Finds the maximum number of bytes we can read or skip, from this stream.
* *
* @param pLength the requested length * @param length the requested length
* @return the maximum number of bytes to read * @return the maximum number of bytes to read
*/ */
private long findMaxLen(long pLength) { private long findMaxLen(long length) {
if (bytesLeft < pLength) { return bytesLeft < length ? Math.max(bytesLeft, 0) : length;
return (int) Math.max(bytesLeft, 0);
}
else {
return pLength;
}
}
@Override
public long skip(long pLength) throws IOException {
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
bytesLeft -= skipped;
return skipped;
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
@@ -904,7 +904,7 @@ public final class StringUtil {
} }
catch (ParseException pe) { catch (ParseException pe) {
// Wrap in RuntimeException // Wrap in RuntimeException
throw new IllegalArgumentException(pe.getMessage()); throw new IllegalArgumentException(pe.getMessage() + " at pos " + pe.getErrorOffset());
} }
} }
@@ -593,8 +593,8 @@ public class StringUtilTest {
cal.clear(); cal.clear();
cal.set(Calendar.HOUR, 1); cal.set(Calendar.HOUR, 1);
cal.set(Calendar.MINUTE, 2); cal.set(Calendar.MINUTE, 2);
date = StringUtil.toDate("1:02 am", format = new SimpleDateFormat("HH:mm");
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US)); date = StringUtil.toDate("1:02", format);
assertNotNull(date); assertNotNull(date);
assertEquals(cal.getTime(), date); assertEquals(cal.getTime(), date);
} }
+2 -2
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
@@ -47,7 +47,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.1</version> <version>4.13.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.contrib</groupId> <groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId> <artifactId>contrib</artifactId>
@@ -65,7 +65,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.1</version> <version>4.13.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-batik</artifactId> <artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name> <name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -17,7 +17,7 @@
<properties> <properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name> <project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
<batik.version>1.15</batik.version> <batik.version>1.16</batik.version>
</properties> </properties>
<build> <build>
@@ -51,7 +51,7 @@
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.11.0</version> <version>2.13.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-bmp</artifactId> <artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name> <name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-clippath</artifactId> <artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name> <name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-core</artifactId> <artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name> <name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.stream; package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl; import javax.imageio.stream.ImageInputStreamImpl;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@@ -52,6 +53,10 @@ import static java.lang.Math.max;
* for shorter reads, like single byte or bit reads. * for shorter reads, like single byte or bit reads.
*/ */
final class BufferedChannelImageInputStream extends ImageInputStreamImpl { final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private static final Closeable CLOSEABLE_STUB = new Closeable() {
@Override public void close() {}
};
static final int DEFAULT_BUFFER_SIZE = 8192; static final int DEFAULT_BUFFER_SIZE = 8192;
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
@@ -63,6 +68,7 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private final byte[] integralCacheArray = integralCache.array(); private final byte[] integralCacheArray = integralCache.array();
private SeekableByteChannel channel; private SeekableByteChannel channel;
private Closeable closeable;
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
@@ -86,49 +92,62 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
* @throws SecurityException if a security manager is installed, and it denies read access to 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 { public BufferedChannelImageInputStream(final Path file) throws IOException {
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ)); this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
} }
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
* <p>
* Closing this stream will close the {@code RandomAccessFile}.
* </p>
* *
* @param file a {@code RandomAccessFile} to read from. * @param file a {@code RandomAccessFile} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}. * @throws IllegalArgumentException if {@code file} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final RandomAccessFile file) { public BufferedChannelImageInputStream(final RandomAccessFile file) {
// Assumption: Closing the FileChannel will also close its backing RandomAccessFile // Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
// (it does in the OpenJDK implementation, and it makes sense, although I can't see this is documented behaviour). this(notNull(file, "file").getChannel(), true);
this(notNull(file, "file").getChannel());
} }
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
* <p> * <p>
* Closing this stream will close the {@code FileInputStream}. * Closing this stream will <em>not</em> close the {@code FileInputStream}.
* </p> * </p>
* *
* @param inputStream a {@code FileInputStream} to read from. * @param inputStream a {@code FileInputStream} to read from.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}. * @throws IllegalArgumentException if {@code inputStream} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final FileInputStream inputStream) { public BufferedChannelImageInputStream(final FileInputStream inputStream) {
// Assumption: Closing the FileChannel will also close its backing FileInputStream (it does in the OpenJDK implementation, although I can't see this is documented). this(notNull(inputStream, "inputStream").getChannel(), false);
this(notNull(inputStream, "inputStream").getChannel());
} }
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
* <p> * <p>
* Closing this stream will close the {@code SeekableByteChannel}. * Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
* </p> * </p>
* *
* @param channel a {@code SeekableByteChannel} to read from. * @param channel a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}. * @throws IllegalArgumentException if {@code channel} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final SeekableByteChannel channel) { 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.channel = notNull(channel, "channel");
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -246,8 +265,14 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
buffer = null; buffer = null;
byteBuffer = null; byteBuffer = null;
channel.close();
channel = null; channel = null;
try {
closeable.close();
}
finally {
closeable = null;
}
} }
// Need to override the readShort(), readInt() and readLong() methods, // Need to override the readShort(), readInt() and readLong() methods,
@@ -315,9 +340,9 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
public void flushBefore(final long pos) throws IOException { public void flushBefore(final long pos) throws IOException {
super.flushBefore(pos); super.flushBefore(pos);
if (channel instanceof MemoryCache) { if (channel instanceof Cache) {
// In case of memory cache, free up memory // In case of memory cache, free up memory
((MemoryCache) channel).flushBefore(pos); ((Cache) channel).flushBefore(pos);
} }
} }
} }
@@ -53,7 +53,7 @@ public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStre
} }
// Otherwise, create a cache for backwards seeking // Otherwise, create a cache for backwards seeking
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(channel, cacheDir): new MemoryCache(channel)); return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
} }
throw new IllegalArgumentException("Expected input of type InputStream: " + input); throw new IllegalArgumentException("Expected input of type InputStream: " + input);
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
private final int dataOffset; private final int dataOffset;
private final int dataLength; private final int dataLength;
public ByteArrayImageInputStream(final byte[] pData) { public ByteArrayImageInputStream(final byte[] data) {
this(pData, 0, pData != null ? pData.length : -1); this(data, 0, data != null ? data.length : -1);
} }
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) { public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
data = notNull(pData, "data"); this.data = notNull(data, "data");
dataOffset = isBetween(0, pData.length, offset, "offset"); dataOffset = isMax(data.length, offset, "offset");
dataLength = isBetween(0, pData.length - offset, length, "length"); dataLength = isMax(data.length - offset, length, "length");
} }
private static int isBetween(final int low, final int high, final int value, final String name) { private static int isMax(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)); return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
} }
public int read() throws IOException { public int read() throws IOException {
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
return data[((int) streamPos++) + dataOffset] & 0xff; 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) { if (streamPos >= dataLength) {
return -1; return -1;
} }
int length = (int) Math.min(this.dataLength - streamPos, pLength); int length = (int) Math.min(dataLength - streamPos, len);
bitOffset = 0; bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length); System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
streamPos += length; streamPos += length;
return length; return length;
@@ -0,0 +1,7 @@
package com.twelvemonkeys.imageio.stream;
import java.nio.channels.SeekableByteChannel;
interface Cache extends SeekableByteChannel {
void flushBefore(long pos);
}
@@ -26,19 +26,19 @@ import static java.nio.file.StandardOpenOption.WRITE;
// the usual {@link #read read} and {@link #write write} methods. From the // the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively // standpoint of performance it is generally only worth mapping relatively
// large files into memory. // large files into memory.
final class DiskCache implements SeekableByteChannel { final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13; final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache; private final FileChannel cache;
private final ReadableByteChannel channel; private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor? // TODO: Perhaps skip this constructor?
DiskCache(InputStream stream, File cacheDir) throws IOException { FileCache(InputStream stream, File cacheDir) throws IOException {
// Stream will be closed with channel, documented behavior // Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir); this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
} }
public DiskCache(ReadableByteChannel channel, File cacheDir) throws IOException { public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
this.channel = notNull(channel, "channel"); this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory"); isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
@@ -65,12 +65,7 @@ final class DiskCache implements SeekableByteChannel {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { cache.close();
cache.close();
}
finally {
channel.close();
}
} }
@Override @Override
@@ -110,5 +105,8 @@ final class DiskCache implements SeekableByteChannel {
public SeekableByteChannel truncate(long size) { public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException(); throw new NonWritableChannelException();
} }
@Override public void flushBefore(long pos) {
}
} }
@@ -13,12 +13,16 @@ import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min; import static java.lang.Math.min;
public final class MemoryCache implements SeekableByteChannel { final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13; 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 List<byte[]> cache = new ArrayList<>();
private final ReadableByteChannel channel; private final ReadableByteChannel channel;
private int maxBlock = Integer.MAX_VALUE;
private long length; private long length;
private long position; private long position;
private long start; private long start;
@@ -34,12 +38,14 @@ public final class MemoryCache implements SeekableByteChannel {
byte[] fetchBlock() throws IOException { byte[] fetchBlock() throws IOException {
long currPos = position; long currPos = position;
long index = currPos / BLOCK_SIZE; long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) { if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded"); throw new IOException("Memory cache max size exceeded");
} }
if (index > maxBlock) {
return NULL_BLOCK;
}
while (index >= cache.size()) { while (index >= cache.size()) {
byte[] block; byte[] block;
@@ -51,7 +57,14 @@ public final class MemoryCache implements SeekableByteChannel {
} }
cache.add(block); cache.add(block);
length += readBlock(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); return cache.get((int) index);
@@ -63,7 +76,7 @@ public final class MemoryCache implements SeekableByteChannel {
while (wrapped.hasRemaining()) { while (wrapped.hasRemaining()) {
int count = channel.read(wrapped); int count = channel.read(wrapped);
if (count == -1) { if (count == -1) {
// Last block // Last block, EOF found
break; break;
} }
} }
@@ -78,23 +91,18 @@ public final class MemoryCache implements SeekableByteChannel {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { cache.clear();
cache.clear();
}
finally {
channel.close();
}
} }
@Override @Override
public int read(ByteBuffer dest) throws IOException { public int read(ByteBuffer dest) throws IOException {
byte[] buffer = fetchBlock(); byte[] buffer = fetchBlock();
int bufferPos = (int) (position % BLOCK_SIZE);
if (position >= length) { if (position >= length) {
return -1; return -1;
} }
int bufferPos = (int) (position % BLOCK_SIZE);
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position)); int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
dest.put(buffer, bufferPos, len); dest.put(buffer, bufferPos, len);
@@ -135,7 +143,8 @@ public final class MemoryCache implements SeekableByteChannel {
throw new NonWritableChannelException(); throw new NonWritableChannelException();
} }
void flushBefore(long pos) { @Override
public void flushBefore(long pos) {
if (pos < start) { if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position"); throw new IndexOutOfBoundsException("pos < flushed position");
} }
@@ -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. * Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
* *
* @param pStream the underlying stream * @param stream the underlying stream
* @param pLength the maximum length to read from the stream. * @param length the maximum length to read from the stream.
* Note that {@code pStream} may contain less than this maximum number of bytes. * 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 IOException if {@code stream}'s position can't be determined.
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0} * @throws IllegalArgumentException if {@code stream == null} or {@code length < 0}
*/ */
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException { public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException {
Validate.notNull(pStream, "stream"); Validate.notNull(stream, "stream");
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d"); Validate.isTrue(length >= 0, length, "length < 0: %d");
stream = pStream; this.stream = stream;
startPos = pStream.getStreamPosition(); this.startPos = stream.getStreamPosition();
length = pLength; this.length = length;
} }
public int read() throws IOException { 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 if (streamPos >= length) { // Local EOF
return -1; return -1;
} }
// Safe cast, as pLength can never cause int overflow // Safe cast, as len can never cause int overflow
int length = (int) Math.min(pLength, this.length - streamPos); int length = (int) Math.min(len, this.length - streamPos);
int count = stream.read(pBytes, pOffset, length); int count = stream.read(bytes, off, length);
if (count >= 0) { if (count >= 0) {
streamPos += count; streamPos += count;
@@ -113,18 +113,18 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
} }
@Override @Override
public void seek(final long pPosition) throws IOException { public void seek(final long position) throws IOException {
if (pPosition < getFlushedPosition()) { if (position < getFlushedPosition()) {
throw new IndexOutOfBoundsException("pos < flushedPosition"); throw new IndexOutOfBoundsException("pos < flushedPosition");
} }
stream.seek(startPos + pPosition); stream.seek(startPos + position);
streamPos = pPosition; streamPos = position;
} }
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"}) @SuppressWarnings("MethodDoesntCallSuperMethod")
@Override @Override
protected void finalize() throws Throwable { protected void finalize() {
// Empty finalizer (for improved performance; no need to call super.finalize() in this case) // Empty finalizer (for improved performance; no need to call super.finalize() in this case)
} }
} }
@@ -80,7 +80,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
// Otherwise revert to cached // Otherwise revert to cached
InputStream urlStream = url.openStream(); InputStream urlStream = url.openStream();
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(urlStream, cacheDir) : new MemoryCache(urlStream)); return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
} }
throw new IllegalArgumentException("Expected input of type URL: " + input); throw new IllegalArgumentException("Expected input of type URL: " + input);
@@ -36,9 +36,11 @@ import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile; import java.awt.color.ICC_Profile;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assume.assumeFalse;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
public class KCMSSanitizerStrategyTest { public class KCMSSanitizerStrategyTest {
@@ -56,6 +58,8 @@ public class KCMSSanitizerStrategyTest {
@Test @Test
public void testFixProfileUpdateHeader() throws Exception { public void testFixProfileUpdateHeader() throws Exception {
assumeICC_ProfileNotSealed(); // Ignores test for JDK 19+
byte[] header = new byte[128]; byte[] header = new byte[128];
header[ICC_Profile.icHdrRenderingIntent + 3] = 1; header[ICC_Profile.icHdrRenderingIntent + 3] = 1;
ICC_Profile profile = mock(ICC_Profile.class); ICC_Profile profile = mock(ICC_Profile.class);
@@ -69,6 +73,17 @@ public class KCMSSanitizerStrategyTest {
verify(profile).setData(eq(ICC_Profile.icSigHead), any(byte[].class)); verify(profile).setData(eq(ICC_Profile.icSigHead), any(byte[].class));
} }
static void assumeICC_ProfileNotSealed() {
try {
Method isSealed = Class.class.getMethod("isSealed");
Boolean result = (Boolean) isSealed.invoke(ICC_Profile.class);
assumeFalse("Can't mock ICC_Profile, class is sealed (as of JDK 19).", result);
}
catch (ReflectiveOperationException ignore) {
// We can't have sealed classes if we don't have the isSealed method...
}
}
@Test @Test
public void testFixProfileCorbisRGB() throws IOException { public void testFixProfileCorbisRGB() throws IOException {
// TODO: Consider re-writing this using mocks, to avoid dependencies on the CMS implementation // TODO: Consider re-writing this using mocks, to avoid dependencies on the CMS implementation
@@ -34,6 +34,7 @@ import org.junit.Test;
import java.awt.color.ICC_Profile; import java.awt.color.ICC_Profile;
import static com.twelvemonkeys.imageio.color.KCMSSanitizerStrategyTest.assumeICC_ProfileNotSealed;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -46,6 +47,8 @@ public class LCMSSanitizerStrategyTest {
@Test @Test
public void testFixProfile() throws Exception { public void testFixProfile() throws Exception {
assumeICC_ProfileNotSealed(); // Ignores test for JDK 19+
ICC_Profile profile = mock(ICC_Profile.class); ICC_Profile profile = mock(ICC_Profile.class);
new LCMSSanitizerStrategy().fixProfile(profile); new LCMSSanitizerStrategy().fixProfile(profile);
@@ -57,7 +57,7 @@ import static org.mockito.Mockito.verify;
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ * @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/ */
// TODO: Remove this test, and instead test the disk cache directly! // TODO: Remove this test, and instead test the disk cache directly!
public class BufferedChannelImageInputStreamDiskCacheTest { public class BufferedChannelImageInputStreamFileCacheTest {
private final Random random = new Random(170984354357234566L); private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) { private InputStream randomDataToInputStream(byte[] data) {
@@ -68,7 +68,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreate() throws IOException { public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(new ByteArrayInputStream(new byte[0]), null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
} }
} }
@@ -76,7 +76,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreateNullStream() throws IOException { public void testCreateNullStream() throws IOException {
try { try {
new DiskCache((InputStream) null, null); new FileCache((InputStream) null, null);
fail("Expected IllegalArgumentException"); fail("Expected IllegalArgumentException");
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
@@ -90,7 +90,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreateNullChannel() throws IOException { public void testCreateNullChannel() throws IOException {
try { try {
new DiskCache((ReadableByteChannel) null, null); new FileCache((ReadableByteChannel) null, null);
fail("Expected IllegalArgumentException"); fail("Expected IllegalArgumentException");
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
@@ -106,7 +106,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024]; byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data); InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) { for (byte value : data) {
@@ -122,7 +122,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024]; byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data); InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024]; byte[] result = new byte[1024];
@@ -141,7 +141,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 14]; byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data); InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7]; byte[] result = new byte[7];
@@ -159,7 +159,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 18]; byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data); InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9]; byte[] result = new byte[9];
@@ -180,7 +180,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[256]; byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data); InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2]; byte[] buffer = new byte[data.length * 2];
@@ -198,7 +198,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) { for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit()); assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
} }
@@ -212,7 +212,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) { for (int i = 1; i <= 64; i++) {
stream.seek(0); stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i)); assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
@@ -228,7 +228,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 60; i++) { for (int i = 1; i <= 60; i++) {
stream.seek(0); stream.seek(0);
stream.setBitOffset(i % 8); stream.setBitOffset(i % 8);
@@ -244,7 +244,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) { for (int i = 0; i < bytes.length / 2; i++) {
@@ -282,7 +282,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) { for (int i = 0; i < bytes.length / 4; i++) {
@@ -320,7 +320,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) { for (int i = 0; i < bytes.length / 8; i++) {
@@ -357,7 +357,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[9]; byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.seek(1000); stream.seek(1000);
assertEquals(-1, stream.read()); assertEquals(-1, stream.read());
@@ -406,11 +406,11 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testClose() throws IOException { public void testClose() throws IOException {
// Create wrapper stream // Create wrapper stream
InputStream mock = mock(InputStream.class); Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(mock, null)); ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close(); stream.close();
verify(mock, only()).close(); verify(cache, only()).close();
} }
@Test @Test
@@ -422,7 +422,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[size]; byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) { try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
byte[] result = new byte[size]; byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
@@ -402,15 +402,33 @@ public class BufferedChannelImageInputStreamMemoryCacheTest {
assertEquals(-1, stream.read()); assertEquals(-1, stream.read());
} }
} }
@Test
public void testSeekWayPastEOFShouldNotThrowOOME() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.seek(Integer.MAX_VALUE * 4L * 512L); // ~4 TB
assertEquals(-1, stream.read()); // No OOME should happen...
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test @Test
public void testClose() throws IOException { public void testClose() throws IOException {
// Create wrapper stream // Create wrapper stream
InputStream mock = mock(InputStream.class); Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(mock)); ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close(); stream.close();
verify(mock, only()).close(); verify(cache, only()).close();
} }
@Test @Test
@@ -47,7 +47,7 @@ import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals; import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** /**
@@ -403,14 +403,11 @@ public class BufferedChannelImageInputStreamTest {
@Test @Test
public void testCloseChannel() throws IOException { public void testCloseChannel() throws IOException {
// NOTE: As the stream-based constructor is chained to the channel-based one, SeekableByteChannel channel = mock(SeekableByteChannel.class);
// we'll rely on the fact that closing the channel will close the stream. ImageInputStream stream = new BufferedChannelImageInputStream(channel);
SeekableByteChannel mock = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(mock);
stream.close(); stream.close();
verify(mock, only()).close(); verify(channel, never()).close();
} }
@Test @Test
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-hdr</artifactId> <artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name> <name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-icns</artifactId> <artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name> <name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-iff</artifactId> <artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name> <name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-jpeg-jai-interop</artifactId> <artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name> <name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId> <artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name> <name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name> <name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
@@ -35,8 +35,11 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
@@ -50,6 +53,7 @@ import java.util.Iterator;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import org.junit.Test; import org.junit.Test;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -490,12 +494,15 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA")); assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA"));
} }
@Test(timeout = 1500L) @Test(timeout = 2500L)
public void testNoExternalRequest() throws Exception { public void testNoExternalRequest() throws Exception {
// TODO: Use dynamic port? String maliciousXML = resourceAsString("/xmp/xmp-jpeg-xxe.xml");
try (HTTPServer server = new HTTPServer(7777)) {
try { try (HTTPServer server = new HTTPServer()) {
createReader().read(getResourceAsIIS("/xmp/xmp-jpeg-xxe.xml")); String dynamicXML = maliciousXML.replace("http://localhost:7777/", "http://localhost:" + server.port() + "/");
try (DirectImageInputStream input = new DirectImageInputStream(new ByteArrayInputStream(dynamicXML.getBytes(StandardCharsets.UTF_8)));) {
createReader().read(input);
} catch (IOException ioe) { } catch (IOException ioe) {
if (ioe.getMessage().contains("501")) { if (ioe.getMessage().contains("501")) {
throw new AssertionError("Reading should not cause external requests", ioe); throw new AssertionError("Reading should not cause external requests", ioe);
@@ -507,12 +514,26 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
} }
} }
private String resourceAsString(String name) throws IOException {
StringBuilder builder = new StringBuilder(1024);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(getResource(name).openStream(), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line)
.append('\n');
}
}
return builder.toString();
}
private static class HTTPServer implements AutoCloseable { private static class HTTPServer implements AutoCloseable {
private final ServerSocket server; private final ServerSocket server;
private final Thread thread; private final Thread thread;
HTTPServer(int port) throws IOException { HTTPServer() throws IOException {
server = new ServerSocket(port, 1); server = new ServerSocket(0, 1);
thread = new Thread(new Runnable() { thread = new Thread(new Runnable() {
@Override public void run() { @Override public void run() {
serve(); serve();
@@ -521,6 +542,10 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
thread.start(); thread.start();
} }
public final int port() {
return server.getLocalPort();
}
private void serve() { private void serve() {
try { try {
Socket client = server.accept(); Socket client = server.accept();
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-pcx</artifactId> <artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name> <name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -13,7 +13,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
} }
private static PlanarConfiguration planarConfiguration(PCXHeader header) { private static PlanarConfiguration planarConfiguration(PCXHeader header) {
System.out.println("header = " + header);
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null; return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
} }
@@ -25,6 +24,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
return "RLE"; return "RLE";
} }
return "Uknown"; return "Unknown";
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-pdf</artifactId> <artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name> <name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-pict</artifactId> <artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name> <name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-pnm</artifactId> <artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name> <name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
@@ -30,8 +30,6 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.ImageWriterBase;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
@@ -40,7 +38,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.awt.image.DataBuffer; import java.awt.image.*;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
@@ -53,11 +51,7 @@ abstract class HeaderWriter {
this.imageOutput = imageOutput; this.imageOutput = imageOutput;
} }
public static void write(final IIOImage image, final ImageWriterBase writer, final ImageOutputStream imageOutput) throws IOException { static HeaderWriter createHeaderWriter(final String formatName, final ImageOutputStream imageOutput) {
createHeaderWriter(writer.getFormatName(), imageOutput).writeHeader(image, writer.getOriginatingProvider());
}
private static HeaderWriter createHeaderWriter(final String formatName, final ImageOutputStream imageOutput) {
if (formatName.equals("pam")) { if (formatName.equals("pam")) {
return new PAMHeaderWriter(imageOutput); return new PAMHeaderWriter(imageOutput);
} }
@@ -83,25 +77,36 @@ abstract class HeaderWriter {
return image.hasRaster() ? image.getRaster().getNumBands() : image.getRenderedImage().getSampleModel().getNumBands(); return image.hasRaster() ? image.getRaster().getNumBands() : image.getRenderedImage().getSampleModel().getNumBands();
} }
protected final SampleModel getSampleModel(final IIOImage image) {
return image.hasRaster() ? image.getRaster().getSampleModel() : image.getRenderedImage().getSampleModel();
}
protected int getMaxVal(final IIOImage image) { protected int getMaxVal(final IIOImage image) {
int transferType = getTransferType(image); int transferType = getTransferType(image);
if (transferType == DataBuffer.TYPE_BYTE) { switch (transferType) {
return PNM.MAX_VAL_8BIT; case DataBuffer.TYPE_BYTE:
} return PNM.MAX_VAL_8BIT;
else if (transferType == DataBuffer.TYPE_USHORT) {
return PNM.MAX_VAL_16BIT; case DataBuffer.TYPE_USHORT:
} return PNM.MAX_VAL_16BIT;
// else if (transferType == DataBuffer.TYPE_INT) {
// TODO: Support TYPE_INT through conversion, if number of channels is 3 or 4 (TYPE_INT_RGB, TYPE_INT_ARGB) case DataBuffer.TYPE_INT:
// } // We support TYPE_INT through conversion, if number of channels is 3 or 4 (TYPE_INT_RGB, TYPE_INT_ARGB)
else { SampleModel sampleModel = getSampleModel(image);
throw new IllegalArgumentException("Unsupported data type: " + transferType); int numBands = sampleModel.getNumBands();
if (sampleModel instanceof SinglePixelPackedSampleModel && (numBands == 3 || numBands == 4)) {
return PNM.MAX_VAL_8BIT;
}
// ...else fall through
default:
throw new IllegalArgumentException("Unsupported data type: " + transferType);
} }
} }
protected final int getTransferType(final IIOImage image) { protected final int getTransferType(final IIOImage image) {
return image.hasRaster() ? image.getRaster().getTransferType() : image.getRenderedImage().getSampleModel().getTransferType(); return image.hasRaster() ? image.getRaster().getTransferType() : image.getRenderedImage().getSampleModel().getTransferType();
} }
protected final void writeComments(final IIOMetadata metadata, final ImageWriterSpi provider) throws IOException { protected final void writeComments(final IIOMetadata metadata, final ImageWriterSpi provider) throws IOException {
@@ -120,5 +125,4 @@ abstract class HeaderWriter {
} }
} }
} }
} }
@@ -30,6 +30,8 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
@@ -44,6 +46,8 @@ final class PAMHeaderWriter extends HeaderWriter {
@Override @Override
public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException { public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
TupleType tupleType = tupleType(image);
// Write PAM magic // Write PAM magic
imageOutput.writeShort(PNM.PAM); imageOutput.writeShort(PNM.PAM);
imageOutput.write('\n'); imageOutput.write('\n');
@@ -52,12 +56,19 @@ final class PAMHeaderWriter extends HeaderWriter {
// Write width/height and number of channels // Write width/height and number of channels
imageOutput.write(String.format("WIDTH %s\nHEIGHT %s\n", getWidth(image), getHeight(image)).getBytes(UTF_8)); imageOutput.write(String.format("WIDTH %s\nHEIGHT %s\n", getWidth(image), getHeight(image)).getBytes(UTF_8));
imageOutput.write(String.format("DEPTH %s\n", getNumBands(image)).getBytes(UTF_8)); imageOutput.write(String.format("DEPTH %s\n", getNumBands(image)).getBytes(UTF_8));
// TODO: maxSample (8 or16 bit)
imageOutput.write(String.format("MAXVAL %s\n", getMaxVal(image)).getBytes(UTF_8)); imageOutput.write(String.format("MAXVAL %s\n", getMaxVal(image)).getBytes(UTF_8));
// TODO: Determine tuple type based on input color model and image data
TupleType tupleType = getNumBands(image) > 3 ? TupleType.RGB_ALPHA : TupleType.RGB;
imageOutput.write(String.format("TUPLTYPE %s\nENDHDR\n", tupleType).getBytes(UTF_8)); imageOutput.write(String.format("TUPLTYPE %s\nENDHDR\n", tupleType).getBytes(UTF_8));
} }
private static TupleType tupleType(IIOImage image) {
TupleType tupleType = image.hasRaster()
? TupleType.forPAM(image.getRaster())
: TupleType.forPAM(ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()));
if (tupleType == null) {
throw new IllegalArgumentException("Unknown TupleType for " + (image.hasRaster() ? image.getRaster() : image.getRenderedImage()));
}
return tupleType;
}
} }
@@ -45,12 +45,11 @@ public final class PAMImageWriterSpi extends ImageWriterSpiBase {
super(new PAMProviderInfo()); super(new PAMProviderInfo());
} }
public boolean canEncodeImage(final ImageTypeSpecifier pType) { public boolean canEncodeImage(final ImageTypeSpecifier type) {
// TODO: FixMe return TupleType.forPAM(type) != null;
return true;
} }
public ImageWriter createWriterInstance(final Object pExtension) { public ImageWriter createWriterInstance(final Object extension) {
return new PNMImageWriter(this); return new PNMImageWriter(this);
} }
@@ -69,7 +69,7 @@ final class PFMHeaderParser extends HeaderParser {
public PNMHeader parse() throws IOException { public PNMHeader parse() throws IOException {
int width = 0; int width = 0;
int height = 0; int height = 0;
float maxSample = tupleType == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? 1 : 0; // PBM has no maxSample line float maxSample = 0;
List<String> comments = new ArrayList<>(); List<String> comments = new ArrayList<>();
@@ -77,7 +77,7 @@ final class PFMHeaderParser extends HeaderParser {
String line = input.readLine(); String line = input.readLine();
if (line == null) { if (line == null) {
throw new IIOException("Unexpeced end of stream"); throw new IIOException("Unexpected end of stream");
} }
int commentStart = line.indexOf('#'); int commentStart = line.indexOf('#');
@@ -30,6 +30,8 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
@@ -45,8 +47,7 @@ final class PNMHeaderWriter extends HeaderWriter {
@Override @Override
public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException { public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
// Write P4/P5/P6 magic (Support only RAW formats for now; if we are to support PLAIN formats, pass parameter) // Write P4/P5/P6 magic (Support only RAW formats for now; if we are to support PLAIN formats, pass parameter)
// TODO: Determine PBM, PBM or PPM based on input color model and image data? short type = type(image);
short type = PNM.PPM;
imageOutput.writeShort(type); imageOutput.writeShort(type);
imageOutput.write('\n'); imageOutput.write('\n');
@@ -61,4 +62,35 @@ final class PNMHeaderWriter extends HeaderWriter {
imageOutput.write(String.format("%s\n", getMaxVal(image)).getBytes(UTF_8)); imageOutput.write(String.format("%s\n", getMaxVal(image)).getBytes(UTF_8));
} }
} }
private short type(IIOImage image) {
TupleType type = tupleType(image);
if (type != null) {
switch (type) {
case BLACKANDWHITE_WHITE_IS_ZERO:
return PNM.PBM;
case GRAYSCALE:
return PNM.PGM;
case RGB:
return PNM.PPM;
default:
// fall through...
}
}
throw new IllegalArgumentException("Unsupported tupleType: " + type);
}
private static TupleType tupleType(IIOImage image) {
TupleType tupleType = image.hasRaster()
? TupleType.forPNM(image.getRaster())
: TupleType.forPNM(ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()));
if (tupleType == null) {
throw new IllegalArgumentException("Unknown TupleType for " + (image.hasRaster() ? image.getRaster() : image.getRenderedImage()));
}
return tupleType;
}
} }
@@ -31,14 +31,17 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.RasterUtils;
import javax.imageio.*; import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage; import javax.imageio.stream.ImageOutputStream;
import java.awt.image.DataBuffer; import java.awt.image.*;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -69,7 +72,7 @@ public final class PNMImageWriter extends ImageWriterBase {
// TODO: Issue warning if streamMetadata is non-null? // TODO: Issue warning if streamMetadata is non-null?
// TODO: Issue warning if IIOImage contains thumbnails or other data we can't store? // TODO: Issue warning if IIOImage contains thumbnails or other data we can't store?
HeaderWriter.write(image, this, imageOutput); writeHeader(image, this, imageOutput);
// TODO: Sub region // TODO: Sub region
// TODO: Subsampling // TODO: Subsampling
@@ -80,16 +83,20 @@ public final class PNMImageWriter extends ImageWriterBase {
processImageComplete(); processImageComplete();
} }
private void writeHeader(final IIOImage image, final ImageWriterBase writer, final ImageOutputStream imageOutput) throws IOException {
HeaderWriter.createHeaderWriter(writer.getFormatName(), imageOutput).writeHeader(image, writer.getOriginatingProvider());
}
private void writeImageData(final IIOImage image) throws IOException { private void writeImageData(final IIOImage image) throws IOException {
// - dump data as is (or convert, if TYPE_INT_xxx) // - dump data as is (or convert, if TYPE_INT_xxx
// Enforce RGB/CMYK order for such data! // Enforce RGB/CMYK order for such data!
// TODO: Loop over x/y tiles, using 0,0 is only valid for BufferedImage // TODO: Loop over x/y tiles, using 0,0 is only valid for BufferedImage
// TODO: PNM/PAM does not support tiling, we must iterate all tiles along the x-axis for each row we write // TODO: PNM/PAM does not support tiling, we must iterate all tiles along the x-axis for each row we write
Raster tile = image.hasRaster() ? image.getRaster() : image.getRenderedImage().getTile(0, 0); Raster tile = image.hasRaster() ? image.getRaster() : image.getRenderedImage().getTile(0, 0);
tile = tile.getTransferType() == DataBuffer.TYPE_INT ? RasterUtils.asByteRaster(tile) : tile;
SampleModel sampleModel = tile.getSampleModel(); SampleModel sampleModel = tile.getSampleModel();
DataBuffer dataBuffer = tile.getDataBuffer(); DataBuffer dataBuffer = tile.getDataBuffer();
int tileWidth = tile.getWidth(); int tileWidth = tile.getWidth();
@@ -48,12 +48,11 @@ public final class PNMImageWriterSpi extends ImageWriterSpiBase {
super(new PNMProviderInfo()); super(new PNMProviderInfo());
} }
public boolean canEncodeImage(final ImageTypeSpecifier pType) { public boolean canEncodeImage(final ImageTypeSpecifier type) {
// TODO: FixMe: Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB return TupleType.forPNM(type) != null;
return true;
} }
public ImageWriter createWriterInstance(final Object pExtension) { public ImageWriter createWriterInstance(final Object extension) {
return new PNMImageWriter(this); return new PNMImageWriter(this);
} }
@@ -30,7 +30,10 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import javax.imageio.ImageTypeSpecifier;
import java.awt.*; import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
enum TupleType { enum TupleType {
// Official: // Official:
@@ -80,4 +83,150 @@ enum TupleType {
public boolean isValidMaxSample(int maxSample) { public boolean isValidMaxSample(int maxSample) {
return maxSample >= minMaxSample && maxSample <= maxMaxSample; return maxSample >= minMaxSample && maxSample <= maxMaxSample;
} }
static TupleType forPNM(Raster raster) {
return filterPNM(forPAM(raster));
}
static TupleType forPNM(ImageTypeSpecifier type) {
return filterPNM(forPAM(type));
}
private static TupleType filterPNM(TupleType tupleType) {
if (tupleType == null) {
return null;
}
switch (tupleType) {
case BLACKANDWHITE:
return BLACKANDWHITE_WHITE_IS_ZERO;
case GRAYSCALE:
case RGB:
return tupleType;
default:
return null;
}
}
static TupleType forPAM(Raster raster) {
SampleModel sampleModel = raster.getSampleModel();
switch (sampleModel.getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_INT:
// B/W, Gray or RGB
int bands = sampleModel.getNumBands();
if (bands == 1 && sampleModel.getSampleSize(0) == 1) {
return TupleType.BLACKANDWHITE;
}
else if (bands == 2 && sampleModel.getSampleSize(0) == 1 && sampleModel.getSampleSize(1) == 1) {
return TupleType.BLACKANDWHITE_ALPHA;
}
// We can only write 8 or 16 bits/band
if (!(sampleModel.getSampleSize(0) == 8 || sampleModel.getSampleSize(0) == 16)) {
return null;
}
for (int i = 1; i < bands; i++) {
if (sampleModel.getSampleSize(0) != sampleModel.getSampleSize(i)) {
return null;
}
}
if (bands == 1) {
return TupleType.GRAYSCALE;
}
else if (bands == 2) {
return TupleType.GRAYSCALE_ALPHA;
}
else if (bands == 3) {
return TupleType.RGB;
}
else if (bands == 4) {
return TupleType.RGB_ALPHA;
}
// ...else fall through...
}
return null;
}
static TupleType forPAM(ImageTypeSpecifier type) {
// Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
switch (type.getBufferedImageType()) {
// 1 bit b/w or b/w + a
case BufferedImage.TYPE_BYTE_BINARY:
switch (type.getNumBands()) {
case 1:
return type.getBitsPerBand(0) == 1 ? TupleType.BLACKANDWHITE : null;
case 2:
return type.getBitsPerBand(0) == 2 || type.getBitsPerBand(0) == 1 && type.getBitsPerBand(1) == 1 ? TupleType.BLACKANDWHITE_ALPHA : null;
default:
return null;
}
// Gray
case BufferedImage.TYPE_BYTE_GRAY:
case BufferedImage.TYPE_USHORT_GRAY:
return TupleType.GRAYSCALE;
// RGB
case BufferedImage.TYPE_3BYTE_BGR:
case BufferedImage.TYPE_INT_RGB:
case BufferedImage.TYPE_INT_BGR:
return TupleType.RGB;
// RGBA
case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
case BufferedImage.TYPE_INT_ARGB:
case BufferedImage.TYPE_INT_ARGB_PRE:
return TupleType.RGB_ALPHA;
default:
// BYTE, USHORT or INT (packed)
switch (type.getSampleModel().getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_INT:
// Gray or RGB
ColorModel colorModel = type.getColorModel();
if (!(colorModel instanceof IndexColorModel)) {
ColorSpace cs = colorModel.getColorSpace();
// We can only write 8 or 16 bits/band
int bands = type.getNumBands();
if (!(type.getBitsPerBand(0) == 8 || type.getBitsPerBand(0) == 16)) {
return null;
}
for (int i = 1; i < bands; i++) {
if (type.getBitsPerBand(0) != type.getBitsPerBand(i)) {
return null;
}
}
if (cs.getType() == ColorSpace.TYPE_GRAY && bands == 1) {
return TupleType.GRAYSCALE;
}
else if (cs.getType() == ColorSpace.TYPE_GRAY && colorModel.hasAlpha() && bands == 2) {
return TupleType.GRAYSCALE_ALPHA;
}
else if (cs.getType() == ColorSpace.TYPE_RGB && bands == 3) {
return TupleType.RGB;
}
else if (cs.getType() == ColorSpace.TYPE_RGB && colorModel.hasAlpha() && bands == 4) {
return TupleType.RGB_ALPHA;
}
else if (cs.getType() == ColorSpace.TYPE_CMYK && bands == 4) {
return TupleType.CMYK;
}
else if (cs.getType() == ColorSpace.TYPE_CMYK && colorModel.hasAlpha() && bands == 5) {
return TupleType.CMYK_ALPHA;
}
// ...else fall through...
}
}
}
return null;
}
} }
@@ -30,15 +30,23 @@
package com.twelvemonkeys.imageio.plugins.pnm; package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter; import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists; import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist; import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* PAMImageWriterSpiTest. * PAMImageWriterSpiTest.
@@ -65,4 +73,85 @@ public class PAMImageWriterSpiTest {
public void getOutputTypes() { public void getOutputTypes() {
assertNotNull(spi.getOutputTypes()); assertNotNull(spi.getOutputTypes());
} }
@Test
public void canEncodeImageBinary() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)));
}
@Test
public void canNotEncodeImageIndexed() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED)));
}
@Test
public void canEncodeImageGray() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY)));
}
@Test
public void canEncodeImageGrayAlpha() {
ComponentColorModel grayAlphaByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaByte, grayAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel grayAlphaUShort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaUShort, grayAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canEncodeImageRGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)));
}
@Test
public void canEncodeImageARGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)));
}
@Test
public void canEncodeImageCMYK() {
ComponentColorModel cmykByte = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykByte, cmykByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykUShort = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykUShort, cmykUShort.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykAlphaByte = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykAlphaByte, cmykAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykAlphaUShort = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykAlphaUShort, cmykAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canNotEncodeImageNonGrayOrRGB() {
ComponentColorModel xyzByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzByte, xyzByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel xyzUshort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzUshort, xyzUshort.createCompatibleSampleModel(1, 1))));
}
} }
@@ -0,0 +1,37 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.util.Arrays;
import java.util.List;
public class PAMImageWriterTest extends ImageWriterAbstractTest<PNMImageWriter> {
// NOTE: It's the same writer, however, the different SPI configures PAM mode, and enables extra formats
@Override
protected ImageWriterSpi createProvider() {
return new PAMImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB_PRE),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT), Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, 10, 10, 2, null), false, null),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE), Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 10, 10, 4, null), false, null),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT), Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, 10, 10, 5, null), false, null)
);
}
}
@@ -32,13 +32,19 @@ package com.twelvemonkeys.imageio.plugins.pnm;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter; import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists; import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist; import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* PNMImageWriterSpiTest. * PNMImageWriterSpiTest.
@@ -65,4 +71,70 @@ public class PNMImageWriterSpiTest {
public void getOutputTypes() { public void getOutputTypes() {
assertNotNull(spi.getOutputTypes()); assertNotNull(spi.getOutputTypes());
} }
@Test
public void canEncodeImageBinary() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)));
}
@Test
public void canNotEncodeImageIndexed() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED)));
}
@Test
public void canEncodeImageGray() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY)));
}
@Test
public void canNotEncodeImageGrayAlpha() {
ComponentColorModel grayAlphaByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaByte, grayAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel grayAlphaUShort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaUShort, grayAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canEncodeImageRGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)));
}
@Test
public void canNotEncodeImageARGB() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)));
}
@Test
public void canNotEncodeImageNonGrayOrRGB() {
ComponentColorModel xyzByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzByte, xyzByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel xyzUshort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzUshort, xyzUshort.createCompatibleSampleModel(1, 1))));
}
} }
@@ -3,8 +3,7 @@ package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest; import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage; import java.awt.image.*;
import java.awt.image.RenderedImage;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -17,10 +16,12 @@ public class PNMImageWriterTest extends ImageWriterAbstractTest<PNMImageWriter>
@Override @Override
protected List<? extends RenderedImage> getTestData() { protected List<? extends RenderedImage> getTestData() {
return Arrays.asList( return Arrays.asList(
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR), new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY), new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_USHORT_GRAY), new BufferedImage(100, 100, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR) new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR)
); );
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-psd</artifactId> <artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name> <name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -709,4 +709,6 @@ interface PSD extends com.twelvemonkeys.imageio.metadata.psd.PSD {
int luni = 'l' << 24 | 'u' << 16 | 'n' << 8 | 'i'; int luni = 'l' << 24 | 'u' << 16 | 'n' << 8 | 'i';
int lyid = 'l' << 24 | 'y' << 16 | 'i' << 8 | 'd'; int lyid = 'l' << 24 | 'y' << 16 | 'i' << 8 | 'd';
int lsct = 'l' << 24 | 's' << 16 | 'c' << 8 | 't'; int lsct = 'l' << 24 | 's' << 16 | 'c' << 8 | 't';
// Undocumented: Nested section divider setting
int lsdk = 'l' << 24 | 's' << 16 | 'd' << 8 | 'k';
} }
@@ -944,7 +944,7 @@ public final class PSDImageReader extends ImageReaderBase {
if (metadata.layerInfo == null) { if (metadata.layerInfo == null) {
while (imageInput.getStreamPosition() + 12 < metadata.layerAndMaskInfoStart + layerAndMaskInfoLength) { while (imageInput.getStreamPosition() + 12 < metadata.layerAndMaskInfoStart + layerAndMaskInfoLength) {
int resSig = imageInput.readInt(); int resSig = imageInput.readInt();
if (resSig != PSD.RESOURCE_TYPE) { if (resSig != PSD.RESOURCE_TYPE && resSig != PSD.RESOURCE_TYPE_LONG) {
processWarningOccurred(String.format("Bad resource alignment, expected: '8BIM' was '%s'", PSDUtil.intToStr(resSig))); processWarningOccurred(String.format("Bad resource alignment, expected: '8BIM' was '%s'", PSDUtil.intToStr(resSig)));
break; break;
} }
@@ -155,6 +155,7 @@ final class PSDLayerInfo {
layerId = pInput.readInt(); layerId = pInput.readInt();
break; break;
case PSD.lsdk:
case PSD.lsct: case PSD.lsct:
if (resourceLength < 4) { if (resourceLength < 4) {
throw new IIOException(String.format("Expected sectionDividerSetting length >= 4: %d", resourceLength)); throw new IIOException(String.format("Expected sectionDividerSetting length >= 4: %d", resourceLength));
@@ -203,6 +203,7 @@ public final class PSDMetadata extends AbstractMetadata {
IIOMetadataNode guideNode = new IIOMetadataNode("Guide"); IIOMetadataNode guideNode = new IIOMetadataNode("Guide");
guideNode.setAttribute("location", Integer.toString(guide.location)); guideNode.setAttribute("location", Integer.toString(guide.location));
guideNode.setAttribute("orientation", GUIDE_ORIENTATIONS[guide.direction]); guideNode.setAttribute("orientation", GUIDE_ORIENTATIONS[guide.direction]);
node.appendChild(guideNode);
} }
} }
else if (imageResource instanceof PSDPixelAspectRatio) { else if (imageResource instanceof PSDPixelAspectRatio) {
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream; import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.io.SubStream;
import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder; import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
@@ -103,10 +104,12 @@ final class PSDUtil {
final int[] byteCounts, long compressedLength) throws IOException { final int[] byteCounts, long compressedLength) throws IOException {
switch (compression) { switch (compression) {
case PSD.COMPRESSION_NONE: case PSD.COMPRESSION_NONE:
return new SubImageInputStream(stream, stream.length()); long streamLength = stream.length();
return new SubImageInputStream(stream, streamLength < 0 ? Long.MAX_VALUE : streamLength);
case PSD.COMPRESSION_RLE: case PSD.COMPRESSION_RLE:
return new DirectImageInputStream(new SequenceInputStream(new LazyPackBitsStreamEnumeration(byteCounts, stream))); int rowLength = (columns * bitsPerSample + 7) / 8;
return new DirectImageInputStream(new SequenceInputStream(new LazyPackBitsStreamEnumeration(stream, byteCounts, rowLength)));
case PSD.COMPRESSION_ZIP: case PSD.COMPRESSION_ZIP:
return new DirectImageInputStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength))); return new DirectImageInputStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength)));
@@ -123,11 +126,13 @@ final class PSDUtil {
private static class LazyPackBitsStreamEnumeration implements Enumeration<InputStream> { private static class LazyPackBitsStreamEnumeration implements Enumeration<InputStream> {
private final ImageInputStream stream; private final ImageInputStream stream;
private final int[] byteCounts; private final int[] byteCounts;
private final int rowLength;
private int index; private int index;
public LazyPackBitsStreamEnumeration(int[] byteCounts, ImageInputStream stream) { public LazyPackBitsStreamEnumeration(ImageInputStream stream, int[] byteCounts, int rowLength) {
this.byteCounts = byteCounts;
this.stream = stream; this.stream = stream;
this.byteCounts = byteCounts;
this.rowLength = rowLength;
} }
@Override @Override
@@ -137,7 +142,7 @@ final class PSDUtil {
@Override @Override
public InputStream nextElement() { public InputStream nextElement() {
return new DecoderStream(createStreamAdapter(stream, byteCounts[index++]), new PackBitsDecoder()); return new SubStream(new DecoderStream(createStreamAdapter(stream, byteCounts[index++]), new PackBitsDecoder(), rowLength), rowLength);
} }
} }
} }
@@ -31,10 +31,12 @@
package com.twelvemonkeys.imageio.plugins.psd; package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import org.junit.Test; import org.junit.Test;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream; import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
@@ -65,6 +67,29 @@ public class PSDUtilDecompressorStreamTest {
} }
} }
@Test
public void testUncompressedUnknownLength() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 0x7f's
byte[] data = new byte[] {
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f
};
try (ImageInputStream input = createDecompressorStream(new DirectImageInputStream(new ByteArrayInputStream(data)), PSD.COMPRESSION_NONE, 3, 8, null, 9)) {
byte[] row = new byte[3];
for (int y = 0; y < 3; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 0x7f, b);
}
}
assertEquals(-1, input.read());
}
}
@Test @Test
public void testPackBits() throws IOException { public void testPackBits() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 42's // Data represents 3 x 3 raster with 8 bit samples, all 42's
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-reference</artifactId> <artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name> <name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-sgi</artifactId> <artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name> <name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-tga</artifactId> <artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name> <name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-thumbsdb</artifactId> <artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name> <name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-tiff-jai-interop</artifactId> <artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name> <name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-tiff-jdk-interop</artifactId> <artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name> <name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name> <name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -0,0 +1,198 @@
/*
* Copyright (c) 2023, 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.tiff;
import com.twelvemonkeys.lang.Validate;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
/**
* A decoder for data converted using "floating point horizontal differencing predictor".
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
*/
final class HorizontalDeDifferencingFloatingPointStream extends InputStream {
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
// Adapted from the C code in Adobe Photoshop® TIFF Technical Note 3
private final int columns;
// NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1
private final int samplesPerPixel;
private final int bytesPerSample;
private final ReadableByteChannel channel;
private final ByteBuffer buffer;
private final byte[] fpRow;
public HorizontalDeDifferencingFloatingPointStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
this.samplesPerPixel = samplesPerPixel;
Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s");
bytesPerSample = (samplesPerPixel * bitsPerSample + 7) / 8;
channel = Channels.newChannel(Validate.notNull(stream, "stream"));
buffer = ByteBuffer.allocate(columns * bytesPerSample)
.order(byteOrder);
fpRow = buffer.array().clone();
buffer.flip();
}
private static boolean isValidBPS(final int bitsPerSample) {
switch (bitsPerSample) {
case 16:
case 24:
case 32:
case 64:
return true;
default:
return false;
}
}
@SuppressWarnings("StatementWithEmptyBody")
private boolean fetch() throws IOException {
buffer.clear();
// This *SHOULD* read an entire row of pixels (or nothing at all) into the buffer,
// otherwise we will throw EOFException below
while (channel.read(buffer) > 0);
if (buffer.position() > 0) {
if (buffer.hasRemaining()) {
throw new EOFException("Unexpected end of stream");
}
decodeRow();
buffer.flip();
return true;
}
else {
buffer.position(buffer.capacity());
return false;
}
}
private void decodeDeltaBytes(byte[] bytes, int columns, int samplesPerPixel) {
for (int column = 1; column < columns; column++) {
for (int channel = 0; channel < samplesPerPixel; channel++) {
bytes[column * samplesPerPixel + channel] = (byte) (bytes[column * samplesPerPixel + channel] + bytes[(column - 1) * samplesPerPixel + channel]);
}
}
}
private void decodeFloatingPointDelta(byte[] input, byte[] output, int columns, int channels, int bytesPerSample, final ByteOrder order) {
// undo byte difference on input
decodeDeltaBytes(input, columns * bytesPerSample, channels);
// reorder the bytes into the floating point buffer
int rowIncrement = columns * channels;
for (int column = 0; column < rowIncrement; column++) {
for (int b = 0; b < bytesPerSample; b++) {
output[bytesPerSample * column + b] = order == ByteOrder.BIG_ENDIAN
? input[b * rowIncrement + column]
: input[(bytesPerSample - b - 1) * rowIncrement + column];
}
}
}
private void decodeRow() {
// Un-apply horizontal predictor
decodeFloatingPointDelta(buffer.array(), fpRow, columns, samplesPerPixel, bytesPerSample, buffer.order());
System.arraycopy(fpRow, 0, buffer.array(), 0, fpRow.length);
}
@Override
public int read() throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
return buffer.get() & 0xff;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
int read = Math.min(buffer.remaining(), len);
buffer.get(b, off, read);
return read;
}
@Override
public long skip(long n) throws IOException {
if (n < 0) {
return 0;
}
if (!buffer.hasRemaining()) {
if (!fetch()) {
return 0; // SIC
}
}
int skipped = (int) Math.min(buffer.remaining(), n);
buffer.position(buffer.position() + skipped);
return skipped;
}
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
if (channel.isOpen()) {
channel.close();
}
}
}
}
@@ -68,7 +68,8 @@ final class HorizontalDeDifferencingStream extends InputStream {
channel = Channels.newChannel(Validate.notNull(stream, "stream")); channel = Channels.newChannel(Validate.notNull(stream, "stream"));
buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder); buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8)
.order(byteOrder);
buffer.flip(); buffer.flip();
} }
@@ -93,7 +93,6 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter; import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
@@ -911,7 +910,7 @@ public final class TIFFImageReader extends ImageReaderBase {
Set<ImageTypeSpecifier> specs = new LinkedHashSet<>(5); Set<ImageTypeSpecifier> specs = new LinkedHashSet<>(5);
// TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc // TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc
if (rawType.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_RGB) { if (rawType.getColorModel().getColorSpace().isCS_sRGB()) {
if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) { if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) {
specs.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); specs.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
// specs.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)); // specs.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
@@ -2429,7 +2428,7 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFExtension.COMPRESSION_DEFLATE: case TIFFExtension.COMPRESSION_DEFLATE:
// TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical // TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical
case TIFFCustom.COMPRESSION_PIXTIFF_ZIP: case TIFFCustom.COMPRESSION_PIXTIFF_ZIP:
return new InflaterInputStream(stream, new Inflater(), 1024); return new InflaterInputStream(stream);
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE: case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
case TIFFExtension.COMPRESSION_CCITT_T4: case TIFFExtension.COMPRESSION_CCITT_T4:
case TIFFExtension.COMPRESSION_CCITT_T6: case TIFFExtension.COMPRESSION_CCITT_T6:
@@ -2484,7 +2483,7 @@ public final class TIFFImageReader extends ImageReaderBase {
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING: case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder); return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder);
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT: case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
throw new IIOException("Unsupported TIFF Predictor value: " + predictor); return new HorizontalDeDifferencingFloatingPointStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder);
default: default:
throw new IIOException("Unknown TIFF Predictor value: " + predictor); throw new IIOException("Unknown TIFF Predictor value: " + predictor);
} }
@@ -531,6 +531,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
final int sampleSize = renderedImage.getSampleModel().getSampleSize(0); final int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
final int numBands = renderedImage.getSampleModel().getNumBands(); final int numBands = renderedImage.getSampleModel().getNumBands();
// TODO: This buffer should probably have order matching that of imageOutput, but only if writing "actual" 16 or 32 bit samples, not "packed" samples
final byte[] buffer = new byte[(tileWidth * numBands * sampleSize + 7) / 8];
int bufferPos = 0;
for (int yTile = minTileY; yTile < maxYTiles; yTile++) { for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
for (int xTile = minTileX; xTile < maxXTiles; xTile++) { for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
final Raster tile = renderedImage.getTile(xTile, yTile); final Raster tile = renderedImage.getTile(xTile, yTile);
@@ -568,16 +572,17 @@ public final class TIFFImageWriter extends ImageWriterBase {
for (int s = 0; s < numBands; s++) { for (int s = 0; s < numBands; s++) {
if (sampleSize == 8 || shift == 0) { if (sampleSize == 8 || shift == 0) {
// Normal interleaved/planar case // Normal interleaved/planar case
stream.writeByte((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff)); buffer[bufferPos++] = ((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
} }
else { else {
// "Packed" case // "Packed" case
stream.writeByte((byte) (rowBuffer.getElem(b, x - offsetX + bandOffsets[s]) & 0xff)); buffer[bufferPos++] = ((byte) (rowBuffer.getElem(b, x - offsetX + bandOffsets[s]) & 0xff));
} }
} }
} }
flushBuffer(buffer, bufferPos, stream);
bufferPos = 0;
flushStream(stream); flushStream(stream);
} }
} }
@@ -597,9 +602,13 @@ public final class TIFFImageWriter extends ImageWriterBase {
for (int x = offsetX; x < tileWidth + offsetX; x++) { for (int x = offsetX; x < tileWidth + offsetX; x++) {
int xOff = yOff + x; int xOff = yOff + x;
stream.writeShort((short) (dataBuffer.getElem(b, xOff) & 0xffff)); int elem = dataBuffer.getElem(b, xOff);
buffer[bufferPos++] = (byte) ((elem >>> 8) & 0xff);
buffer[bufferPos++] = (byte) (elem & 0xff);
} }
flushBuffer(buffer, bufferPos, stream);
bufferPos = 0;
flushStream(stream); flushStream(stream);
} }
} }
@@ -642,9 +651,15 @@ public final class TIFFImageWriter extends ImageWriterBase {
for (int x = offsetX; x < tileWidth + offsetX; x++) { for (int x = offsetX; x < tileWidth + offsetX; x++) {
int xOff = yOff + x; int xOff = yOff + x;
stream.writeInt(dataBuffer.getElem(b, xOff)); int elem = dataBuffer.getElem(b, xOff);
buffer[bufferPos++] = (byte) ((elem >>> 24) & 0xff);
buffer[bufferPos++] = (byte) ((elem >>> 16) & 0xff);
buffer[bufferPos++] = (byte) ((elem >>> 8) & 0xff);
buffer[bufferPos++] = (byte) (elem & 0xff);
} }
flushBuffer(buffer, bufferPos, stream);
bufferPos = 0;
flushStream(stream); flushStream(stream);
} }
} }
@@ -661,10 +676,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
int element = dataBuffer.getElem(b, xOff); int element = dataBuffer.getElem(b, xOff);
for (int s = 0; s < numBands; s++) { for (int s = 0; s < numBands; s++) {
stream.writeByte((byte) ((element >> bitOffsets[s]) & 0xff)); buffer[bufferPos++] = (byte) ((element >> bitOffsets[s]) & 0xff);
} }
} }
flushBuffer(buffer, bufferPos, stream);
bufferPos = 0;
flushStream(stream); flushStream(stream);
} }
} }
@@ -690,14 +707,18 @@ public final class TIFFImageWriter extends ImageWriterBase {
processImageComplete(); processImageComplete();
} }
private void flushStream(DataOutput stream) throws IOException { private static void flushStream(DataOutput stream) throws IOException {
// Need to flush/start new compression for each row, for proper LZW/PackBits/Deflate/ZLib compression // Need to flush/start new compression for each row, for proper LZW/PackBits/Deflate/ZLib
if (stream instanceof DataOutputStream) { if (stream instanceof DataOutputStream) {
DataOutputStream dataOutputStream = (DataOutputStream) stream; DataOutputStream dataOutputStream = (DataOutputStream) stream;
dataOutputStream.flush(); dataOutputStream.flush();
} }
} }
private static void flushBuffer(final byte[] buffer, final int bufferPos, final DataOutput stream) throws IOException {
stream.write(buffer, 0, bufferPos);
}
// Metadata // Metadata
@Override @Override
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-webp</artifactId> <artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name> <name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull;
/** /**
* LSBBitReader * LSBBitReader
* *
@@ -49,14 +51,13 @@ public final class LSBBitReader {
private long streamPosition = -1; private long streamPosition = -1;
/** /**
* Pre buffers up to the next 8 Bytes in input. * Pre-buffers up to the next 8 Bytes in input.
* Contains valid bits in bits 63 to {@code bitOffset} (inclusive). * Contains valid bits in bits 63 to {@code bitOffset} (inclusive).
* Should always be refilled to have at least 56 valid bits (if possible)
*/ */
private long buffer; private long buffer;
public LSBBitReader(ImageInputStream imageInput) { public LSBBitReader(ImageInputStream imageInput) {
this.imageInput = imageInput; this.imageInput = notNull(imageInput);
} }
/** /**
@@ -89,20 +90,16 @@ public final class LSBBitReader {
if (bits > 56) { if (bits > 56) {
throw new IllegalArgumentException("Tried peeking over 56"); throw new IllegalArgumentException("Tried peeking over 56");
} }
return readBits(bits, true); return readBits(bits, true);
} }
//Driver
private long readBits(int bits, boolean peek) throws IOException { private long readBits(int bits, boolean peek) throws IOException {
if (bits <= 56) { if (bits <= 56) {
// Could eliminate if we never read from the underlying InputStream
/* // outside this class after the object is created
Could eliminate if we never read from the underlying InputStream outside this class after the object is if (streamPosition != imageInput.getStreamPosition()) {
created // Need to reset buffer as stream was read in the meantime
*/
long inputStreamPosition = imageInput.getStreamPosition();
if (streamPosition != inputStreamPosition) {
//Need to reset buffer as stream was read in the meantime
resetBuffer(); resetBuffer();
} }
@@ -110,45 +107,46 @@ public final class LSBBitReader {
if (!peek) { if (!peek) {
bitOffset += bits; bitOffset += bits;
refillBuffer();
if (bitOffset >= 8) {
refillBuffer();
}
} }
return ret; return ret;
} }
else { else {
//FIXME Untested // Peek always false in this case
long lower = readBits(56); long lower = readBits(56);
return (readBits(bits - 56) << (56)) | lower; return (readBits(bits - 56) << (56)) | lower;
} }
} }
private void refillBuffer() throws IOException { private void refillBuffer() throws IOException {
// Set to stream position consistent with buffered bytes
imageInput.readLong(); // Don't replace with skipBytes(8) or seek(+8), this will invalidate stream buffer... TODO: Fix streams to cope...
//Set to stream position consistent with buffered bytes
imageInput.seek(streamPosition + 8);
for (; bitOffset >= 8; bitOffset -= 8) { for (; bitOffset >= 8; bitOffset -= 8) {
try { try {
byte b = imageInput.readByte(); byte b = imageInput.readByte();
buffer >>>= 8; buffer = ((long) b << 56) | buffer >>> 8;
streamPosition++; streamPosition++;
buffer |= ((long) b << 56);
} }
catch (EOFException e) { catch (EOFException e) {
imageInput.seek(streamPosition); imageInput.seek(streamPosition);
return; return;
} }
} }
/*
Reset to guarantee stream position consistent with returned bytes // Reset to guarantee stream position consistent with returned bytes
Would not need to do this seeking around when the underlying ImageInputStream is never read from outside // Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
this class after the object is created. // this class after the object is created.
*/
imageInput.seek(streamPosition); imageInput.seek(streamPosition);
} }
private void resetBuffer() throws IOException { private void resetBuffer() throws IOException {
long inputStreamPosition = imageInput.getStreamPosition(); long inputStreamPosition = imageInput.getStreamPosition();
try { try {
buffer = imageInput.readLong(); buffer = imageInput.readLong();
bitOffset = 0; bitOffset = 0;
@@ -156,7 +154,7 @@ public final class LSBBitReader {
imageInput.seek(inputStreamPosition); imageInput.seek(inputStreamPosition);
} }
catch (EOFException e) { catch (EOFException e) {
//Retry byte by byte // Retry byte by byte
streamPosition = inputStreamPosition - 8; streamPosition = inputStreamPosition - 8;
bitOffset = 64; bitOffset = 64;
refillBuffer(); refillBuffer();
@@ -164,7 +162,7 @@ public final class LSBBitReader {
} }
//Left for backwards compatibility / Compatibility with ImageInputStream interface // Left for backwards compatibility / Compatibility with ImageInputStream interface
public int readBit() throws IOException { public int readBit() throws IOException {
return (int) readBits(1); return (int) readBits(1);
} }
@@ -61,6 +61,7 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static com.twelvemonkeys.imageio.plugins.webp.lossless.VP8LDecoder.copyIntoRasterWithParams;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
@@ -96,7 +97,9 @@ final class WebPImageReader extends ImageReaderBase {
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata); super.setInput(input, seekForwardOnly, ignoreMetadata);
lsbBitReader = new LSBBitReader(imageInput); if (imageInput != null) {
lsbBitReader = new LSBBitReader(imageInput);
}
} }
private void readHeader(int imageIndex) throws IOException { private void readHeader(int imageIndex) throws IOException {
@@ -130,8 +133,8 @@ final class WebPImageReader extends ImageReaderBase {
if (DEBUG) { if (DEBUG) {
System.out.printf("chunk: '%s'\n", fourCC(nextChunk)); System.out.printf("chunk: '%s'\n", fourCC(nextChunk));
System.out.println("chunkLength: " + chunkLength);
System.out.println("chunkStart: " + chunkStart); System.out.println("chunkStart: " + chunkStart);
System.out.println("chunkLength: " + chunkLength);
} }
switch (nextChunk) { switch (nextChunk) {
@@ -272,19 +275,19 @@ final class WebPImageReader extends ImageReaderBase {
} }
// RsV|I|L|E|X|A|R // RsV|I|L|E|X|A|R
int reserved = (int) imageInput.readBits(2); int reserved = lsbBitReader.readBit();
if (reserved != 0) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
} }
header.containsICCP = imageInput.readBit() == 1; header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
header.containsALPH = imageInput.readBit() == 1; // L -> aLpha header.containsXMP_ = lsbBitReader.readBit() == 1;
header.containsEXIF = imageInput.readBit() == 1; header.containsEXIF = lsbBitReader.readBit() == 1;
header.containsXMP_ = imageInput.readBit() == 1; header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
header.containsANIM = imageInput.readBit() == 1; // A -> Anim header.containsICCP = lsbBitReader.readBit() == 1;
reserved = (int) imageInput.readBits(25); // 1 + 24 bits reserved reserved = (int) lsbBitReader.readBits(26); // 2 + 24 bits reserved
if (reserved != 0) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
@@ -462,6 +465,8 @@ final class WebPImageReader extends ImageReaderBase {
} }
private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width, final int height) throws IOException { private void readVP8Extended(BufferedImage destination, ImageReadParam param, long streamEnd, final int width, final int height) throws IOException {
boolean seenALPH = false;
while (imageInput.getStreamPosition() < streamEnd) { while (imageInput.getStreamPosition() < streamEnd) {
int nextChunk = imageInput.readInt(); int nextChunk = imageInput.readInt();
long chunkLength = imageInput.readUnsignedInt(); long chunkLength = imageInput.readUnsignedInt();
@@ -469,18 +474,25 @@ final class WebPImageReader extends ImageReaderBase {
if (DEBUG) { if (DEBUG) {
System.out.printf("chunk: '%s'\n", fourCC(nextChunk)); System.out.printf("chunk: '%s'\n", fourCC(nextChunk));
System.out.println("chunkLength: " + chunkLength);
System.out.println("chunkStart: " + chunkStart); System.out.println("chunkStart: " + chunkStart);
System.out.println("chunkLength: " + chunkLength);
} }
switch (nextChunk) { switch (nextChunk) {
case WebP.CHUNK_ALPH: case WebP.CHUNK_ALPH:
seenALPH = true;
readAlpha(destination, param, width, height); readAlpha(destination, param, width, height);
break; break;
case WebP.CHUNK_VP8_: case WebP.CHUNK_VP8_:
readVP8(RasterUtils.asByteRaster(destination.getRaster()) readVP8(RasterUtils.asByteRaster(destination.getRaster())
.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[] {0, 1, 2}), param); .createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), 0, 0, new int[] {0, 1, 2}), param);
if (header.containsALPH && !seenALPH) {
// May happen for animation frames, if some frames are fully opaque
opaqueAlpha(destination.getAlphaRaster());
}
break; break;
case WebP.CHUNK_VP8L: case WebP.CHUNK_VP8L:
@@ -509,16 +521,16 @@ final class WebPImageReader extends ImageReaderBase {
} }
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException { private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
int reserved = (int) imageInput.readBits(2); int compression = (int) lsbBitReader.readBits(2);
int filtering = (int) lsbBitReader.readBits(2);
int preProcessing = (int) lsbBitReader.readBits(2);
int reserved = (int) lsbBitReader.readBits(2);
if (reserved != 0) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved)); processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
} }
int preProcessing = (int) imageInput.readBits(2);
int filtering = (int) imageInput.readBits(2);
int compression = (int) imageInput.readBits(2);
if (DEBUG) { if (DEBUG) {
System.out.println("preProcessing: " + preProcessing); System.out.println("preProcessing: " + preProcessing);
System.out.println("filtering: " + filtering); System.out.println("filtering: " + filtering);
@@ -534,21 +546,26 @@ final class WebPImageReader extends ImageReaderBase {
// Simulate header // Simulate header
imageInput.seek(imageInput.getStreamPosition() - 5); imageInput.seek(imageInput.getStreamPosition() - 5);
WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, destination.getWidth(), destination.getHeight(), 4, null); // Temp alpha raster must have same dimensions as the source, because of filtering.
readVP8Lossless(tempRaster, param, width, height); WritableRaster tempRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, 4, null);
readVP8Lossless(tempRaster, null, width, height);
// Copy from green (band 1) in temp to alpha in destination // Copy from green (band 1) in temp to alpha in destination
alphaRaster.setRect(tempRaster.createChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[] {1})); WritableRaster alphaChannel = tempRaster.createWritableChild(0, 0, tempRaster.getWidth(), tempRaster.getHeight(), 0, 0, new int[]{1});
alphaFilter(alphaChannel, filtering);
copyIntoRasterWithParams(alphaChannel, alphaRaster, param);
break; break;
default: default:
processWarningOccurred("Unknown WebP alpha compression: " + compression); processWarningOccurred("Unknown WebP alpha compression: " + compression);
opaqueAlpha(alphaRaster); opaqueAlpha(alphaRaster);
break; break;
} }
}
private void alphaFilter(WritableRaster alphaRaster, int filtering) {
if (filtering != AlphaFiltering.NONE) { if (filtering != AlphaFiltering.NONE) {
for (int y = 0; y < destination.getHeight(); y++) { for (int y = 0; y < alphaRaster.getHeight(); y++) {
for (int x = 0; x < destination.getWidth(); x++) { for (int x = 0; x < alphaRaster.getWidth(); x++) {
int predictorAlpha = getPredictorAlpha(alphaRaster, filtering, y, x); int predictorAlpha = getPredictorAlpha(alphaRaster, filtering, y, x);
alphaRaster.setSample(x, y, 0, alphaRaster.getSample(x, y, 0) + predictorAlpha % 256); alphaRaster.setSample(x, y, 0, alphaRaster.getSample(x, y, 0) + predictorAlpha % 256);
} }
@@ -675,8 +692,8 @@ final class WebPImageReader extends ImageReaderBase {
long chunkStart = imageInput.getStreamPosition(); long chunkStart = imageInput.getStreamPosition();
// System.err.printf("chunk: '%s'\n", fourCC(nextChunk)); // System.err.printf("chunk: '%s'\n", fourCC(nextChunk));
// System.err.println("chunkLength: " + chunkLength);
// System.err.println("chunkStart: " + chunkStart); // System.err.println("chunkStart: " + chunkStart);
// System.err.println("chunkLength: " + chunkLength);
switch (nextChunk) { switch (nextChunk) {
case WebP.CHUNK_EXIF: case WebP.CHUNK_EXIF:
@@ -48,16 +48,14 @@ final class ColorIndexingTransform implements Transform {
@Override @Override
public void applyInverse(WritableRaster raster) { public void applyInverse(WritableRaster raster) {
int width = raster.getWidth(); int width = raster.getWidth();
int height = raster.getHeight(); int height = raster.getHeight();
byte[] rgba = new byte[4]; byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
//Reversed so no used elements are overridden (in case of packing) // Reversed so no used elements are overridden (in case of packing)
for (int x = width - 1; x >= 0; x--) { for (int x = width - 1; x >= 0; x--) {
int componentSize = 8 >> bits; int componentSize = 8 >> bits;
int packed = 1 << bits; int packed = 1 << bits;
int xC = x / packed; int xC = x / packed;
@@ -67,10 +65,14 @@ final class ColorIndexingTransform implements Transform {
int index = sample >> componentOffset & ((1 << componentSize) - 1); int index = sample >> componentOffset & ((1 << componentSize) - 1);
//Arraycopy for 4 elements might not be beneficial // Arraycopy for 4 elements might not be beneficial
System.arraycopy(colorTable, index * 4, rgba, 0, 4); System.arraycopy(colorTable, index * 4, rgba, 0, 4);
raster.setDataElements(x, y, rgba); // rgba[0] = colorTable[index * 4];
// rgba[1] = colorTable[index * 4 + 1];
// rgba[2] = colorTable[index * 4 + 2];
// rgba[3] = colorTable[index * 4 + 3];
raster.setDataElements(x, y, rgba);
} }
} }
} }
@@ -37,11 +37,11 @@ import java.awt.image.*;
* @author Simon Kammermeier * @author Simon Kammermeier
*/ */
final class HuffmanInfo { final class HuffmanInfo {
public Raster huffmanMetaCodes; //Raster allows intuitive lookup by x and y public final Raster huffmanMetaCodes; // Raster allows intuitive lookup by x and y
public int metaCodeBits; public final int metaCodeBits;
public HuffmanCodeGroup[] huffmanGroups; public final HuffmanCodeGroup[] huffmanGroups;
public HuffmanInfo(Raster huffmanMetaCodes, int metaCodeBits, HuffmanCodeGroup[] huffmanGroups) { public HuffmanInfo(Raster huffmanMetaCodes, int metaCodeBits, HuffmanCodeGroup[] huffmanGroups) {
this.huffmanMetaCodes = huffmanMetaCodes; this.huffmanMetaCodes = huffmanMetaCodes;
@@ -83,7 +83,6 @@ final class HuffmanTable {
* @throws IOException when reading produces an exception * @throws IOException when reading produces an exception
*/ */
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException { public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
boolean simpleLengthCode = lsbBitReader.readBit() == 1; boolean simpleLengthCode = lsbBitReader.readBit() == 1;
if (simpleLengthCode) { if (simpleLengthCode) {
@@ -104,11 +103,9 @@ final class HuffmanTable {
} }
} }
else { else {
/* // code lengths also huffman coded
code lengths also huffman coded // first read the "first stage" code lengths
first read the "first stage" code lengths // In the following this is called the L-Code (for length code)
In the following this is called the L-Code (for length code)
*/
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4); int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
short[] lCodeLengths = new short[L_CODE_ORDER.length]; short[] lCodeLengths = new short[L_CODE_ORDER.length];
int numPosCodeLens = 0; int numPosCodeLens = 0;
@@ -116,16 +113,15 @@ final class HuffmanTable {
for (int i = 0; i < numLCodeLengths; i++) { for (int i = 0; i < numLCodeLengths; i++) {
short len = (short) lsbBitReader.readBits(3); short len = (short) lsbBitReader.readBits(3);
lCodeLengths[L_CODE_ORDER[i]] = len; lCodeLengths[L_CODE_ORDER[i]] = len;
if (len > 0) { if (len > 0) {
numPosCodeLens++; numPosCodeLens++;
} }
} }
//Use L-Code to read the actual code lengths // Use L-Code to read the actual code lengths
short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens); short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
buildFromLengths(codeLengths); buildFromLengths(codeLengths);
} }
} }
@@ -142,25 +138,21 @@ final class HuffmanTable {
buildFromLengths(codeLengths, numPosCodeLens); buildFromLengths(codeLengths, numPosCodeLens);
} }
// Helper methods to allow reusing in different constructors
/*
Helper methods to allow reusing in different constructors
*/
private void buildFromLengths(short[] codeLengths) { private void buildFromLengths(short[] codeLengths) {
int numPosCodeLens = 0; int numPosCodeLens = 0;
for (short codeLength : codeLengths) { for (short codeLength : codeLengths) {
if (codeLength != 0) { if (codeLength != 0) {
numPosCodeLens++; numPosCodeLens++;
} }
} }
buildFromLengths(codeLengths, numPosCodeLens); buildFromLengths(codeLengths, numPosCodeLens);
} }
private void buildFromLengths(short[] codeLengths, int numPosCodeLens) { private void buildFromLengths(short[] codeLengths, int numPosCodeLens) {
// Pack code length and corresponding symbols as described above
//Pack code length and corresponding symbols as described above
int[] lengthsAndSymbols = new int[numPosCodeLens]; int[] lengthsAndSymbols = new int[numPosCodeLens];
int index = 0; int index = 0;
@@ -170,28 +162,25 @@ final class HuffmanTable {
} }
} }
//Special case: Only 1 code value // Special case: Only 1 code value
if (numPosCodeLens == 1) { if (numPosCodeLens == 1) {
//Length is 0 so mask to clear length bits // Length is 0 so mask to clear length bits
Arrays.fill(level1, lengthsAndSymbols[0] & 0xffff); Arrays.fill(level1, lengthsAndSymbols[0] & 0xffff);
} }
//Due to the layout of the elements this effectively first sorts by length and then symbol. // Due to the layout of the elements this effectively first sorts by length and then symbol.
Arrays.sort(lengthsAndSymbols); Arrays.sort(lengthsAndSymbols);
/* // The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
The next code, in the bit order it would appear on the input stream, i.e. it is reversed. // Only the lowest bits (corresponding to the bit length of the code) are considered.
Only the lowest bits (corresponding to the bit length of the code) are considered. // Example: code 0..010 (length 2) would appear as 0..001.
Example: code 0..010 (length 2) would appear as 0..001.
*/
int code = 0; int code = 0;
//Used for level2 lookup // Used for level2 lookup
int rootEntry = -1; int rootEntry = -1;
int[] currentTable = null; int[] currentTable = null;
for (int i = 0; i < lengthsAndSymbols.length; i++) { for (int i = 0; i < lengthsAndSymbols.length; i++) {
int lengthAndSymbol = lengthsAndSymbols[i]; int lengthAndSymbol = lengthsAndSymbols[i];
int length = lengthAndSymbol >>> 16; int length = lengthAndSymbol >>> 16;
@@ -202,16 +191,15 @@ final class HuffmanTable {
} }
} }
else { else {
//Existing level2 table not fitting // Existing level2 table not fitting
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) { if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
/* // Figure out needed table size.
Figure out needed table size. // Start at current symbol and length.
Start at current symbol and length. // Every symbol uses 1 slot at the current bit length.
Every symbol uses 1 slot at the current bit length. // Going up 1 bit in length multiplies the slots by 2.
Going up 1 bit in length multiplies the slots by 2. // No more open slots indicate the table size to be big enough.
No more open slots indicate the table size to be big enough.
*/
int maxLength = length; int maxLength = length;
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS); for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
j < lengthsAndSymbols.length && openSlots > 0; j < lengthsAndSymbols.length && openSlots > 0;
j++, openSlots--) { j++, openSlots--) {
@@ -230,11 +218,11 @@ final class HuffmanTable {
rootEntry = code & ((1 << LEVEL1_BITS) - 1); rootEntry = code & ((1 << LEVEL1_BITS) - 1);
level2.add(currentTable); level2.add(currentTable);
//Set root table indirection // Set root table indirection
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1); level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1);
} }
//Add to existing (or newly generated) 2nd level table // Add to existing (or newly generated) 2nd level table
for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) { for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff); currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
} }
@@ -256,12 +244,12 @@ final class HuffmanTable {
private int nextCode(int code, int length) { private int nextCode(int code, int length) {
int a = (~code) & ((1 << length) - 1); int a = (~code) & ((1 << length) - 1);
//This will result in the highest 0-bit in the lower length bits of code set (by construction of a) // This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
//I.e. the lowest 0-bit in the value code represents // I.e. the lowest 0-bit in the value code represents
int step = Integer.highestOneBit(a); int step = Integer.highestOneBit(a);
//In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit // In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
//This corresponds to adding 1 to the value // This corresponds to adding 1 to the value
return (code & (step - 1)) | step; return (code & (step - 1)) | step;
} }
@@ -270,7 +258,7 @@ final class HuffmanTable {
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens); HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens);
//Not sure where this comes from. Just adapted from the libwebp implementation // Not sure where this comes from. Just adapted from the libwebp implementation
int codedSymbols; int codedSymbols;
if (lsbBitReader.readBit() == 1) { if (lsbBitReader.readBit() == 1) {
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3)); int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
@@ -282,13 +270,13 @@ final class HuffmanTable {
short[] codeLengths = new short[alphabetSize]; short[] codeLengths = new short[alphabetSize];
//Default code for repeating // Default code for repeating
short prevLength = 8; short prevLength = 8;
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) { for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
short len = huffmanTable.readSymbol(lsbBitReader); short len = huffmanTable.readSymbol(lsbBitReader);
if (len < 16) { //Literal length if (len < 16) { // Literal length
codeLengths[i] = len; codeLengths[i] = len;
if (len != 0) { if (len != 0) {
prevLength = len; prevLength = len;
@@ -300,16 +288,16 @@ final class HuffmanTable {
int repeatOffset; int repeatOffset;
switch (len) { switch (len) {
case 16: //Repeat previous case 16: // Repeat previous
repeatSymbol = prevLength; repeatSymbol = prevLength;
extraBits = 2; extraBits = 2;
repeatOffset = 3; repeatOffset = 3;
break; break;
case 17: //Repeat 0 short case 17: // Repeat 0 short
extraBits = 3; extraBits = 3;
repeatOffset = 3; repeatOffset = 3;
break; break;
case 18: //Repeat 0 long case 18: // Repeat 0 long
extraBits = 7; extraBits = 7;
repeatOffset = 11; repeatOffset = 11;
break; break;
@@ -319,7 +307,6 @@ final class HuffmanTable {
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset); int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
if (i + repeatCount > alphabetSize) { if (i + repeatCount > alphabetSize) {
throw new IIOException( throw new IIOException(
String.format( String.format(
@@ -330,11 +317,9 @@ final class HuffmanTable {
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol); Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
i += repeatCount - 1; i += repeatCount - 1;
} }
} }
return codeLengths; return codeLengths;
} }
@@ -346,21 +331,20 @@ final class HuffmanTable {
* @throws IOException when the reader throws one reading a symbol * @throws IOException when the reader throws one reading a symbol
*/ */
public short readSymbol(LSBBitReader lsbBitReader) throws IOException { public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS); int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
int lengthAndSymbol = level1[index]; int lengthAndSymbol = level1[index];
int length = lengthAndSymbol >>> 16; int length = lengthAndSymbol >>> 16;
if (length > LEVEL1_BITS) { if (length > LEVEL1_BITS) {
//Lvl2 lookup // Lvl2 lookup
lsbBitReader.readBits(LEVEL1_BITS); //Consume bits of first level lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); //Peek remaining required bits int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index]; lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
length = lengthAndSymbol >>> 16; length = lengthAndSymbol >>> 16;
} }
lsbBitReader.readBits(length); //Consume bits lsbBitReader.readBits(length); // Consume bits
return (short) (lengthAndSymbol & 0xffff); return (short) (lengthAndSymbol & 0xffff);
} }
@@ -51,25 +51,23 @@ final class PredictorTransform implements Transform {
@Override @Override
public void applyInverse(WritableRaster raster) { public void applyInverse(WritableRaster raster) {
int width = raster.getWidth(); int width = raster.getWidth();
int height = raster.getHeight(); int height = raster.getHeight();
byte[] rgba = new byte[4]; byte[] rgba = new byte[4];
//Handle top and left border separately // Handle top and left border separately
//(0,0) Black (0x000000ff) predict // (0,0) Black (0x000000ff) predict
raster.getDataElements(0, 0, rgba); raster.getDataElements(0, 0, rgba);
rgba[3] += 0xff; rgba[3] += 0xff;
raster.setDataElements(0, 0, rgba); raster.setDataElements(0, 0, rgba);
byte[] predictor = new byte[4]; byte[] predictor = new byte[4];
byte[] predictor2 = new byte[4]; byte[] predictor2 = new byte[4];
byte[] predictor3 = new byte[4]; byte[] predictor3 = new byte[4];
//(x,0) L predict // (x,0) L predict
for (int x = 1; x < width; x++) { for (int x = 1; x < width; x++) {
raster.getDataElements(x, 0, rgba); raster.getDataElements(x, 0, rgba);
raster.getDataElements(x - 1, 0, predictor); raster.getDataElements(x - 1, 0, predictor);
@@ -78,7 +76,7 @@ final class PredictorTransform implements Transform {
raster.setDataElements(x, 0, rgba); raster.setDataElements(x, 0, rgba);
} }
//(0,y) T predict // (0,y) T predict
for (int y = 1; y < height; y++) { for (int y = 1; y < height; y++) {
raster.getDataElements(0, y, rgba); raster.getDataElements(0, y, rgba);
raster.getDataElements(0, y - 1, predictor); raster.getDataElements(0, y - 1, predictor);
@@ -89,16 +87,14 @@ final class PredictorTransform implements Transform {
for (int y = 1; y < height; y++) { for (int y = 1; y < height; y++) {
for (int x = 1; x < width; x++) { for (int x = 1; x < width; x++) {
int transformType = data.getSample(x >> bits, y >> bits, 1); int transformType = data.getSample(x >> bits, y >> bits, 1);
raster.getDataElements(x, y, rgba); raster.getDataElements(x, y, rgba);
int lX = x - 1; //x for left int lX = x - 1; // x for left
int tY = y - 1; // y for top
int tY = y - 1; //y for top // top right is not (x+1, tY) if last pixel in line instead (0, y)
//top right is not (x+1, tY) if last pixel in line instead (0, y)
int trX = x == width - 1 ? 0 : x + 1; int trX = x == width - 1 ? 0 : x + 1;
int trY = x == width - 1 ? y : tY; int trY = x == width - 1 ? y : tY;
@@ -36,7 +36,7 @@ package com.twelvemonkeys.imageio.plugins.webp.lossless;
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/ */
// Hmm.. Why doesn't SUBTRACT_GREEN follow the convention? // Hmm... Why doesn't SUBTRACT_GREEN follow the convention?
interface TransformType { interface TransformType {
int PREDICTOR_TRANSFORM = 0; int PREDICTOR_TRANSFORM = 0;
int COLOR_TRANSFORM = 1; int COLOR_TRANSFORM = 1;
@@ -117,7 +117,7 @@ public final class VP8LDecoder {
if (topLevel) { if (topLevel) {
Rectangle bounds = new Rectangle(width, height); Rectangle bounds = new Rectangle(width, height);
fullSizeRaster = getRasterForDecoding(raster, param, bounds); fullSizeRaster = createDecodeRaster(raster, param, bounds);
// If multiple indices packed into one pixel xSize is different from raster width // If multiple indices packed into one pixel xSize is different from raster width
decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null); decodeRaster = fullSizeRaster.createWritableChild(0, 0, xSize, height, 0, 0, null);
@@ -134,40 +134,12 @@ public final class VP8LDecoder {
transform.applyInverse(fullSizeRaster); transform.applyInverse(fullSizeRaster);
} }
if (fullSizeRaster != raster && param != null) { if (fullSizeRaster != raster) {
// Copy into destination raster with settings applied copyIntoRasterWithParams(fullSizeRaster, raster, param);
Rectangle sourceRegion = param.getSourceRegion();
int sourceXSubsampling = param.getSourceXSubsampling();
int sourceYSubsampling = param.getSourceYSubsampling();
int subsamplingXOffset = param.getSubsamplingXOffset();
int subsamplingYOffset = param.getSubsamplingYOffset();
Point destinationOffset = param.getDestinationOffset();
if (sourceRegion == null) {
sourceRegion = raster.getBounds();
}
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
// Only apply offset (and limit to requested region)
raster.setRect(destinationOffset.x, destinationOffset.y, fullSizeRaster);
}
else {
// Manual copy, more efficient way might exist
byte[] rgba = new byte[4];
int xEnd = raster.getWidth() + raster.getMinX();
int yEnd = raster.getHeight() + raster.getMinY();
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset; xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset; yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
fullSizeRaster.getDataElements(xSrc, ySrc, rgba);
raster.setDataElements(xDst, yDst, rgba);
}
}
}
} }
} }
private WritableRaster getRasterForDecoding(WritableRaster raster, ImageReadParam param, Rectangle bounds) { private WritableRaster createDecodeRaster(WritableRaster raster, ImageReadParam param, Rectangle bounds) {
// If the ImageReadParam requires only a subregion of the image, and if the whole image does not fit into the // If the ImageReadParam requires only a subregion of the image, and if the whole image does not fit into the
// Raster or subsampling is requested, we need a temporary Raster as we can only decode the whole image at once // Raster or subsampling is requested, we need a temporary Raster as we can only decode the whole image at once
boolean originSet = false; boolean originSet = false;
@@ -182,9 +154,9 @@ public final class VP8LDecoder {
else { else {
bounds.setLocation(param.getDestinationOffset()); bounds.setLocation(param.getDestinationOffset());
originSet = true; originSet = true;
} }
} }
if (!raster.getBounds().contains(bounds)) { if (!raster.getBounds().contains(bounds)) {
// Can't reuse existing // Can't reuse existing
return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height, 4 * bounds.width, return Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, bounds.width, bounds.height, 4 * bounds.width,
@@ -192,9 +164,39 @@ public final class VP8LDecoder {
} }
return originSet ? return originSet ?
// Recenter to (0, 0) // Recenter to (0, 0)
raster.createWritableChild(bounds.x, bounds.y, bounds.width, bounds.height, 0, 0, null) : raster.createWritableChild(bounds.x, bounds.y, bounds.width, bounds.height, 0, 0, null) :
raster; raster;
}
/**
* Copy a source raster into a destination raster with optional settings applied.
*/
public static void copyIntoRasterWithParams(final Raster srcRaster, final WritableRaster dstRaster, final ImageReadParam param) {
Rectangle sourceRegion = param != null && param.getSourceRegion() != null ? param.getSourceRegion() : srcRaster.getBounds();
int sourceXSubsampling = param != null ? param.getSourceXSubsampling() : 1;
int sourceYSubsampling = param != null ? param.getSourceYSubsampling() : 1;
int subsamplingXOffset = param != null ? param.getSubsamplingXOffset() : 0;
int subsamplingYOffset = param != null ? param.getSubsamplingYOffset() : 0;
Point destinationOffset = param != null ? param.getDestinationOffset() : new Point(0, 0) ;
if (sourceXSubsampling == 1 && sourceYSubsampling == 1) {
// Only apply offset (and limit to requested region)
dstRaster.setRect(destinationOffset.x, destinationOffset.y, srcRaster);
}
else {
// Subsampled case
byte[] rgba = new byte[4];
int xEnd = dstRaster.getWidth() + dstRaster.getMinX();
int yEnd = dstRaster.getHeight() + dstRaster.getMinY();
for (int yDst = destinationOffset.y, ySrc = sourceRegion.y + subsamplingYOffset; yDst < yEnd; yDst++, ySrc += sourceYSubsampling) {
for (int xDst = destinationOffset.x, xSrc = sourceRegion.x + subsamplingXOffset; xDst < xEnd; xDst++, xSrc += sourceXSubsampling) {
srcRaster.getDataElements(xSrc, ySrc, rgba);
dstRaster.setDataElements(xDst, yDst, rgba);
}
}
}
} }
private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException { private void decodeImage(WritableRaster raster, HuffmanInfo huffmanInfo, ColorCache colorCache) throws IOException {
@@ -35,11 +35,11 @@ import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
final class BoolDecoder { final class BoolDecoder {
private int bit_count; /* # of bits shifted out of value, at most 7 */ private int bitCount; // # of bits shifted out of value, at most 7
ImageInputStream data; ImageInputStream data;
private long offset; /* pointer to next compressed data byte */ private long offset; // pointer to next compressed data byte
private int range; /* always identical to encoder's range */ private int range; // always identical to encoder's range
private int value; /* contains at least 24 significant bits */ private int value; // contains at least 24 significant bits
BoolDecoder(ImageInputStream frame, long offset) throws IOException { BoolDecoder(ImageInputStream frame, long offset) throws IOException {
this.data = frame; this.data = frame;
@@ -48,15 +48,15 @@ final class BoolDecoder {
} }
private void initBoolDecoder() throws IOException { private void initBoolDecoder() throws IOException {
value = 0; /* value = first 16 input bits */ value = 0; // value = first 16 input bits
data.seek(offset); data.seek(offset);
value = data.readUnsignedByte() << 8; value = data.readUnsignedByte() << 8;
// value = (data[offset]) << 8; // value = (data[offset]) << 8;
offset++; offset++;
range = 255; /* initial range is full */ range = 255; // initial range is full
bit_count = 0; /* have not yet shifted out any bits */ bitCount = 0; // have not yet shifted out any bits
} }
public int readBit() throws IOException { public int readBit() throws IOException {
@@ -66,21 +66,21 @@ final class BoolDecoder {
public int readBool(int probability) throws IOException { public int readBool(int probability) throws IOException {
int bit = 0; int bit = 0;
int split; int split;
int bigsplit; int bigSplit;
int range = this.range; int range = this.range;
int value = this.value; int value = this.value;
split = 1 + (((range - 1) * probability) >> 8); split = 1 + (((range - 1) * probability) >> 8);
bigsplit = (split << 8); bigSplit = (split << 8);
range = split; range = split;
if (value >= bigsplit) { if (value >= bigSplit) {
range = this.range - split; range = this.range - split;
value = value - bigsplit; value = value - bigSplit;
bit = 1; bit = 1;
} }
{ {
int count = this.bit_count; int count = this.bitCount;
int shift = Globals.vp8dxBitreaderNorm[range]; int shift = Globals.vp8dxBitreaderNorm[range];
range <<= shift; range <<= shift;
value <<= shift; value <<= shift;
@@ -94,44 +94,35 @@ final class BoolDecoder {
count += 8; count += 8;
} }
this.bit_count = count; this.bitCount = count;
} }
this.value = value; this.value = value;
this.range = range; this.range = range;
return bit; return bit;
} }
/* /**
* Convenience function reads a "literal", that is, a "num_bits" wide * Convenience method reads a "literal", that is, a "numBits" wide
* unsigned value whose bits come high- to low-order, with each bit encoded * unsigned value whose bits come high- to low-order, with each bit encoded
* at probability 128 (i.e., 1/2). * at probability 128 (i.e., 1/2).
*/ */
public int readLiteral(int num_bits) throws IOException { public int readLiteral(int numBits) throws IOException {
int v = 0; int v = 0;
while (num_bits-- > 0) { while (numBits-- > 0) {
v = (v << 1) + readBool(128); v = (v << 1) + readBool(128);
} }
return v; return v;
} }
// int readTree(int t[], /* tree specification */ int p[] /* corresponding interior node probabilities */) throws IOException { int readTree(int[] t, /* tree specification */ int[] p, /* corresponding interior node probabilities */ int skipBranches) throws IOException {
// int i = 0; /* begin at root */ int i = skipBranches * 2; // begin at root
//
// /* Descend tree until leaf is reached */
// while ((i = t[i + readBool(p[i >> 1])]) > 0) {
// }
// return -i; /* return value is negation of nonpositive index */
//
// }
//
// int readTree(int t[], /* tree specification */ int p[], /* corresponding interior node probabilities */ int skip_branches) throws IOException {
int readTree(int[] t, /* tree specification */ int[] p, /* corresponding interior node probabilities */ int skip_branches) throws IOException {
int i = skip_branches * 2; /* begin at root */
/* Descend tree until leaf is reached */ // Descend tree until leaf is reached
while ((i = t[i + readBool(p[i >> 1])]) > 0) { while ((i = t[i + readBool(p[i >> 1])]) > 0) {
} }
return -i; /* return value is negation of nonpositive index */ return -i; // return value is negation of nonpositive index
} }
public void seek() throws IOException { public void seek() throws IOException {
@@ -0,0 +1,270 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import org.junit.Test;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import static org.junit.Assert.assertEquals;
/**
* LSBBitReaderTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: LSBBitReaderTest.java,v 1.0 16/10/2022 haraldk Exp$
*/
public class LSBBitReaderTest {
@Test
public void testReadBit() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00010010, 0b00100001, 0b00001000, 0b00000100,
/*TODO: Remove these, should not be needed... */ 0, 0, 0, 0
});
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testReadBits() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000,
});
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
assertEquals(8, bitReader.readBits(4));
assertEquals(16, bitReader.readBits(5));
assertEquals(32, bitReader.readBits(6));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testPeekBits() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000
});
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.peekBits(2));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
assertEquals(8, bitReader.peekBits(4));
assertEquals(8, bitReader.readBits(4));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
assertEquals(32, bitReader.peekBits(6));
assertEquals(32, bitReader.readBits(6));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.peekBits(8));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.peekBits(10));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.peekBits(11));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.peekBits(13));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testReadBetweenBits() throws IOException {
ImageInputStream stream = createStream(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000
});
final LSBBitReader bitReader = new LSBBitReader(stream);
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.peekBits(2));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
// We've read 6 bits, but still on the 1st byte
assertEquals(0b00100101, stream.readByte());
// Start reading from the second byte (10 == 2)
assertEquals(2, bitReader.readBits(2));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
// We've now read 7 bits, but still on the second byte
assertEquals(1, stream.getStreamPosition());
assertEquals(0b01000010, stream.readByte());
assertEquals(2, stream.getStreamPosition());
assertEquals(16, bitReader.peekBits(11));
assertEquals(0b00010000, stream.readByte());
assertEquals(3, stream.getStreamPosition());
stream.seek(2);
assertEquals(2, stream.getStreamPosition());
// Start reading from the third byte (10000 == 16)
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.peekBits(8));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.peekBits(10));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.peekBits(11));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.peekBits(13));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
private static LSBBitReader createBitReader(final byte[] data) {
ImageInputStream stream = createStream(data);
return new LSBBitReader(stream);
}
private static ImageInputStream createStream(byte[] data) {
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
return stream;
}
}
@@ -1,11 +1,8 @@
package com.twelvemonkeys.imageio.plugins.webp; package com.twelvemonkeys.imageio.plugins.webp;
import static java.util.Arrays.asList; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import java.awt.*; import org.junit.Test;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam; import javax.imageio.ImageReadParam;
@@ -13,10 +10,13 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.List;
import org.junit.Test; import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
/** /**
* WebPImageReaderTest * WebPImageReaderTest
@@ -58,7 +58,9 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 394), new Dimension(400, 400), new Dimension(400, 400), new Dimension(400, 394),
new Dimension(371, 394), new Dimension(394, 382), new Dimension(400, 388), new Dimension(371, 394), new Dimension(394, 382), new Dimension(400, 388),
new Dimension(394, 383), new Dimension(394, 394), new Dimension(372, 394), new Dimension(394, 383), new Dimension(394, 394), new Dimension(372, 394),
new Dimension(400, 400), new Dimension(320, 382)) new Dimension(400, 400), new Dimension(320, 382)),
// Alpha transparency and Alpha filtering
new TestData(getClassLoaderResource("/webp/alpha_filter.webp"), new Dimension(1600, 1600))
); );
} }
@@ -141,4 +143,48 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
reader.dispose(); reader.dispose();
} }
} }
@Test
public void testReadAlphaTransparent() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/1_webp_a.webp"))) {
reader.setInput(stream);
BufferedImage image = reader.read(0);
assertEquals(Transparency.TRANSLUCENT, image.getTransparency());
assertRGBEquals("Expected transparent corner (0, 0)", 0x00000000, image.getRGB(0, 0) & 0xFF000000, 8);
assertRGBEquals("Expected opaque center (200, 150)", 0xff9a4e01, image.getRGB(200, 150), 8);
assertRGBEquals("Expected transparent corner (399, 0)", 0x00000000, image.getRGB(399, 0) & 0xFF000000, 8);
assertRGBEquals("Expected transparent corner (0, 300)", 0x00000000, image.getRGB(0, 300) & 0xFF000000, 8);
assertRGBEquals("Expected transparent corner (399, 300)", 0x00000000, image.getRGB(399, 300) & 0xFF000000, 8);
}
finally {
reader.dispose();
}
}
@Test
public void testAlphaSubsampling() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/alpha_filter.webp"))) {
reader.setInput(stream);
// Read the image using a subsampling factor of 2
ImageReadParam param = new ImageReadParam();
param.setSourceSubsampling(2, 2, 0, 0);
BufferedImage image = reader.read(0, param);
assertRGBEquals("Expected transparent at (100, 265)", 0x00000000, image.getRGB(100, 265) & 0xFF000000, 8);
assertRGBEquals("Expected transparent at (512, 320)", 0x00000000, image.getRGB(512, 320) & 0xFF000000, 8);
assertRGBEquals("Expected opaque at (666, 444)", 0xFF000000, image.getRGB(666, 444) & 0xFF000000, 8);
assertRGBEquals("Expected opaque corner (799, 799)", 0xFF000000, image.getRGB(699, 699) & 0xFF000000, 8);
}
finally {
reader.dispose();
}
}
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-xwd</artifactId> <artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name> <name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+3 -3
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
@@ -98,14 +98,14 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.1</version> <version>4.13.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>3.12.4</version> <version>4.11.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Executable → Regular
+18 -19
View File
@@ -4,7 +4,7 @@
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>TwelveMonkeys</name> <name>TwelveMonkeys</name>
<description>TwelveMonkeys parent POM</description> <description>TwelveMonkeys parent POM</description>
@@ -98,7 +98,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId> <artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version> <version>3.1.0</version>
<configuration> <configuration>
<!-- Prevent gpg from using pinentry programs --> <!-- Prevent gpg from using pinentry programs -->
<gpgArguments> <gpgArguments>
@@ -119,7 +119,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version> <version>3.5.0</version>
<executions> <executions>
<execution> <execution>
<id>attach-javadocs</id> <id>attach-javadocs</id>
@@ -145,7 +145,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId> <artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version> <version>3.4.0</version>
</plugin> </plugin>
<plugin> <plugin>
@@ -167,14 +167,13 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version> <version>3.3.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
<id>attach-sources</id> <id>attach-sources</id>
<goals> <goals>
<goal>jar-no-fork</goal> <goal>jar-no-fork</goal>
<goal>jar</goal>
<goal>test-jar</goal> <goal>test-jar</goal>
</goals> </goals>
</execution> </execution>
@@ -184,7 +183,7 @@
<plugin> <plugin>
<groupId>org.sonatype.plugins</groupId> <groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId> <artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version> <version>1.6.13</version>
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<serverId>ossrh</serverId> <serverId>ossrh</serverId>
@@ -199,7 +198,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version> <version>3.3.1</version>
<configuration> <configuration>
<encoding>UTF-8</encoding> <encoding>UTF-8</encoding>
</configuration> </configuration>
@@ -207,7 +206,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
<inherited>true</inherited> <inherited>true</inherited>
<executions> <executions>
<execution> <execution>
@@ -222,11 +221,11 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.11.0</version>
<inherited>true</inherited> <inherited>true</inherited>
<configuration> <configuration>
<source>1.7</source> <source>8</source>
<target>1.7</target> <target>8</target>
<showDeprecation>false</showDeprecation> <showDeprecation>false</showDeprecation>
<debuglevel>source,lines</debuglevel> <debuglevel>source,lines</debuglevel>
<compilerArguments> <compilerArguments>
@@ -237,7 +236,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.1.2</version>
<configuration> <configuration>
<systemProperties> <systemProperties>
<property> <property>
@@ -250,7 +249,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId> <artifactId>maven-release-plugin</artifactId>
<version>3.0.0-M4</version> <version>3.0.1</version>
<configuration> <configuration>
<autoVersionSubmodules>true</autoVersionSubmodules> <autoVersionSubmodules>true</autoVersionSubmodules>
<releaseProfiles>release</releaseProfiles> <releaseProfiles>release</releaseProfiles>
@@ -260,19 +259,19 @@
<dependency> <dependency>
<groupId>org.apache.maven.scm</groupId> <groupId>org.apache.maven.scm</groupId>
<artifactId>maven-scm-provider-gitexe</artifactId> <artifactId>maven-scm-provider-gitexe</artifactId>
<version>1.11.2</version> <version>2.0.1</version>
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId> <artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0-M1</version> <version>3.1.1</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId> <artifactId>maven-surefire-report-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.1.2</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>
@@ -282,12 +281,12 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId> <artifactId>maven-pmd-plugin</artifactId>
<version>3.14.0</version> <version>3.21.0</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId> <artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version> <version>3.3.0</version>
</plugin> </plugin>
</plugins> </plugins>
+6 -5
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.5-SNAPSHOT</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -15,7 +15,7 @@
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId> <artifactId>servlet-api</artifactId>
<version>2.4</version> <version>2.5</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
@@ -29,7 +29,7 @@
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>4.1.0</version> <version>4.11.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -50,7 +50,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>2.4</version> <version>3.3.0</version>
<configuration> <configuration>
<archive> <archive>
<manifestEntries> <manifestEntries>
@@ -62,10 +62,11 @@
</archive> </archive>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version> <version>3.5.0</version>
<executions> <executions>
<execution> <execution>
<id>jakarta</id> <id>jakarta</id>
@@ -37,6 +37,7 @@ import java.util.List;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.spi.IIORegistry; import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ServiceRegistry; import javax.imageio.spi.ServiceRegistry;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
@@ -57,31 +58,38 @@ import javax.servlet.ServletContextListener;
public final class IIOProviderContextListener implements ServletContextListener { public final class IIOProviderContextListener implements ServletContextListener {
public void contextInitialized(final ServletContextEvent event) { public void contextInitialized(final ServletContextEvent event) {
event.getServletContext().log("Scanning for locally installed ImageIO plugin providers");
// Registers all locally available IIO plugins. // Registers all locally available IIO plugins.
ImageIO.scanForPlugins(); ImageIO.scanForPlugins();
} }
public void contextDestroyed(final ServletContextEvent event) { public void contextDestroyed(final ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
// De-register any locally registered IIO plugins. Relies on each web app having its own context class loader. // De-register any locally registered IIO plugins. Relies on each web app having its own context class loader.
final IIORegistry registry = IIORegistry.getDefaultInstance(); LocalFilter localFilter = new LocalFilter(Thread.currentThread().getContextClassLoader()); // scanForPlugins uses context class loader
final LocalFilter localFilter = new LocalFilter(Thread.currentThread().getContextClassLoader()); // scanForPlugins uses context class loader
IIORegistry registry = IIORegistry.getDefaultInstance();
Iterator<Class<?>> categories = registry.getCategories(); Iterator<Class<?>> categories = registry.getCategories();
while (categories.hasNext()) { while (categories.hasNext()) {
Class<?> category = categories.next(); deregisterLocalProvidersForCategory(registry, localFilter, categories.next(), servletContext);
Iterator<?> providers = registry.getServiceProviders(category, localFilter, false); }
}
// Copy the providers, as de-registering while iterating over providers will lead to ConcurrentModificationExceptions. private static <T> void deregisterLocalProvidersForCategory(IIORegistry registry, LocalFilter localFilter, Class<T> category, ServletContext context) {
List<Object> providersCopy = new ArrayList<>(); Iterator<T> providers = registry.getServiceProviders(category, localFilter, false);
while (providers.hasNext()) {
providersCopy.add(providers.next());
}
for (Object provider : providersCopy) { // Copy the providers, as de-registering while iterating over providers will lead to ConcurrentModificationExceptions.
registry.deregisterServiceProvider(provider); List<T> providersCopy = new ArrayList<>();
event.getServletContext().log(String.format("Unregistered locally installed provider class: %s", provider.getClass())); while (providers.hasNext()) {
} providersCopy.add(providers.next());
}
for (T provider : providersCopy) {
registry.deregisterServiceProvider(provider, category);
context.log(String.format("Unregistered locally installed provider class: %s", provider.getClass()));
} }
} }
@@ -33,7 +33,6 @@ package com.twelvemonkeys.servlet.image;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Locale; import java.util.Locale;
@@ -54,32 +53,29 @@ import org.junit.Test;
* @version $Id: IIOProviderContextListenerTest.java,v 1.0 02.01.14 12:33 haraldk Exp$ * @version $Id: IIOProviderContextListenerTest.java,v 1.0 02.01.14 12:33 haraldk Exp$
*/ */
public class IIOProviderContextListenerTest { public class IIOProviderContextListenerTest {
private final ServletContext context = mock(ServletContext.class);
private final ServletContextEvent initialized = new ServletContextEvent(context);
private final ServletContextEvent destroyed = new ServletContextEvent(context);
@Test @Test
public void testContextInitialized() { public void testContextInitialized() {
ServletContextListener listener = new IIOProviderContextListener(); ServletContextListener listener = new IIOProviderContextListener();
listener.contextInitialized(mock(ServletContextEvent.class)); listener.contextInitialized(initialized);
} }
@Test @Test
public void testContextDestroyed() { public void testContextDestroyed() {
ServletContext context = mock(ServletContext.class);
ServletContextEvent destroyed = mock(ServletContextEvent.class);
when(destroyed.getServletContext()).thenReturn(context);
ServletContextListener listener = new IIOProviderContextListener(); ServletContextListener listener = new IIOProviderContextListener();
listener.contextInitialized(mock(ServletContextEvent.class)); listener.contextInitialized(initialized);
listener.contextDestroyed(destroyed); listener.contextDestroyed(destroyed);
} }
// Regression test for issue #29 // Regression test for issue #29
@Test @Test
public void testDestroyConcurrentModRegression() { public void testDestroyConcurrentModRegression() {
ServletContext context = mock(ServletContext.class);
ServletContextEvent destroyed = mock(ServletContextEvent.class);
when(destroyed.getServletContext()).thenReturn(context);
ServletContextListener listener = new IIOProviderContextListener(); ServletContextListener listener = new IIOProviderContextListener();
listener.contextInitialized(mock(ServletContextEvent.class)); listener.contextInitialized(initialized);
ImageReaderSpi provider1 = new MockImageReaderSpiOne(); ImageReaderSpi provider1 = new MockImageReaderSpiOne();
ImageReaderSpi provider2 = new MockImageReaderSpiToo(); ImageReaderSpi provider2 = new MockImageReaderSpiToo();